From ca7c6238d1d4b24c811746ac7bf2286059fb62bf Mon Sep 17 00:00:00 2001 From: pawcode Development Date: Fri, 22 Mar 2024 20:31:01 +0100 Subject: [PATCH] feat(*): add missing stories --- .gitignore | 3 +- .storybook/main.ts | 2 +- .storybook/utils.ts | 2 +- angular.json | 2 +- documentation.json | 11160 ---------------- .../export/export-modal.component.stories.ts | 33 + .../export-download.component.stories.ts | 30 + .../export-format.component.stories.ts | 19 + .../export-success.component.stories.ts | 40 + .../request-format.component.stories.ts | 12 + .../home-generator.component.stories.ts | 34 + .../home-manual.component.stories.ts | 12 + .../home-support.component.stories.ts | 12 + .../layout/constants/navigation-entries.ts | 23 + src/app/layout/layout.component.css | 4 + src/app/layout/layout.component.html | 7 +- src/app/layout/layout.component.ts | 24 +- .../layout-analytics-consent.component.css | 4 - ...out-analytics-consent.component.stories.ts | 30 + .../layout-analytics-consent.component.ts | 2 +- .../layout-footer.component.stories.ts | 27 + .../layout-navigation.component.stories.ts | 61 + .../layout-options.component.stories.ts | 37 + src/app/shared/constants/tailwind-colors.ts | 339 +- .../ui/toast/toast.component.stories.ts | 18 +- .../view-palette.component.stories.ts | 56 + 26 files changed, 784 insertions(+), 11209 deletions(-) delete mode 100644 documentation.json create mode 100644 src/app/export/export-modal.component.stories.ts create mode 100644 src/app/export/ui/export-download/export-download.component.stories.ts create mode 100644 src/app/export/ui/export-format/export-format.component.stories.ts create mode 100644 src/app/export/ui/export-success/export-success.component.stories.ts create mode 100644 src/app/export/ui/request-format/request-format.component.stories.ts create mode 100644 src/app/home/ui/home-generator/home-generator.component.stories.ts create mode 100644 src/app/home/ui/home-manual/home-manual.component.stories.ts create mode 100644 src/app/home/ui/home-support/home-support.component.stories.ts create mode 100644 src/app/layout/constants/navigation-entries.ts delete mode 100644 src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.css create mode 100644 src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.stories.ts create mode 100644 src/app/layout/ui/layout-footer/layout-footer.component.stories.ts create mode 100644 src/app/layout/ui/layout-navigation/layout-navigation.component.stories.ts create mode 100644 src/app/layout/ui/layout-options/layout-options.component.stories.ts create mode 100644 src/app/view/ui/view-palette/view-palette.component.stories.ts diff --git a/.gitignore b/.gitignore index 1fefca3..4df3203 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ testem.log .DS_Store Thumbs.db -*storybook.log \ No newline at end of file +*storybook.log +documentation.json \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts index 8886033..aa6b76d 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -15,6 +15,6 @@ const config: StorybookConfig = { docs: { autodocs: 'tag' }, - staticDirs: ['../src/assets'] + staticDirs: [{ from: '../src/assets', to: 'assets' }] }; export default config; diff --git a/.storybook/utils.ts b/.storybook/utils.ts index 6c17f6c..32697d0 100644 --- a/.storybook/utils.ts +++ b/.storybook/utils.ts @@ -8,7 +8,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader'; TranslateModule.forRoot({ loader: { provide: TranslateLoader, - useFactory: (http: HttpClient) => new TranslateHttpLoader(http, './i18n/', '.json'), + useFactory: (http: HttpClient) => new TranslateHttpLoader(http), deps: [HttpClient] } }) diff --git a/angular.json b/angular.json index 51f6048..67e3193 100644 --- a/angular.json +++ b/angular.json @@ -89,7 +89,7 @@ "configDir": ".storybook", "browserTarget": "rainbow-palette:build", "compodoc": true, - "compodocArgs": ["-e", "json", "-d", ".", "--disablePrivate", "--disableProtected", "-w"], + "compodocArgs": ["-e", "json", "-d", ".", "--disablePrivate", "--disableProtected", "-w", "-t"], "port": 6006 } }, diff --git a/documentation.json b/documentation.json deleted file mode 100644 index be6ec15..0000000 --- a/documentation.json +++ /dev/null @@ -1,11160 +0,0 @@ -{ - "pipes": [], - "interfaces": [ - { - "name": "ColorMapEntry", - "id": "interface-ColorMapEntry-018fd140a60e9c39bc07bf5713bec5497d29f4f7d88fc7aca6a0e877ab89a51a82daceb95713891c65620f8c09a813d67c6adf80f81c968bf0a83366b57d7668", - "file": "src/app/shared/data-access/color-name.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "interface", - "sourceCode": "import { HttpClient } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { firstValueFrom } from 'rxjs';\nimport { Shade } from '../model/shade.model';\nimport { HSLObject } from '../types/color-format';\nimport { ToastService } from './toast.service';\n\ninterface ColorMapEntry {\n hue: number;\n saturation: number;\n lightness: number;\n name: string;\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ColorNameService {\n private readonly _http = inject(HttpClient);\n private readonly _toastService = inject(ToastService);\n\n private colorDictionary?: Array;\n\n public async getColorName(shade: Shade): Promise {\n if (!this.colorDictionary) {\n await this.loadColorMap();\n }\n\n if (!this.colorDictionary) {\n return shade.hex.substring(1);\n }\n\n const name = this.colorDictionary\n .filter((entry) => {\n if (shade.hsl.S === 0) {\n return entry.hue === -1;\n } else {\n return entry.hue !== -1;\n }\n })\n .map((entry) => ({\n name: entry.name,\n distance: this.calculateDistance(shade.hsl, entry)\n }))\n .sort((a, b) => a.distance - b.distance)\n .map((entry) => entry.name)\n .at(0);\n\n if (!name) {\n console.warn('No color name found for', shade.hex);\n return shade.hex.substring(1);\n }\n\n return name.replace(/(\\w)(\\w*)/g, (_, first, rest) => first.toUpperCase() + rest.toLowerCase());\n }\n\n private async loadColorMap(): Promise {\n try {\n const data = await firstValueFrom(this._http.get('/assets/color-dictionary.csv', { responseType: 'text' }));\n\n this.colorDictionary = data\n .split('\\n')\n .filter((_, index) => index > 0)\n .map((line) => line.split(';'))\n .map((entry) => ({\n name: entry[0],\n hue: parseInt(entry[1], 10),\n saturation: parseInt(entry[2], 10),\n lightness: parseInt(entry[3], 10)\n }));\n } catch (error) {\n this._toastService.showToast({\n type: 'error',\n message: 'toast.error.load-color-map'\n });\n }\n }\n\n private calculateDistance(hsl: HSLObject, entry: ColorMapEntry): number {\n return (\n 10 * Math.abs(hsl.H - entry.hue) + 5 * Math.abs(hsl.S - entry.saturation) + Math.abs(hsl.L - entry.lightness)\n );\n }\n}\n\nexport class ColorNameServiceMock {\n public async getColorName(shade: Shade): Promise {\n return shade.hex.substring(1);\n }\n}\n", - "properties": [ - { - "name": "hue", - "deprecated": false, - "deprecationMessage": "", - "type": "number", - "optional": false, - "description": "", - "line": 9 - }, - { - "name": "lightness", - "deprecated": false, - "deprecationMessage": "", - "type": "number", - "optional": false, - "description": "", - "line": 11 - }, - { - "name": "name", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 12 - }, - { - "name": "saturation", - "deprecated": false, - "deprecationMessage": "", - "type": "number", - "optional": false, - "description": "", - "line": 10 - } - ], - "indexSignatures": [], - "kind": 171, - "methods": [], - "extends": [] - }, - { - "name": "Formatter", - "id": "interface-Formatter-5c9785d3ec480b42d4d58b77d077631abc9984fb21a43f0a40d15ab9940e1c0e61ba185bd1874dc1f42a55854b8aa2d0e70a8209a6ac151f4ad21db93f59a9b1", - "file": "src/app/shared/interfaces/formatter.interface.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "interface", - "sourceCode": "import { Color } from '../model/color.model';\nimport { Palette } from '../model/palette.model';\nimport { Shade } from '../model/shade.model';\n\nexport interface Formatter {\n filename: string;\n mimeType: string;\n\n formatFile(palette: Palette): string;\n\n formatPalette(palette: Palette): string;\n\n formatColor(color: Color): string;\n\n formatShade(shade: Shade, name: string): string;\n}\n", - "properties": [ - { - "name": "filename", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 6 - }, - { - "name": "mimeType", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 7 - } - ], - "indexSignatures": [], - "kind": 173, - "methods": [ - { - "name": "formatColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 13, - "deprecated": false, - "deprecationMessage": "", - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatFile", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 9, - "deprecated": false, - "deprecationMessage": "", - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatPalette", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 11, - "deprecated": false, - "deprecationMessage": "", - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatShade", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 15, - "deprecated": false, - "deprecationMessage": "", - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "extends": [] - }, - { - "name": "LanguageOption", - "id": "interface-LanguageOption-a5cff5c2fec7085486b2e0ac1a231d2d028f8225aabd4fce042b665f17c333de01a3e34664d5d26a5a37c7db65665ba8b011aa4bba5e56e3c3242f229ffd8c17", - "file": "src/app/layout/types/language.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "interface", - "sourceCode": "export type Language = 'en' | 'de';\n\n/**\n * A language option for the layout options.\n */\nexport interface LanguageOption {\n /**\n * The value of the language option.\n */\n value: Language;\n\n /**\n * The label of the language option.\n */\n label: string;\n\n /**\n * The flag of the language option.\n */\n flag: string;\n}\n", - "properties": [ - { - "name": "flag", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "

The flag of the language option.

\n", - "line": 23, - "rawdescription": "\n\nThe flag of the language option.\n" - }, - { - "name": "label", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "

The label of the language option.

\n", - "line": 18, - "rawdescription": "\n\nThe label of the language option.\n" - }, - { - "name": "value", - "deprecated": false, - "deprecationMessage": "", - "type": "Language", - "optional": false, - "description": "

The value of the language option.

\n", - "line": 13, - "rawdescription": "\n\nThe value of the language option.\n" - } - ], - "indexSignatures": [], - "kind": 171, - "description": "

A language option for the layout options.

\n", - "rawdescription": "\n\nA language option for the layout options.\n", - "methods": [], - "extends": [] - }, - { - "name": "NavigationEntry", - "id": "interface-NavigationEntry-81e8be0b878f6753498ac4ba7ceb9e84cb8bd886ae0a2a8ca9f71c13d70b2f6d3de8cfe819b94759d1c892390c68f595b8c09b71fc35caaaa0d30075376bf720", - "file": "src/app/layout/types/navigation-entry.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "interface", - "sourceCode": "export interface NavigationEntry {\n /**\n * The title of the navigation entry.\n * This can also be a translation key.\n */\n title: string;\n\n /**\n * The path to navigate to.\n */\n path: string;\n\n /**\n * The icon to display next to the title.\n * This has to be an icon from an `@ng-icons` integration or a custom SVG.\n */\n icon: string;\n\n /**\n * The description of the navigation entry.\n * This can also be a translation key.\n * This is shown as a tooltip on hover.\n */\n description: string;\n}\n", - "properties": [ - { - "name": "description", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "

The description of the navigation entry.\nThis can also be a translation key.\nThis is shown as a tooltip on hover.

\n", - "line": 27, - "rawdescription": "\n\nThe description of the navigation entry.\nThis can also be a translation key.\nThis is shown as a tooltip on hover.\n" - }, - { - "name": "icon", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "

The icon to display next to the title.\nThis has to be an icon from an @ng-icons integration or a custom SVG.

\n", - "line": 20, - "rawdescription": "\n\nThe icon to display next to the title.\nThis has to be an icon from an `@ng-icons` integration or a custom SVG.\n" - }, - { - "name": "path", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "

The path to navigate to.

\n", - "line": 14, - "rawdescription": "\n\nThe path to navigate to.\n" - }, - { - "name": "title", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "

The title of the navigation entry.\nThis can also be a translation key.

\n", - "line": 9, - "rawdescription": "\n\nThe title of the navigation entry.\nThis can also be a translation key.\n" - } - ], - "indexSignatures": [], - "kind": 171, - "description": "

A navigation entry for the layout navigation.

\n", - "rawdescription": "\n\nA navigation entry for the layout navigation.\n", - "methods": [], - "extends": [] - }, - { - "name": "ThemeOption", - "id": "interface-ThemeOption-d0ccdf3a4d6f3d4ef2785b3032c2a2a245130961d1ba37d4e4b5a723583d8498af2c392081ad61f11edd095664aaf743923fdfe0da76bc06fc9ead838f7a5bf9", - "file": "src/app/shared/types/theme.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "interface", - "sourceCode": "import { IconType } from '@ng-icons/core';\n\n/**\n * A theme type.\n */\nexport type Theme = 'light' | 'dark';\n\n/**\n * A theme option for the layout options.\n */\nexport interface ThemeOption {\n /*\n * The value of the theme option.\n */\n value: Theme;\n\n /**\n * The label of the theme option.\n */\n label: string;\n\n /**\n * The icon of the theme option.\n */\n icon: IconType;\n}\n", - "properties": [ - { - "name": "icon", - "deprecated": false, - "deprecationMessage": "", - "type": "IconType", - "optional": false, - "description": "

The icon of the theme option.

\n", - "line": 25, - "rawdescription": "\n\nThe icon of the theme option.\n" - }, - { - "name": "label", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "

The label of the theme option.

\n", - "line": 20, - "rawdescription": "\n\nThe label of the theme option.\n" - }, - { - "name": "value", - "deprecated": false, - "deprecationMessage": "", - "type": "Theme", - "optional": false, - "description": "", - "line": 15 - } - ], - "indexSignatures": [], - "kind": 171, - "description": "

A theme option for the layout options.

\n", - "rawdescription": "\n\nA theme option for the layout options.\n", - "methods": [], - "extends": [] - }, - { - "name": "Toast", - "id": "interface-Toast-7799c14a107e765ad4abfc6c8abd5cae517b8bbe988f2ae59f73cca2af8c6c4b7afc8eb58cdd7ade37d8bbdcff3afc7580744de69d2ca1e8e3cf499685ba4dea", - "file": "src/app/shared/interfaces/toast.interface.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "interface", - "sourceCode": "export const ToastTimeouts = {\n success: 3000,\n error: 10000,\n warning: 5000,\n info: 5000,\n default: 5000,\n test: 10\n};\n\n/**\n * The interface for the toast.\n */\nexport interface Toast {\n /**\n * The type of the toast.\n * The type is used to determine the timeout and styling of the toast.\n */\n type?: keyof typeof ToastTimeouts;\n /**\n * The message to be displayed in the toast.\n * It can be a string or a translation key.\n */\n message: string;\n /**\n * The parameters to be used in the translation.\n */\n parameters?: Record;\n}\n", - "properties": [ - { - "name": "message", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "

The message to be displayed in the toast.\nIt can be a string or a translation key.

\n", - "line": 23, - "rawdescription": "\n\nThe message to be displayed in the toast.\nIt can be a string or a translation key.\n" - }, - { - "name": "parameters", - "deprecated": false, - "deprecationMessage": "", - "type": "Record", - "optional": true, - "description": "

The parameters to be used in the translation.

\n", - "line": 27, - "rawdescription": "\n\nThe parameters to be used in the translation.\n" - }, - { - "name": "type", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": true, - "description": "

The type of the toast.\nThe type is used to determine the timeout and styling of the toast.

\n", - "line": 18, - "rawdescription": "\n\nThe type of the toast.\nThe type is used to determine the timeout and styling of the toast.\n" - } - ], - "indexSignatures": [], - "kind": 171, - "description": "

The interface for the toast.

\n", - "rawdescription": "\n\nThe interface for the toast.\n", - "methods": [], - "extends": [] - }, - { - "name": "User", - "id": "interface-User-d4821beac0e4a852e513391b7470981c4353fdd95a18628bb68136c8695b489ee15b926e2e5031bc26cf30e1508e0a362c3ee13a06734702b4d15af140e485d5", - "file": "src/stories/user.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "interface", - "sourceCode": "export interface User {\n name: string;\n}\n", - "properties": [ - { - "name": "name", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 2 - } - ], - "indexSignatures": [], - "kind": 171, - "methods": [], - "extends": [] - } - ], - "injectables": [ - { - "name": "AnalyticsService", - "id": "injectable-AnalyticsService-d61fee52d86051b7e0d8bc5ed9f8df94a29a624755b7cfdd547f335d1d8d0ab33b644d7ec9e825f748fabf435732d1f965f41dd7e22730f7636852c8f8f3be20", - "file": "src/app/shared/data-access/analytics.service.ts", - "properties": [], - "methods": [ - { - "name": "acceptAnalytics", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 174, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "declineAnalytics", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 182, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "setIsPwa", - "args": [ - { - "name": "isPwa", - "type": "boolean", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 170, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "isPwa", - "type": "boolean", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "trackEvent", - "args": [ - { - "name": "category", - "type": "TrackingEventCategory", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "action", - "type": "TrackingEventAction", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "name", - "type": "TrackingEventName", - "deprecated": false, - "deprecationMessage": "", - "optional": true - }, - { - "name": "value", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 192, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nTrack a custom event\n", - "description": "

Track a custom event

\n", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": { - "pos": 5802, - "end": 5810, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "category" - }, - "type": "TrackingEventCategory", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 5796, - "end": 5801, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

Event category

\n" - }, - { - "name": { - "pos": 5838, - "end": 5844, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "action" - }, - "type": "TrackingEventAction", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 5832, - "end": 5837, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

Event action

\n" - }, - { - "name": { - "pos": 5870, - "end": 5874, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "name" - }, - "type": "TrackingEventName", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "pos": 5864, - "end": 5869, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

Event name

\n" - }, - { - "name": "value", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "trackPaletteExport", - "args": [ - { - "name": "format", - "type": "ExportFormat", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "option", - "type": "ExportOption", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 224, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nTrack the export of a palette\n", - "description": "

Track the export of a palette

\n", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": { - "pos": 6517, - "end": 6523, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "format" - }, - "type": "ExportFormat", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 6511, - "end": 6516, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

Format to export

\n" - }, - { - "name": { - "pos": 6553, - "end": 6559, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "option" - }, - "type": "ExportOption", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 6547, - "end": 6552, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

Export method

\n" - } - ] - }, - { - "name": "trackPaletteGeneration", - "args": [ - { - "name": "scheme", - "type": "PaletteScheme", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 254, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nTrack the generation of a palette\n", - "description": "

Track the generation of a palette

\n", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": { - "pos": 7535, - "end": 7541, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "scheme" - }, - "type": "PaletteScheme", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 7529, - "end": 7534, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

Palette scheme

\n" - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { MatomoTracker } from 'ngx-matomo-client';\nimport { ExportFormat } from '../constants/export-format';\nimport { PaletteScheme } from '../constants/palette-scheme';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\nimport { TrackingEventAction, TrackingEventCategory, TrackingEventName } from '../enums/tracking-event';\nimport { ExportOption } from '../types/export-option';\nimport { LanguageService } from './language.service';\nimport { OfflineService } from './offline.service';\nimport { ThemeService } from './theme.service';\nimport { ToastService } from './toast.service';\nimport { VersionService } from './version.service';\n\nexport enum CustomDimension {\n LANGUAGE = 1,\n THEME = 2,\n PWA = 3,\n VERSION = 4\n}\n\nexport enum AnalyticsStatus {\n UNSET = 'UNSET',\n ACCEPTED = 'ACCEPTED',\n DECLINED = 'DECLINED'\n}\n\ntype TrackingEvent = {\n category: TrackingEventCategory;\n action: TrackingEventAction;\n name?: TrackingEventName;\n value?: number;\n timestamp: number;\n};\n\n@Injectable({\n providedIn: 'root'\n})\nexport class AnalyticsService {\n private readonly _tracker = inject(MatomoTracker);\n private readonly _themeService = inject(ThemeService);\n private readonly _languageService = inject(LanguageService);\n private readonly _versionService = inject(VersionService);\n private readonly _offlineService = inject(OfflineService);\n private readonly _toastService = inject(ToastService);\n\n private readonly _status = signal(AnalyticsStatus.UNSET);\n\n public get status(): Signal {\n return this._status.asReadonly();\n }\n\n public constructor() {\n this._setup();\n\n effect(() => {\n const status = this._status();\n if (status === AnalyticsStatus.UNSET) {\n return;\n }\n\n const analytics = JSON.stringify({\n status,\n expiry: Date.now() + 1000 * 60 * 60 * 24 * 90\n });\n localStorage.setItem(LocalStorageKey.ANALYTICS, analytics);\n\n if (status === AnalyticsStatus.ACCEPTED) {\n this._tracker.setConsentGiven();\n this._processEvents('disabled');\n } else if (status === AnalyticsStatus.DECLINED) {\n this._tracker.forgetConsentGiven();\n }\n });\n\n // Send a heartbeat every 30 seconds\n this._tracker.enableHeartBeatTimer(30);\n\n // Track current theme\n effect(() => {\n this._tracker.setCustomDimension(CustomDimension.THEME, this._themeService.theme());\n });\n\n // Track current language\n effect(() => {\n this._tracker.setCustomDimension(CustomDimension.LANGUAGE, this._languageService.language());\n });\n\n // Track app version\n this._tracker.setCustomDimension(CustomDimension.VERSION, this._versionService.appVersion);\n\n effect(async () => {\n if (!this._offlineService.isOffline()) {\n this._processEvents('offline');\n }\n });\n }\n\n private _setup(): void {\n const analytics = localStorage.getItem(LocalStorageKey.ANALYTICS);\n if (analytics) {\n try {\n const parsed = JSON.parse(analytics);\n if (parsed.expiry < Date.now()) {\n localStorage.removeItem(LocalStorageKey.ANALYTICS);\n } else {\n if (parsed.status === AnalyticsStatus.ACCEPTED) {\n this._status.set(AnalyticsStatus.ACCEPTED);\n } else if (parsed.status === AnalyticsStatus.DECLINED) {\n this._status.set(AnalyticsStatus.DECLINED);\n }\n }\n } catch (error) {\n console.error('Failed to parse analytics status', error);\n }\n }\n\n let userId = localStorage.getItem(LocalStorageKey.USER_ID);\n if (!userId) {\n // Generate random user ID with 16 hex characters\n userId = Array.from({ length: 16 }, () => Math.floor(Math.random() * 16).toString(16)).join('');\n localStorage.setItem(LocalStorageKey.USER_ID, userId);\n }\n this._tracker.setUserId(userId);\n }\n\n private _queueEvent(event: Omit, type: 'disabled' | 'offline'): void {\n const key = type === 'disabled' ? LocalStorageKey.EVENTS_DISABLED : LocalStorageKey.EVENTS_OFFLINE;\n const events = JSON.parse(localStorage.getItem(key) || '[]') as Array;\n\n events.push({ ...event, timestamp: Date.now() });\n localStorage.setItem(key, JSON.stringify(events));\n }\n\n private _processEvents(type: 'disabled' | 'offline'): void {\n const key = type === 'disabled' ? LocalStorageKey.EVENTS_DISABLED : LocalStorageKey.EVENTS_OFFLINE;\n const cache = localStorage.getItem(key);\n if (!cache) {\n return;\n }\n\n const events = JSON.parse(cache) as Array;\n for (const event of events) {\n // Skip events older than 24 hours\n if (event.timestamp < Date.now() - 1000 * 60 * 60 * 24) {\n continue;\n }\n\n this._tracker.trackEvent(event.category, event.action, event.name);\n }\n\n if (key === LocalStorageKey.EVENTS_DISABLED) {\n this.trackEvent(\n TrackingEventCategory.EVENTS_DISABLED,\n TrackingEventAction.EVENTS_DISABLED,\n undefined,\n events.length\n );\n } else {\n this.trackEvent(\n TrackingEventCategory.EVENTS_OFFLINE,\n TrackingEventAction.EVENTS_OFFLINE,\n undefined,\n events.length\n );\n }\n\n localStorage.removeItem(key);\n }\n\n public setIsPwa(isPwa: boolean): void {\n this._tracker.setCustomDimension(CustomDimension.PWA, isPwa ? 'PWA' : 'Web');\n }\n\n public acceptAnalytics(): void {\n this._status.set(AnalyticsStatus.ACCEPTED);\n this._toastService.showToast({\n type: 'info',\n message: 'toast.info.analytics-accepted'\n });\n }\n\n public declineAnalytics(): void {\n this._status.set(AnalyticsStatus.DECLINED);\n }\n\n /**\n * Track a custom event\n * @param category Event category\n * @param action Event action\n * @param name Event name\n */\n public trackEvent(\n category: TrackingEventCategory,\n action: TrackingEventAction,\n name?: TrackingEventName,\n value?: number\n ): void {\n if (this._status() !== AnalyticsStatus.ACCEPTED) {\n this._queueEvent(\n {\n category,\n action,\n name,\n value\n },\n 'disabled'\n );\n return;\n }\n\n if (this._offlineService.isOffline()) {\n this._queueEvent({ category, action, name, value }, 'offline');\n return;\n }\n\n this._tracker.trackEvent(category, action, name, value);\n }\n\n /**\n * Track the export of a palette\n * @param format Format to export\n * @param option Export method\n */\n public trackPaletteExport(format: ExportFormat, option: ExportOption): void {\n const action =\n option === 'copy' ? TrackingEventAction.EXPORT_PALETTE_COPY : TrackingEventAction.EXPORT_PALETTE_FILE;\n\n let name: TrackingEventName | undefined;\n switch (format) {\n case ExportFormat.CSS:\n name = TrackingEventName.EXPORT_PALETTE_CSS;\n break;\n case ExportFormat.SCSS:\n name = TrackingEventName.EXPORT_PALETTE_SCSS;\n break;\n case ExportFormat.LESS:\n name = TrackingEventName.EXPORT_PALETTE_LESS;\n break;\n case ExportFormat.TAILWIND:\n name = TrackingEventName.EXPORT_PALETTE_TAILWIND;\n break;\n default:\n console.warn('Unknown palette export format', format);\n name = TrackingEventName.EXPORT_PALETTE_UNKNOWN;\n }\n\n this.trackEvent(TrackingEventCategory.EXPORT_PALETTE, action, name);\n }\n\n /**\n * Track the generation of a palette\n * @param scheme Palette scheme\n */\n public trackPaletteGeneration(scheme: PaletteScheme): void {\n let name: TrackingEventName | undefined;\n switch (scheme) {\n case PaletteScheme.RAINBOW:\n name = TrackingEventName.GENERATE_PALETTE_RAINBOW;\n break;\n case PaletteScheme.SURPRISE:\n name = TrackingEventName.GENERATE_PALETTE_SURPRISE;\n break;\n case PaletteScheme.MONOCHROME:\n name = TrackingEventName.GENERATE_PALETTE_MONOCHROME;\n break;\n case PaletteScheme.ANALOGOUS:\n name = TrackingEventName.GENERATE_PALETTE_ANALOGOUS;\n break;\n case PaletteScheme.COMPLEMENTARY:\n name = TrackingEventName.GENERATE_PALETTE_COMPLEMENTARY;\n break;\n case PaletteScheme.SPLIT_COMPLEMENTARY:\n name = TrackingEventName.GENERATE_PALETTE_SPLIT_COMPLEMENTARY;\n break;\n case PaletteScheme.TRIADIC:\n name = TrackingEventName.GENERATE_PALETTE_TRIADIC;\n break;\n case PaletteScheme.COMPOUND:\n name = TrackingEventName.GENERATE_PALETTE_COMPOUND;\n break;\n default:\n console.warn('Unknown palette generation scheme', scheme);\n name = TrackingEventName.GENERATE_PALETTE_UNKNOWN;\n }\n\n this.trackEvent(TrackingEventCategory.GENERATE_PALETTE, TrackingEventAction.GENERATE_PALETTE, name);\n }\n}\n\nexport class AnalyticsServiceMock {\n public readonly status = signal(AnalyticsStatus.UNSET).asReadonly();\n\n public setIsPwa(_isPwa: boolean): void {}\n\n public acceptAnalytics(): void {}\n\n public declineAnalytics(): void {}\n\n public trackEvent(_category: TrackingEventCategory, _action: TrackingEventAction, _name?: TrackingEventName): void {}\n\n public trackPaletteExport(_format: ExportFormat, _option: ExportOption): void {}\n\n public trackPaletteGeneration(_scheme: PaletteScheme): void {}\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 50, - "modifierKind": [ - 125 - ] - }, - "accessors": { - "status": { - "name": "status", - "getSignature": { - "name": "status", - "type": "", - "returnType": "Signal", - "line": 48 - } - } - }, - "extends": [], - "type": "injectable" - }, - { - "name": "ColorEditorService", - "id": "injectable-ColorEditorService-bebdbf0d59d8c1cea50ce79f00d2ec1e27f082823d34ae0701750b2dfc75b1d7758237ccc562c7a31af04f432d7352e868f49bb0143f65cf941263088d34038f", - "file": "src/app/editor/data-access/color-editor.service.ts", - "properties": [], - "methods": [ - { - "name": "openColorEditor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "shadeIndex", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 14, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "shadeIndex", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Dialog } from '@angular/cdk/dialog';\nimport { Injectable, inject, signal } from '@angular/core';\nimport { firstValueFrom, tap } from 'rxjs';\nimport { Color } from '../../shared/model';\nimport { sleep } from '../../shared/utils/sleep';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ColorEditorService {\n private readonly _dialog = inject(Dialog);\n private readonly _isModalOpen = signal(false);\n\n public async openColorEditor(color: Color, shadeIndex?: number): Promise {\n if (this._isModalOpen()) {\n return;\n }\n this._isModalOpen.set(true);\n\n const editor = await import('../editor.component').then((c) => c.EditorComponent);\n const dialogRef = this._dialog.open(editor, {\n backdropClass: 'rp-modal-backdrop',\n data: {\n color,\n shadeIndex\n },\n disableClose: true,\n panelClass: 'rp-modal-panel'\n });\n\n return await firstValueFrom(dialogRef.closed.pipe(tap(() => this._isModalOpen.set(false))));\n }\n}\n\nexport class ColorEditorServiceMock {\n public async openColorEditor(color: Color, _shadeIndex?: number): Promise {\n await sleep(10);\n\n return color.copy();\n }\n}\n", - "extends": [], - "type": "injectable" - }, - { - "name": "ColorNameService", - "id": "injectable-ColorNameService-018fd140a60e9c39bc07bf5713bec5497d29f4f7d88fc7aca6a0e877ab89a51a82daceb95713891c65620f8c09a813d67c6adf80f81c968bf0a83366b57d7668", - "file": "src/app/shared/data-access/color-name.service.ts", - "properties": [], - "methods": [ - { - "name": "getColorName", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 24, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { HttpClient } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { firstValueFrom } from 'rxjs';\nimport { Shade } from '../model/shade.model';\nimport { HSLObject } from '../types/color-format';\nimport { ToastService } from './toast.service';\n\ninterface ColorMapEntry {\n hue: number;\n saturation: number;\n lightness: number;\n name: string;\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ColorNameService {\n private readonly _http = inject(HttpClient);\n private readonly _toastService = inject(ToastService);\n\n private colorDictionary?: Array;\n\n public async getColorName(shade: Shade): Promise {\n if (!this.colorDictionary) {\n await this.loadColorMap();\n }\n\n if (!this.colorDictionary) {\n return shade.hex.substring(1);\n }\n\n const name = this.colorDictionary\n .filter((entry) => {\n if (shade.hsl.S === 0) {\n return entry.hue === -1;\n } else {\n return entry.hue !== -1;\n }\n })\n .map((entry) => ({\n name: entry.name,\n distance: this.calculateDistance(shade.hsl, entry)\n }))\n .sort((a, b) => a.distance - b.distance)\n .map((entry) => entry.name)\n .at(0);\n\n if (!name) {\n console.warn('No color name found for', shade.hex);\n return shade.hex.substring(1);\n }\n\n return name.replace(/(\\w)(\\w*)/g, (_, first, rest) => first.toUpperCase() + rest.toLowerCase());\n }\n\n private async loadColorMap(): Promise {\n try {\n const data = await firstValueFrom(this._http.get('/assets/color-dictionary.csv', { responseType: 'text' }));\n\n this.colorDictionary = data\n .split('\\n')\n .filter((_, index) => index > 0)\n .map((line) => line.split(';'))\n .map((entry) => ({\n name: entry[0],\n hue: parseInt(entry[1], 10),\n saturation: parseInt(entry[2], 10),\n lightness: parseInt(entry[3], 10)\n }));\n } catch (error) {\n this._toastService.showToast({\n type: 'error',\n message: 'toast.error.load-color-map'\n });\n }\n }\n\n private calculateDistance(hsl: HSLObject, entry: ColorMapEntry): number {\n return (\n 10 * Math.abs(hsl.H - entry.hue) + 5 * Math.abs(hsl.S - entry.saturation) + Math.abs(hsl.L - entry.lightness)\n );\n }\n}\n\nexport class ColorNameServiceMock {\n public async getColorName(shade: Shade): Promise {\n return shade.hex.substring(1);\n }\n}\n", - "extends": [], - "type": "injectable" - }, - { - "name": "ColorService", - "id": "injectable-ColorService-2002a7ef73112e34b70f8a1fb569570d7a6f93cb2f967f831884914090099e87d408224d3ddc030761633d2b983b163f22768b1d7addd793e4ca30c0b1a01ebe", - "file": "src/app/shared/data-access/color.service.ts", - "properties": [], - "methods": [ - { - "name": "randomColor", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 13, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "regenerateShades", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 27, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nRegenerate every shade but the fixed ones.\n", - "description": "

Regenerate every shade but the fixed ones.

\n", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": { - "pos": 694, - "end": 699, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "color" - }, - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 688, - "end": 693, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "" - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, inject } from '@angular/core';\nimport { Value } from '../model';\nimport { Color } from '../model/color.model';\nimport { Shade } from '../model/shade.model';\nimport { ColorNameService } from './color-name.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ColorService {\n private readonly _colorNameService = inject(ColorNameService);\n\n public async randomColor(): Promise {\n const shade = Shade.random();\n const name = await this._colorNameService.getColorName(shade);\n const color = new Color([Shade.random()], name);\n\n this.regenerateShades(color);\n\n return color;\n }\n\n /**\n * Regenerate every shade but the fixed ones.\n * @param color\n */\n public regenerateShades(color: Color): void {\n // Get all fixed shades sorted by perceived brightness\n const shades = color.shades\n .map((shade) => shade.copy())\n .filter((shade) => shade.fixed)\n .sort((a, b) => b.perceivedBrightness - a.perceivedBrightness);\n\n // Give each shade an index\n const indices = [...Array(10).keys()].map((i) => i * 100);\n indices[0] = 50;\n this._distributeIndices(shades, indices);\n\n // Calculate minimal saturation\n const minimum = this._calculateMinSaturation(shades);\n\n // Create \"white\"\n const white = this._createBorderShade(shades[0], minimum, true);\n shades.unshift(white);\n\n // Create \"black\"\n const black = this._createBorderShade(shades[shades.length - 1], minimum, false);\n shades.push(black);\n\n // Create missing shades\n for (const index of indices) {\n // Shade already exists\n if (shades.some((shade) => shade.index === index)) continue;\n\n // Find darker, lighter and third point\n const lighter =\n shades\n .slice()\n .reverse()\n .find((shade) => shade.index < index && shade.fixed) || white;\n const darker = shades.find((shade) => shade.index > index && shade.fixed) || black;\n let third = shades\n .slice()\n .reverse()\n .find((shade) => shade.index < lighter.index && shade.fixed);\n if (!third) third = shades.find((shade) => shade.index > darker.index && shade.fixed) || black;\n\n // Interpolate properties\n const hue =\n this._mapNumbers(\n index,\n lighter.index,\n darker.index,\n darker.hsl.H - lighter.hsl.H > 180 ? lighter.hsl.H + 360 : lighter.hsl.H,\n lighter.hsl.H - darker.hsl.H > 180 ? darker.hsl.H + 360 : darker.hsl.H\n ) % 360;\n const luminosity = this._mapNumbers(index, lighter.index, darker.index, lighter.hsl.L, darker.hsl.L);\n const saturation = this._calculateSaturation(luminosity, lighter, darker, third);\n\n // Add shade\n const shade = new Shade(index, new Value({ H: hue, S: saturation, L: luminosity }), false);\n shades.push(shade);\n }\n\n // Filter \"black\" and \"white\" and sort again by index\n color.shades = shades\n .filter((shade) => !(shade.index === 0 || shade.index === 1200))\n .sort((a, b) => a.index - b.index);\n }\n\n /**\n * Distribute fixed indices from `[50, 100, 200, ..., 900]` to all shades by perceived brightness.\n * Every index will only appear once.\n * This is done by shifting a duplicate index to the next one.\n *\n * **Help wanted:** This algorithm could be improved by calculating penalty points for shifting, generating multiple variants and choosing the one with the fewest penalty points.\n * @param shades\n * @param indices\n */\n private _distributeIndices(shades: Array, indices: Array): void {\n shades.forEach((shade) => {\n const mapBrightness = Math.max(Math.min(1250 - 12.5 * shade.perceivedBrightness, 999), 1);\n const index = indices.reduce((prev, curr) =>\n Math.abs(mapBrightness - curr) < Math.abs(mapBrightness - prev) ? curr : prev\n );\n shade.index = index;\n });\n\n this._offsetIndicesFromLightest(shades);\n }\n\n /**\n * Offset indices from lightest to darkest so no index appears multiple times.\n * @param shades\n */\n private _offsetIndicesFromLightest(shades: Array): void {\n let minIndex = 0;\n for (let i = 0; i < shades.length; i++) {\n const shade = shades[i];\n\n if (minIndex >= shade.index) {\n if (minIndex < 900) {\n shade.index = minIndex + 100 - (minIndex % 100);\n } else {\n shade.index = 900;\n this._offsetIndicesFromDarkest(shades, i);\n }\n }\n\n minIndex = shade.index;\n }\n }\n\n /**\n * Offset indices from darkest to lightest so the index of `shades[start]` is the biggest and no index appears multiple times.\n * @param shades\n * @param start\n */\n private _offsetIndicesFromDarkest(shades: Array, start: number): void {\n let maxIndex = shades[start].index;\n for (let j = start - 1; j >= 0; j--) {\n const brighter = shades[j];\n\n if (maxIndex > brighter.index) return;\n\n brighter.index = maxIndex -= 100;\n }\n }\n\n /**\n * Calculate the lowest saturation using the current lowest offset with an adjusted one.\n *\n * Returns an object including the calculated saturation alongside the brightness of the shade with the lowest saturation.\n * @param shades\n */\n private _calculateMinSaturation(shades: Array): {\n saturation: number;\n brightness: number;\n } {\n const shadeWithLowestSaturation = shades.reduce((prev, curr) => (curr.hsl.S < prev.hsl.S ? curr : prev));\n const differenceFromMiddleSquared = Math.pow(50 - shadeWithLowestSaturation.perceivedBrightness, 2);\n const evenLowerSaturation = Math.max(shadeWithLowestSaturation.hsl.S - 0.01 * differenceFromMiddleSquared, 0);\n return {\n saturation: Math.round((shadeWithLowestSaturation.hsl.S + evenLowerSaturation) / 2),\n brightness: shadeWithLowestSaturation.perceivedBrightness\n };\n }\n\n /**\n * Creates the most extreme shades (\"black\" and \"white\").\n * This is done with respect to the hue of the current lightest and darkest shades alongside the minimal saturation and brightness of the current color.\n * @param shade\n * @param minimum\n * @param white\n * @private\n */\n private _createBorderShade(shade: Shade, minimum: { saturation: number; brightness: number }, white: boolean): Shade {\n const index = white ? 0 : 1000;\n const hue = this._calculateHue(shade, white);\n const saturation = this._calculateMaxSaturation(minimum, shade, index);\n const luminosity = (1000 - index) / 10;\n\n return new Shade(white ? 0 : 1200, new Value({ H: hue, S: saturation, L: luminosity }), true);\n }\n\n /**\n * Calculate the hue of the lightest and darkest shades with the hue of the nearest neighbor in mind.\n * If we want to create a lighter shade, we rotate in direction 60, 180 and 300 degree respectively.\n * If we want to create a darker shade, we rotate in direction 0, 120 and 240 degree respectively.\n * @param shade\n * @param white\n */\n private _calculateHue(shade: Shade, white: boolean): number {\n const hueAdjustmentDirection =\n shade.hsl.H < 60 || (120 < shade.hsl.H && shade.hsl.H < 180) || (240 < shade.hsl.H && shade.hsl.H < 300) ? 1 : -1;\n\n let hue =\n shade.hsl.H + (hueAdjustmentDirection * (white ? 1 : -1) * (white ? shade.index : 1000 - shade.index)) / 60;\n\n if (white) {\n if ((shade.hsl.H <= 60 && hue > 60) || (shade.hsl.H >= 60 && hue < 60)) {\n hue = 60;\n } else if ((shade.hsl.H <= 180 && hue > 180) || (shade.hsl.H >= 180 && hue < 180)) {\n hue = 180;\n } else if ((shade.hsl.H <= 300 && hue > 300) || (shade.hsl.H >= 300 && hue < 300)) {\n hue = 300;\n }\n } else {\n if (\n (((shade.hsl.H > 340 && shade.hsl.H < 360) || shade.hsl.H === 0) && hue > 0 && hue < 20) ||\n (shade.hsl.H >= 0 && shade.hsl.H < 20 && hue < 0)\n ) {\n hue = 0;\n } else if ((shade.hsl.H <= 120 && hue > 120) || (shade.hsl.H >= 120 && hue < 120)) {\n hue = 120;\n } else if ((shade.hsl.H <= 240 && hue > 240) || (shade.hsl.H >= 240 && hue < 240)) {\n hue = 240;\n }\n }\n\n return Math.round(360 + hue) % 360;\n }\n\n /**\n * Calculate the highest saturation based on a neighbor with minimal saturation and the nearest neighbor.\n * @param minimum\n * @param neighbor\n * @param index\n */\n private _calculateMaxSaturation(\n minimum: { saturation: number; brightness: number },\n neighbor: Shade,\n index: number\n ): number {\n if (neighbor.perceivedBrightness === minimum.brightness)\n return Math.round(-0.01 * Math.pow(minimum.saturation, 2) + 2 * minimum.saturation);\n\n const a = (neighbor.hsl.S - minimum.saturation) / Math.pow(neighbor.perceivedBrightness - minimum.brightness, 2);\n const maxSaturation = a * Math.pow(index / 10 - minimum.brightness, 2) + minimum.saturation;\n\n const minAdd = -0.01 * Math.pow(neighbor.hsl.S, 2) + 2 * neighbor.hsl.S;\n\n return Math.min(Math.round(Math.max(maxSaturation, minAdd, 0)), 100);\n }\n\n /**\n * Map numbers on a linear 1 dimensional scale\n * @param x\n * @param in_min\n * @param in_max\n * @param out_min\n * @param out_max\n */\n private _mapNumbers(x: number, in_min: number, in_max: number, out_min: number, out_max: number): number {\n return Math.round(((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min);\n }\n\n /**\n * Calculate the saturation by interpolating a value for the current luminosity on a quadratic 2 dimensional parable defined by three points (shades).\n * @param luminosity\n * @param left\n * @param middle\n * @param right\n */\n private _calculateSaturation(luminosity: number, left: Shade, middle: Shade, right: Shade): number {\n const ll2 = left.hsl.L * left.hsl.L;\n const ml2 = middle.hsl.L * middle.hsl.L;\n const rl2 = right.hsl.L * right.hsl.L;\n\n const delta =\n ll2 * middle.hsl.L +\n left.hsl.L * rl2 +\n ml2 * right.hsl.L -\n middle.hsl.L * rl2 -\n ll2 * right.hsl.L -\n left.hsl.L * ml2 || 0.01;\n const aNumerator =\n left.hsl.S * middle.hsl.L +\n left.hsl.L * right.hsl.S +\n middle.hsl.S * right.hsl.L -\n middle.hsl.L * right.hsl.S -\n left.hsl.S * right.hsl.L -\n left.hsl.L * middle.hsl.S;\n const bNumerator =\n ll2 * middle.hsl.S +\n left.hsl.S * rl2 +\n ml2 * right.hsl.S -\n middle.hsl.S * rl2 -\n ll2 * right.hsl.S -\n left.hsl.S * ml2;\n const cNumerator =\n ll2 * middle.hsl.L * right.hsl.S +\n left.hsl.L * middle.hsl.S * rl2 +\n left.hsl.S * ml2 * right.hsl.L -\n left.hsl.S * middle.hsl.L * rl2 -\n ll2 * middle.hsl.S * right.hsl.L -\n left.hsl.L * ml2 * right.hsl.S;\n\n const x = aNumerator / delta;\n const y = bNumerator / delta;\n const z = cNumerator / delta;\n\n const saturation = x * luminosity * luminosity + y * luminosity + z;\n return Math.max(Math.min(Math.round(saturation), 100), 0);\n }\n}\n\nexport class ColorServiceMock {\n public async randomColor(): Promise {\n return new Color([Shade.random()], 'Random');\n }\n public regenerateShades(_color: Color): void {}\n}\n", - "extends": [], - "type": "injectable" - }, - { - "name": "DialogService", - "id": "injectable-DialogService-bcd611c160b0f6984d3fc1d98bb0b229435b5549357e48f4b7862e5028b1d8de65f800ca0a541d75813bb8ff810058f545d897327e9c5ddfac833e0b0ada9de7", - "file": "src/app/shared/data-access/dialog.service.ts", - "properties": [], - "methods": [ - { - "name": "alert", - "args": [ - { - "name": "message", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 15, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "message", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "confirm", - "args": [ - { - "name": "message", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 11, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "message", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "prompt", - "args": [ - { - "name": "message", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "defaultValue", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 7, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "message", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "defaultValue", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class DialogService {\n public async prompt(message: string, defaultValue: string): Promise {\n return window.prompt(message, defaultValue) ?? undefined;\n }\n\n public async confirm(message: string): Promise {\n return window.confirm(message);\n }\n\n public async alert(message: string): Promise {\n window.alert(message);\n }\n}\n\nexport class DialogServiceMock {\n public async prompt(_: string, value: string): Promise {\n return `${value}_test`;\n }\n\n public async confirm(_: string): Promise {\n return true;\n }\n\n public async alert(_: string): Promise {\n return;\n }\n}\n", - "extends": [], - "type": "injectable" - }, - { - "name": "ExportModalService", - "id": "injectable-ExportModalService-55a56074e29910bf6875c7dbb0993580444746a0d6df7298cfd9dd45e6af93d5a667cd9091a1d83bef2f3ae857254930c2cf8f785fd6721f2267ead55c26f48a", - "file": "src/app/export/data-access/export-modal.service.ts", - "properties": [], - "methods": [ - { - "name": "openExportModal", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 14, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Dialog } from '@angular/cdk/dialog';\nimport { Injectable, inject, signal } from '@angular/core';\nimport { firstValueFrom, tap } from 'rxjs';\nimport { Palette } from '../../shared/model';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ExportModalService {\n private readonly _dialog = inject(Dialog);\n\n private readonly _isModalOpen = signal(false);\n\n public async openExportModal(palette: Palette): Promise {\n if (this._isModalOpen()) {\n return;\n }\n this._isModalOpen.set(true);\n\n const exportModal = await import('../export-modal.component').then((c) => c.ExportModalComponent);\n const dialogRef = this._dialog.open(exportModal, {\n backdropClass: 'rp-modal-backdrop',\n data: {\n palette\n },\n panelClass: 'rp-modal-panel'\n });\n\n return await firstValueFrom(dialogRef.closed.pipe(tap(() => this._isModalOpen.set(false))));\n }\n}\n", - "extends": [], - "type": "injectable" - }, - { - "name": "ExportService", - "id": "injectable-ExportService-65597d546f11d47e4e92b5779ab3bcd75c9841c826f18120d1021fa66a1a38f2ead4d80d714ca74f8a9dd8e5dc84e5f11f4f90700af6f938d4d6ed3e7156e86c", - "file": "src/app/shared/data-access/export.service.ts", - "properties": [], - "methods": [ - { - "name": "copy", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "formatter", - "type": "Formatter", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 50, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "formatter", - "type": "Formatter", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "download", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "formatter", - "type": "Formatter", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 66, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "formatter", - "type": "Formatter", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "exportPalette", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "format", - "type": "ExportFormat", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "option", - "type": "ExportOption", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 20, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "format", - "type": "ExportFormat", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "option", - "type": "ExportOption", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, inject } from '@angular/core';\nimport { ExportFormat } from '../constants/export-format';\nimport { CssFormatter } from '../formatter/css.formatter';\nimport { LessFormatter } from '../formatter/less.formatter';\nimport { ScssFormatter } from '../formatter/scss.formatter';\nimport { TailwindFormatter } from '../formatter/tailwind.formatter';\nimport { Formatter } from '../interfaces/formatter.interface';\nimport { Palette } from '../model/palette.model';\nimport { ExportOption } from '../types/export-option';\nimport { AnalyticsService } from './analytics.service';\nimport { ToastService } from './toast.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ExportService {\n private readonly _toastService = inject(ToastService);\n private readonly _analyticsService = inject(AnalyticsService);\n\n public async exportPalette(palette: Palette, format: ExportFormat, option: ExportOption): Promise {\n const formatter = this.getFormatterForFormat(format);\n if (!formatter) {\n this._toastService.showToast({\n type: 'error',\n message: 'export.error.format-not-found'\n });\n return false;\n }\n\n const success = await this._exportPalette(palette, formatter, option);\n\n // Track the export event if successful\n if (success) {\n this._analyticsService.trackPaletteExport(format, option);\n }\n\n return success;\n }\n\n private async _exportPalette(palette: Palette, formatter: Formatter, option: ExportOption): Promise {\n if (option === 'copy') {\n return await this.copy(palette, formatter);\n } else if (option === 'file') {\n return await this.download(palette, formatter);\n }\n\n return false;\n }\n\n public async copy(palette: Palette, formatter: Formatter): Promise {\n const content = formatter.formatPalette(palette);\n\n try {\n await navigator.clipboard.writeText(content);\n return true;\n } catch (error) {\n console.error(error);\n this._toastService.showToast({\n type: 'error',\n message: 'export.error.copy-failed'\n });\n return false;\n }\n }\n\n public async download(palette: Palette, formatter: Formatter): Promise {\n const content = formatter.formatFile(palette);\n const blob = new Blob([content], { type: formatter.mimeType });\n\n const a = document.createElement('a');\n a.href = URL.createObjectURL(blob);\n a.download = formatter.filename;\n a.click();\n\n return true;\n }\n\n private getFormatterForFormat(format: ExportFormat): Formatter | undefined {\n switch (format) {\n case ExportFormat.TAILWIND:\n return new TailwindFormatter();\n case ExportFormat.SCSS:\n return new ScssFormatter();\n case ExportFormat.CSS:\n return new CssFormatter();\n case ExportFormat.LESS:\n return new LessFormatter();\n default:\n return undefined;\n }\n }\n}\n\nexport class ExportServiceMock {\n public async exportPalette(_palette: Palette, _format: ExportFormat, _option: ExportOption): Promise {\n return true;\n }\n\n public async copy(_palette: Palette, _formatter: Formatter): Promise {\n return true;\n }\n\n public async download(_palette: Palette, _formatter: Formatter): Promise {\n return true;\n }\n}\n", - "extends": [], - "type": "injectable" - }, - { - "name": "HomeService", - "id": "injectable-HomeService-64596cf9da442e2a38fac475685f1feef0096cabc8f1f9117a91769618c1a2b12307fe602840675493e122349cc238b8abe7a1ed1b5e39dbaac6ded9d45f41b7", - "file": "src/app/home/data-access/home.service.ts", - "properties": [ - { - "name": "hex", - "defaultValue": "signal('#3B82F6')", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 9, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "scheme", - "defaultValue": "signal(PaletteScheme.RAINBOW)", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 10, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [ - { - "name": "loadGenerationSettings", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 23, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "saveGenerationSettings", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 16, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, signal } from '@angular/core';\nimport { PaletteScheme } from '../../shared/constants/palette-scheme';\nimport { LocalStorageKey } from '../../shared/enums/local-storage-keys';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class HomeService {\n public readonly hex = signal('#3B82F6');\n public readonly scheme = signal(PaletteScheme.RAINBOW);\n\n public constructor() {\n this.loadGenerationSettings();\n }\n\n public saveGenerationSettings(): void {\n localStorage.setItem(\n LocalStorageKey.LAST_GENERATION_SETTINGS,\n JSON.stringify({ hex: this.hex(), scheme: this.scheme() })\n );\n }\n\n public loadGenerationSettings(): void {\n const lastGeneration = localStorage.getItem(LocalStorageKey.LAST_GENERATION_SETTINGS);\n\n if (!lastGeneration) {\n return;\n }\n\n try {\n const { hex, scheme } = JSON.parse(lastGeneration);\n\n if (hex) {\n this.hex.set(hex);\n }\n\n if (scheme) {\n this.scheme.set(scheme);\n }\n } catch {\n localStorage.removeItem(LocalStorageKey.LAST_GENERATION_SETTINGS);\n }\n }\n}\n\nexport class HomeServiceMock {\n public readonly hex = signal('#3B82F6');\n public readonly scheme = signal(PaletteScheme.RAINBOW);\n\n public saveGenerationSettings(): void {}\n public loadGenerationSettings(): void {}\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 10, - "modifierKind": [ - 125 - ] - }, - "extends": [], - "type": "injectable" - }, - { - "name": "LanguageService", - "id": "injectable-LanguageService-1248279049809b33d49b94b2ab4de79d9b364541fad43e677bdefa3529c46939ebb4ffdecd635a527f30b883a3ef8f712844054268fd3c94b3ce043e3f15e159", - "file": "src/app/shared/data-access/language.service.ts", - "properties": [], - "methods": [ - { - "name": "setLanguage", - "args": [ - { - "name": "language", - "type": "Language", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 44, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "language", - "type": "Language", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { TranslateService } from '@ngx-translate/core';\nimport { firstValueFrom } from 'rxjs';\nimport { LANGUAGE_OPTIONS } from '../../layout/constants/languages';\nimport { Language } from '../../layout/types/language';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class LanguageService {\n private readonly _translateService = inject(TranslateService);\n private readonly _titleService = inject(Title);\n\n private readonly _language = signal('en');\n\n public get language(): Signal {\n return this._language.asReadonly();\n }\n\n public constructor() {\n effect(async () => {\n await firstValueFrom(this._translateService.use(this._language()));\n localStorage.setItem(LocalStorageKey.LANGUAGE, this._language());\n document.documentElement.setAttribute('lang', this._language());\n this._titleService.setTitle(this._translateService.instant('title'));\n });\n\n const storedLanguage = localStorage.getItem(LocalStorageKey.LANGUAGE);\n if (storedLanguage) {\n this._language.set(storedLanguage as Language);\n return;\n }\n\n const browserLang = this._translateService.getBrowserLang();\n if (LANGUAGE_OPTIONS.find((option) => option.value === browserLang)) {\n this._language.set(browserLang as Language);\n } else {\n this._language.set('en');\n }\n }\n\n public setLanguage(language: Language): void {\n this._language.set(language);\n }\n}\n\nexport class LanguageServiceMock {\n public readonly language = signal('en').asReadonly();\n public setLanguage(_language: Language): void {}\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 20, - "modifierKind": [ - 125 - ] - }, - "accessors": { - "language": { - "name": "language", - "getSignature": { - "name": "language", - "type": "", - "returnType": "Signal", - "line": 18 - } - } - }, - "extends": [], - "type": "injectable" - }, - { - "name": "MobileService", - "id": "injectable-MobileService-51f3c2dda58e7caf43064b20bcf150d3ce031cabc1c44a4baa38f648348976ee92b676509402d3c11d66bde1ad5098b9ab24781fe89ee52760bc3ed2990b188f", - "file": "src/app/shared/data-access/mobile.service.ts", - "properties": [], - "methods": [], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, Signal, effect, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { fromEvent } from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class MobileService {\n private readonly _resize: Signal;\n private readonly _isMobile = signal(false);\n\n public get resize(): Signal {\n return this._resize;\n }\n\n public get isMobile(): Signal {\n return this._isMobile.asReadonly();\n }\n\n public constructor() {\n const resize$ = fromEvent(window, 'resize');\n this._resize = toSignal(resize$);\n\n effect(\n () => {\n // Reference resize signal to trigger computation on window resize\n if (!this._resize()) {\n return;\n }\n\n this._isMobile.set(window.innerWidth < 640);\n },\n {\n allowSignalWrites: true\n }\n );\n\n this._isMobile.set(window.innerWidth < 640);\n }\n}\n\nexport class MobileServiceMock {\n public readonly isMobile = signal(false).asReadonly();\n public readonly resize = signal(undefined).asReadonly();\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 18, - "modifierKind": [ - 125 - ] - }, - "accessors": { - "resize": { - "name": "resize", - "getSignature": { - "name": "resize", - "type": "", - "returnType": "Signal", - "line": 12 - } - }, - "isMobile": { - "name": "isMobile", - "getSignature": { - "name": "isMobile", - "type": "", - "returnType": "Signal", - "line": 16 - } - } - }, - "extends": [], - "type": "injectable" - }, - { - "name": "OfflineService", - "id": "injectable-OfflineService-eada8a658fc87c05ed94e9251da86de70bcf7f302ec35537d019445889e447f4c4e9db745ebb65f92f6f12fa98c75baf9af0cb9b9ecbfdfe01798cef1a404a49", - "file": "src/app/shared/data-access/offline.service.ts", - "properties": [ - { - "name": "isOffline", - "defaultValue": "toSignal(this._isOffline, {\n initialValue: this._isOffline.value\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 11, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BehaviorSubject, debounceTime, fromEvent, map, merge, of } from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class OfflineService {\n private readonly _isOffline = new BehaviorSubject(!navigator.onLine);\n\n public readonly isOffline = toSignal(this._isOffline, {\n initialValue: this._isOffline.value\n });\n\n public constructor() {\n merge(of(null), fromEvent(window, 'online'), fromEvent(window, 'offline'))\n .pipe(\n map(() => navigator.onLine),\n debounceTime(5000)\n )\n .subscribe((isOnline) => {\n this._isOffline.next(!isOnline);\n });\n }\n}\n\nexport class OfflineServiceMock {\n public readonly isOffline = signal(false).asReadonly();\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 13, - "modifierKind": [ - 125 - ] - }, - "extends": [], - "type": "injectable" - }, - { - "name": "PaletteService", - "id": "injectable-PaletteService-43199fad572d441e0952f696167cbe59794b813974a7e45c0163132e686e323ae510b8459a65dbe1550d217d44a4cd4c4acfb482986eb7674501f90cb12b41f6", - "file": "src/app/shared/data-access/palette.service.ts", - "properties": [], - "methods": [ - { - "name": "generatePalette", - "args": [ - { - "name": "hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "scheme", - "type": "PaletteScheme", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 70, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "scheme", - "type": "PaletteScheme", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "loadPaletteFromLocalStorage", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 34, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "savePaletteToLocalStorage", - "args": [ - { - "name": "upgrade", - "type": "", - "deprecated": false, - "deprecationMessage": "", - "defaultValue": "false" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 58, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "upgrade", - "type": "", - "deprecated": false, - "deprecationMessage": "", - "defaultValue": "false", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { PaletteScheme } from '../constants/palette-scheme';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\nimport { Value } from '../model';\nimport { Color } from '../model/color.model';\nimport { Palette } from '../model/palette.model';\nimport { Shade } from '../model/shade.model';\nimport { ColorNameService } from './color-name.service';\nimport { ColorService } from './color.service';\nimport { ToastService } from './toast.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class PaletteService {\n private readonly _colorService = inject(ColorService);\n private readonly _colorNameService = inject(ColorNameService);\n private readonly _toastService = inject(ToastService);\n\n private readonly _palette = signal(undefined);\n\n public get palette(): Signal {\n return this._palette.asReadonly();\n }\n\n public constructor() {\n this.loadPaletteFromLocalStorage();\n\n effect(() => {\n this._updateVariables();\n });\n }\n\n public loadPaletteFromLocalStorage(): void {\n // Check if there was a palette stored for an app update\n let palette = localStorage.getItem(LocalStorageKey.PALETTE_TMP);\n\n if (palette) {\n // Palette was stored for an update, remove it now\n localStorage.removeItem(LocalStorageKey.PALETTE_TMP);\n } else {\n // Load the palette saved by the user\n palette = localStorage.getItem(LocalStorageKey.PALETTE);\n }\n\n if (palette) {\n try {\n this._palette.set(Palette.parse(palette));\n } catch (e) {\n this._toastService.showToast({\n type: 'error',\n message: 'toast.error.palette-load'\n });\n }\n }\n }\n\n public savePaletteToLocalStorage(upgrade = false): void {\n const palette = this._palette();\n if (palette) {\n if (upgrade) {\n // Store the palette in a different key to reload it in the current state after an app update\n localStorage.setItem(LocalStorageKey.PALETTE_TMP, palette.toString());\n } else {\n localStorage.setItem(LocalStorageKey.PALETTE, palette.toString());\n }\n }\n }\n\n public async generatePalette(hex: string, scheme: PaletteScheme): Promise {\n const palette = await this._generatePalette(hex, scheme);\n\n for (const color of palette.colors) {\n // Get the color name\n color.name = await this._colorNameService.getColorName(color.shades[0]);\n\n // Regenerate the shades\n await this._colorService.regenerateShades(color);\n }\n\n this._palette.set(palette);\n }\n\n private async _generatePalette(hex: string, scheme: PaletteScheme): Promise {\n switch (scheme) {\n case PaletteScheme.RAINBOW:\n return this._generateRainbowPalette(hex);\n case PaletteScheme.MONOCHROME:\n return this._generateMonochromePalette(hex);\n case PaletteScheme.ANALOGOUS:\n return this._generateAnalogousPalette(hex);\n case PaletteScheme.COMPLEMENTARY:\n return this._generateComplementaryPalette(hex);\n case PaletteScheme.SPLIT_COMPLEMENTARY:\n return this._generateSplitPalette(hex);\n case PaletteScheme.TRIADIC:\n return this._generateTriadicPalette(hex);\n case PaletteScheme.COMPOUND:\n return this._generateCompoundPalette(hex);\n default:\n return this._generatePalette(hex, this._getRandomScheme());\n }\n }\n\n private _getRandomScheme(): PaletteScheme {\n const schemes = Object.values(PaletteScheme);\n return schemes[Math.floor(Math.random() * schemes.length)];\n }\n\n private _generateRainbowPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n let index = 0;\n const color = new Color([shade], `${index++}`);\n const rainbow = new Palette('Rainbow', [color]);\n\n /*\n * These are hues that are spaced out across the color wheel\n * to create a rainbow effect\n */\n const rainbowHues = [4, 26, 55, 95, 149, 200, 253];\n let currentHue = rainbowHues.reduce((best, current) =>\n Math.abs(current - shade.hsl.H) < Math.abs(best - shade.hsl.H) ? current : best\n );\n if (currentHue === 253 && shade.hsl.H > 308) {\n currentHue = 4;\n }\n\n for (const hue of rainbowHues) {\n if (hue === currentHue) {\n continue;\n }\n\n const newHue = (shade.hsl.H + (hue - currentHue) + 360) % 360;\n const newSaturation = Math.min(100, Math.max(0, shade.hsl.S - 20 + Math.floor(Math.random() * 40)));\n const newLightness = Math.min(100, Math.max(0, shade.hsl.L - 20 + Math.floor(Math.random() * 40)));\n const newShade = new Shade(-1, new Value({ H: newHue, S: newSaturation, L: newLightness }), true);\n\n rainbow.addColor(new Color([newShade], `${index++}`));\n }\n\n return rainbow;\n }\n\n private _generateMonochromePalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const monochrome = new Palette('Monochrome', []);\n\n monochrome.addColor(new Color([shade], 'Primary'));\n monochrome.addColor(new Color([new Shade(-1, new Value({ H: shade.hsl.H, S: 30, L: 50 }), true)], 'Muted'));\n monochrome.addColor(new Color([new Shade(-1, new Value({ H: shade.hsl.H, S: 2, L: 50 }), true)], 'Gray'));\n\n return monochrome;\n }\n\n private _generateAnalogousPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const analogous = new Palette('Analogous', []);\n\n analogous.addColor(new Color([shade], 'Primary'));\n\n analogous.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 315),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n analogous.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 270),\n S: 25,\n L: 20\n }),\n true\n )\n ],\n 'Secondary Muted'\n )\n );\n\n analogous.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 45),\n S: shade.hsl.S,\n L: 50\n }),\n true\n )\n ],\n 'Accent'\n )\n );\n analogous.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 90),\n S: 25,\n L: 20\n }),\n true\n )\n ],\n 'Accent Muted'\n )\n );\n\n return analogous;\n }\n\n private _generateComplementaryPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const complementary = new Palette('Complementary', []);\n\n complementary.addColor(new Color([shade], 'Primary'));\n\n complementary.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: shade.hsl.H,\n S: 3,\n L: 50\n }),\n true\n )\n ],\n 'Gray'\n )\n );\n\n complementary.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 180),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n\n return complementary;\n }\n\n private _generateSplitPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const split = new Palette('Split', []);\n\n split.addColor(new Color([shade], 'Primary'));\n\n split.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 20),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n split.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 20),\n S: 3,\n L: 50\n }),\n true\n )\n ],\n 'Gray'\n )\n );\n\n split.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 180),\n S: shade.hsl.S,\n L: 80\n }),\n true\n )\n ],\n 'Accent'\n )\n );\n\n return split;\n }\n\n private _generateTriadicPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const triadic = new Palette('Triadic', []);\n\n triadic.addColor(new Color([shade], 'Primary'));\n\n triadic.addColor(new Color([new Shade(-1, new Value({ H: shade.hsl.H, S: 20, L: 30 }), true)], 'Primary Muted'));\n\n triadic.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 120),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n triadic.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 120),\n S: 20,\n L: 30\n }),\n true\n )\n ],\n 'Secondary Muted'\n )\n );\n\n triadic.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 240),\n S: shade.hsl.S,\n L: 80\n }),\n true\n )\n ],\n 'Accent'\n )\n );\n\n return triadic;\n }\n\n private _generateCompoundPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const compound = new Palette('Compound', []);\n\n compound.addColor(new Color([shade], 'Primary'));\n\n compound.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 210),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n\n compound.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 150),\n S: shade.hsl.S,\n L: 50\n }),\n true\n )\n ],\n 'Accent'\n )\n );\n\n return compound;\n }\n\n private _updateVariables(): void {\n const root = document.documentElement;\n const activeProperties: Array = [];\n const palette = this._palette();\n\n if (palette) {\n for (const color of palette.colors) {\n const name = color.name.replace(/\\s+/g, '-').toLowerCase();\n\n for (const shade of color.shades) {\n const variable = `--color-${name}-${shade.index}`;\n const value = shade.hex;\n\n root.style.setProperty(variable, value);\n activeProperties.push(variable);\n }\n }\n }\n\n const allProperties = Array.from(root.style);\n for (const property of allProperties) {\n if (!property.startsWith('--color')) {\n continue;\n }\n\n if (!activeProperties.includes(property)) {\n root.style.removeProperty(property);\n }\n }\n }\n\n private _changeHueOnWheel(hue: number, change: number): number {\n let wheel;\n if (hue < 60) wheel = 2 * hue;\n else if (hue < 120) wheel = hue + 60;\n else if (hue < 240) wheel = 0.5 * hue + 120;\n else wheel = hue;\n\n wheel += change;\n wheel %= 360;\n\n let newHue;\n if (wheel < 120) newHue = 0.5 * wheel;\n else if (wheel < 180) newHue = wheel + 300;\n else if (wheel < 240) newHue = 2 * wheel + 120;\n else newHue = wheel;\n\n return newHue % 360;\n }\n}\n\nexport class PaletteServiceMock {\n public palette = signal(new Palette('Mock', [new Color([Shade.random()], 'MockColor')]));\n public loadPaletteFromLocalStorage(): void {}\n public savePaletteToLocalStorage(): void {}\n public generatePalette(_hex: string, _scheme: PaletteScheme): void {}\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 24, - "modifierKind": [ - 125 - ] - }, - "accessors": { - "palette": { - "name": "palette", - "getSignature": { - "name": "palette", - "type": "", - "returnType": "Signal", - "line": 22 - } - } - }, - "extends": [], - "type": "injectable" - }, - { - "name": "PwaService", - "id": "injectable-PwaService-d68a0ba55aff4e1feb45ae5a07a7394f6c2e4adaf29db2190f401a92c9bb453aabe12b87b46640177ee0eec80c4f235e8f0eb252df7a5a66bb743a0208dc1aee", - "file": "src/app/shared/data-access/pwa.service.ts", - "properties": [], - "methods": [], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { SwUpdate, VersionEvent } from '@angular/service-worker';\nimport { TranslateService } from '@ngx-translate/core';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\nimport { TrackingEventAction, TrackingEventCategory } from '../enums/tracking-event';\nimport { IS_RUNNING_TEST } from '../utils/is-running-test';\nimport { AnalyticsService } from './analytics.service';\nimport { DialogService } from './dialog.service';\nimport { PaletteService } from './palette.service';\nimport { ToastService } from './toast.service';\nimport { VersionService } from './version.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class PwaService {\n private readonly _SwUpdate = inject(SwUpdate);\n private readonly _isRunningTest = inject(IS_RUNNING_TEST);\n private readonly _translateService = inject(TranslateService);\n private readonly _analyticsService = inject(AnalyticsService);\n private readonly _toastService = inject(ToastService);\n private readonly _dialogService = inject(DialogService);\n private readonly _paletteService = inject(PaletteService);\n private readonly _versionService = inject(VersionService);\n\n private readonly _isPwa = signal(false);\n\n public get isPwa(): Signal {\n return this._isPwa.asReadonly();\n }\n\n public constructor() {\n effect(() => {\n this._analyticsService.setIsPwa(this._isPwa());\n });\n\n // Check if the app is currently running as a PWA\n if (\n window.matchMedia('(display-mode: standalone)').matches ||\n //@ts-expect-error - Navigator standalone is not a standard web api, but we try to use it anyway\n window.navigator.standalone\n ) {\n this._isPwa.set(true);\n }\n\n // Listen for the app being installed as a PWA\n window.addEventListener('appinstalled', () => {\n this._isPwa.set(true);\n this._analyticsService.trackEvent(TrackingEventCategory.PWA, TrackingEventAction.PWA_INSTALL);\n });\n\n // Listen for updates to the service worker\n this._SwUpdate.versionUpdates.subscribe(async (event) => {\n await this._handleUpdateEvents(event);\n });\n\n // Listen for broken service worker\n this._SwUpdate.unrecoverable.subscribe(() => {\n this._dialogService.alert(this._translateService.instant('pwa.broken'));\n });\n\n // Check if the app is currently updating\n if (localStorage.getItem(LocalStorageKey.UPGRADING)) {\n localStorage.removeItem(LocalStorageKey.UPGRADING);\n this._toastService.showToast({\n type: 'info',\n message: 'pwa.update-success',\n parameters: { version: this._versionService.appVersion }\n });\n }\n }\n\n private async _handleUpdateEvents(event: VersionEvent): Promise {\n // Latest version is already installed\n if (event.type === 'NO_NEW_VERSION_DETECTED') {\n return;\n }\n\n // New version available\n if (event.type === 'VERSION_DETECTED') {\n this._toastService.showToast({\n type: 'info',\n message: 'pwa.update-available',\n parameters: {\n // @ts-expect-error - `appData` is filled by the prebuilt script to contain the current app version\n version: event.version.appData?.version\n }\n });\n return;\n }\n\n // Update installation failed\n if (event.type === 'VERSION_INSTALLATION_FAILED') {\n console.error('PWA update failed', event.error);\n this._analyticsService.trackEvent(TrackingEventCategory.PWA, TrackingEventAction.PWA_UPDATE_FAILED);\n this._toastService.showToast({\n type: 'error',\n message: 'pwa.update-failed',\n parameters: {\n // @ts-expect-error - `appData` is filled by the prebuilt script to contain the current app version\n version: event.version.appData?.version\n }\n });\n return;\n }\n\n // Update was downloaded and can be installed through restart\n const restart = await this._dialogService.confirm(\n this._translateService.instant('pwa.restart', {\n old:\n // @ts-expect-error - `appData` is filled by the prebuilt script to contain the current app version\n event.currentVersion.appData?.version ?? this._versionService.appVersion,\n // @ts-expect-error - `appData` is filled by the prebuilt script to contain the current app version\n new: event.latestVersion.appData?.version\n })\n );\n\n // Continue without updating\n if (!restart) {\n return;\n }\n\n // Save the current palette to local storage\n this._paletteService.savePaletteToLocalStorage(true);\n\n // Set the flag to indicate that the app is currently updating\n localStorage.setItem(LocalStorageKey.UPGRADING, 'true');\n\n // Track the update\n this._analyticsService.trackEvent(TrackingEventCategory.PWA, TrackingEventAction.PWA_UPDATE_COMPLETED);\n\n // Reload the app to apply the update\n if (!this._isRunningTest) {\n document.location.reload();\n }\n }\n}\n\nexport class PwaServiceMock {}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 30, - "modifierKind": [ - 125 - ] - }, - "accessors": { - "isPwa": { - "name": "isPwa", - "getSignature": { - "name": "isPwa", - "type": "", - "returnType": "Signal", - "line": 28 - } - } - }, - "extends": [], - "type": "injectable" - }, - { - "name": "ThemeService", - "id": "injectable-ThemeService-706f23df04ad56029f3427f2ceccb50cc2e781f3d80231272ea583e79efc0d87430091fa88fce59558af211b7d75f209c33faaf79fca0a24619d120326759505", - "file": "src/app/shared/data-access/theme.service.ts", - "properties": [], - "methods": [ - { - "name": "setTheme", - "args": [ - { - "name": "theme", - "type": "Theme", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 35, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "theme", - "type": "Theme", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, Signal, effect, signal } from '@angular/core';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\nimport { Theme } from '../types/theme';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ThemeService {\n private readonly _theme = signal('light');\n\n public get theme(): Signal {\n return this._theme.asReadonly();\n }\n\n public constructor() {\n effect(async () => {\n localStorage.setItem(LocalStorageKey.THEME, this._theme());\n document.documentElement.classList.toggle('dark', this._theme() === 'dark');\n });\n\n const storedTheme = localStorage.getItem(LocalStorageKey.THEME);\n if (storedTheme === 'dark') {\n this._theme.set('dark');\n return;\n } else if (storedTheme === 'light') {\n return;\n }\n\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n if (prefersDark) {\n this._theme.set('dark');\n }\n }\n\n public setTheme(theme: Theme): void {\n this._theme.set(theme);\n }\n}\n\nexport class ThemeServiceMock {\n public readonly theme = signal('light').asReadonly();\n public setTheme(_theme: Theme): void {}\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 13, - "modifierKind": [ - 125 - ] - }, - "accessors": { - "theme": { - "name": "theme", - "getSignature": { - "name": "theme", - "type": "", - "returnType": "Signal", - "line": 11 - } - } - }, - "extends": [], - "type": "injectable" - }, - { - "name": "ToastService", - "id": "injectable-ToastService-38eb14cbec2f75a0e59079d1a748d7839c64fc16432c6abdfb9493155d9bc15aa2e91b60eb188b852941e6ff4a94acee9443a9d68e5103a232b4726106e05c93", - "file": "src/app/shared/data-access/toast.service.ts", - "properties": [], - "methods": [ - { - "name": "hideToast", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 98, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nHides the toast.\n", - "description": "

Hides the toast.

\n", - "modifierKind": [ - 125 - ] - }, - { - "name": "showToast", - "args": [ - { - "name": "toast", - "type": "Toast", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 91, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nShows the toast.\n\n", - "description": "

Shows the toast.

\n", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": { - "pos": 2760, - "end": 2765, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "toast" - }, - "type": "Toast", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 2754, - "end": 2759, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

The toast to be displayed.

\n" - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { GlobalPositionStrategy, Overlay, OverlayRef, PositionStrategy } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { Toast, ToastTimeouts } from '../interfaces/toast.interface';\nimport { ToastComponent } from '../ui/toast/toast.component';\nimport { MobileService } from './mobile.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ToastService {\n private readonly _overlay = inject(Overlay);\n private readonly _mobileService = inject(MobileService);\n\n private readonly _toast = signal(undefined);\n\n public get toast(): Signal {\n return this._toast.asReadonly();\n }\n\n private _hideToast: ReturnType | undefined;\n private _positionStrategy: PositionStrategy | undefined;\n private _overlayRef: OverlayRef | undefined;\n\n public constructor() {\n // Create an overlay for the toast.\n this._overlayRef = this._overlay.create();\n\n effect(() => {\n // If there is no toast, clear the timeout and return.\n if (!this._toast()) {\n if (this._hideToast) {\n clearTimeout(this._hideToast);\n }\n this._hideToast = undefined;\n return;\n }\n\n // If there was a previous timeout, clear it.\n if (this._hideToast) {\n clearTimeout(this._hideToast);\n this._hideToast = undefined;\n }\n\n // Set a new timeout to hide the toast.\n this._hideToast = setTimeout(\n () => {\n this._toast.set(undefined);\n },\n ToastTimeouts[this._toast()!.type ?? 'default']\n );\n });\n\n // Attach the toast component to the overlay.\n effect(() => {\n if (this._toast()) {\n if (!this._overlayRef) {\n return;\n }\n\n this._overlayRef.detach();\n const toastPortal = new ComponentPortal(ToastComponent);\n const toastComponent = this._overlayRef.attach(toastPortal);\n toastComponent.setInput('toast', this._toast());\n const closeSubscription = toastComponent.instance.close.subscribe(() => {\n this.hideToast();\n closeSubscription.unsubscribe();\n });\n } else {\n this._overlayRef?.detach();\n }\n });\n\n // Update the position strategy based on the device type.\n effect(() => {\n if (this._mobileService.isMobile()) {\n this._positionStrategy = new GlobalPositionStrategy().top('0.5rem').centerHorizontally();\n } else {\n this._positionStrategy = new GlobalPositionStrategy().bottom('0.5rem').end('0.5rem');\n }\n\n this._overlayRef?.updatePositionStrategy(this._positionStrategy);\n });\n }\n\n /**\n * Shows the toast.\n *\n * @param toast The toast to be displayed.\n */\n public showToast(toast: Toast): void {\n this._toast.set(toast);\n }\n\n /**\n * Hides the toast.\n */\n public hideToast(): void {\n this._toast.set(undefined);\n }\n}\n\nexport class ToastServiceMock {\n private readonly _toast = signal({\n type: 'test',\n message: 'test'\n });\n\n public get toast(): Signal {\n return this._toast.asReadonly();\n }\n\n public showToast(toast: Toast): void {\n this._toast.set(toast);\n }\n\n public hideToast(): void {\n this._toast.set(undefined);\n }\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 23, - "modifierKind": [ - 125 - ] - }, - "accessors": { - "toast": { - "name": "toast", - "getSignature": { - "name": "toast", - "type": "", - "returnType": "Signal", - "line": 17 - } - } - }, - "extends": [], - "type": "injectable" - }, - { - "name": "VersionService", - "id": "injectable-VersionService-48e8e94506d692f007f368965242f170b3a778567364275688c7e00cf6ba5aaa2de5d7652f55cb355614985bcfe325b5a36a7009088217f39f88532f4cbd075c", - "file": "src/app/shared/data-access/version.service.ts", - "properties": [ - { - "name": "angularVersion", - "defaultValue": "VERSION.full", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 8, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "appVersion", - "defaultValue": "packageJson.version", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 9, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [], - "deprecated": false, - "deprecationMessage": "", - "description": "", - "rawdescription": "\n", - "sourceCode": "import { Injectable, VERSION } from '@angular/core';\nimport packageJson from '../../../../package.json';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class VersionService {\n public readonly angularVersion = VERSION.full;\n public readonly appVersion = packageJson.version;\n\n public constructor() {\n console.info(\n '%cRainbowPalette',\n 'font-weight: bold; font-size: 1.1em; text-decoration: underline; margin-top: 1em;'\n );\n\n const angularVersionLength = this.angularVersion.length;\n const appVersionLength = this.appVersion.length;\n\n const length = Math.max(angularVersionLength, appVersionLength);\n const paddingAngular = ' '.repeat(length - angularVersionLength);\n const paddingApp = ' '.repeat(length - appVersionLength);\n\n console.info(` Angular: ${paddingAngular}${this.angularVersion}\\n App: ${paddingApp}${this.appVersion}`);\n }\n}\n\nexport class VersionServiceMock {\n public readonly angularVersion = '0.0.0';\n public readonly appVersion = '0.0.0';\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 9, - "modifierKind": [ - 125 - ] - }, - "extends": [], - "type": "injectable" - } - ], - "guards": [], - "interceptors": [], - "classes": [ - { - "name": "AnalyticsServiceMock", - "id": "class-AnalyticsServiceMock-d61fee52d86051b7e0d8bc5ed9f8df94a29a624755b7cfdd547f335d1d8d0ab33b644d7ec9e825f748fabf435732d1f965f41dd7e22730f7636852c8f8f3be20", - "file": "src/app/shared/data-access/analytics.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { MatomoTracker } from 'ngx-matomo-client';\nimport { ExportFormat } from '../constants/export-format';\nimport { PaletteScheme } from '../constants/palette-scheme';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\nimport { TrackingEventAction, TrackingEventCategory, TrackingEventName } from '../enums/tracking-event';\nimport { ExportOption } from '../types/export-option';\nimport { LanguageService } from './language.service';\nimport { OfflineService } from './offline.service';\nimport { ThemeService } from './theme.service';\nimport { ToastService } from './toast.service';\nimport { VersionService } from './version.service';\n\nexport enum CustomDimension {\n LANGUAGE = 1,\n THEME = 2,\n PWA = 3,\n VERSION = 4\n}\n\nexport enum AnalyticsStatus {\n UNSET = 'UNSET',\n ACCEPTED = 'ACCEPTED',\n DECLINED = 'DECLINED'\n}\n\ntype TrackingEvent = {\n category: TrackingEventCategory;\n action: TrackingEventAction;\n name?: TrackingEventName;\n value?: number;\n timestamp: number;\n};\n\n@Injectable({\n providedIn: 'root'\n})\nexport class AnalyticsService {\n private readonly _tracker = inject(MatomoTracker);\n private readonly _themeService = inject(ThemeService);\n private readonly _languageService = inject(LanguageService);\n private readonly _versionService = inject(VersionService);\n private readonly _offlineService = inject(OfflineService);\n private readonly _toastService = inject(ToastService);\n\n private readonly _status = signal(AnalyticsStatus.UNSET);\n\n public get status(): Signal {\n return this._status.asReadonly();\n }\n\n public constructor() {\n this._setup();\n\n effect(() => {\n const status = this._status();\n if (status === AnalyticsStatus.UNSET) {\n return;\n }\n\n const analytics = JSON.stringify({\n status,\n expiry: Date.now() + 1000 * 60 * 60 * 24 * 90\n });\n localStorage.setItem(LocalStorageKey.ANALYTICS, analytics);\n\n if (status === AnalyticsStatus.ACCEPTED) {\n this._tracker.setConsentGiven();\n this._processEvents('disabled');\n } else if (status === AnalyticsStatus.DECLINED) {\n this._tracker.forgetConsentGiven();\n }\n });\n\n // Send a heartbeat every 30 seconds\n this._tracker.enableHeartBeatTimer(30);\n\n // Track current theme\n effect(() => {\n this._tracker.setCustomDimension(CustomDimension.THEME, this._themeService.theme());\n });\n\n // Track current language\n effect(() => {\n this._tracker.setCustomDimension(CustomDimension.LANGUAGE, this._languageService.language());\n });\n\n // Track app version\n this._tracker.setCustomDimension(CustomDimension.VERSION, this._versionService.appVersion);\n\n effect(async () => {\n if (!this._offlineService.isOffline()) {\n this._processEvents('offline');\n }\n });\n }\n\n private _setup(): void {\n const analytics = localStorage.getItem(LocalStorageKey.ANALYTICS);\n if (analytics) {\n try {\n const parsed = JSON.parse(analytics);\n if (parsed.expiry < Date.now()) {\n localStorage.removeItem(LocalStorageKey.ANALYTICS);\n } else {\n if (parsed.status === AnalyticsStatus.ACCEPTED) {\n this._status.set(AnalyticsStatus.ACCEPTED);\n } else if (parsed.status === AnalyticsStatus.DECLINED) {\n this._status.set(AnalyticsStatus.DECLINED);\n }\n }\n } catch (error) {\n console.error('Failed to parse analytics status', error);\n }\n }\n\n let userId = localStorage.getItem(LocalStorageKey.USER_ID);\n if (!userId) {\n // Generate random user ID with 16 hex characters\n userId = Array.from({ length: 16 }, () => Math.floor(Math.random() * 16).toString(16)).join('');\n localStorage.setItem(LocalStorageKey.USER_ID, userId);\n }\n this._tracker.setUserId(userId);\n }\n\n private _queueEvent(event: Omit, type: 'disabled' | 'offline'): void {\n const key = type === 'disabled' ? LocalStorageKey.EVENTS_DISABLED : LocalStorageKey.EVENTS_OFFLINE;\n const events = JSON.parse(localStorage.getItem(key) || '[]') as Array;\n\n events.push({ ...event, timestamp: Date.now() });\n localStorage.setItem(key, JSON.stringify(events));\n }\n\n private _processEvents(type: 'disabled' | 'offline'): void {\n const key = type === 'disabled' ? LocalStorageKey.EVENTS_DISABLED : LocalStorageKey.EVENTS_OFFLINE;\n const cache = localStorage.getItem(key);\n if (!cache) {\n return;\n }\n\n const events = JSON.parse(cache) as Array;\n for (const event of events) {\n // Skip events older than 24 hours\n if (event.timestamp < Date.now() - 1000 * 60 * 60 * 24) {\n continue;\n }\n\n this._tracker.trackEvent(event.category, event.action, event.name);\n }\n\n if (key === LocalStorageKey.EVENTS_DISABLED) {\n this.trackEvent(\n TrackingEventCategory.EVENTS_DISABLED,\n TrackingEventAction.EVENTS_DISABLED,\n undefined,\n events.length\n );\n } else {\n this.trackEvent(\n TrackingEventCategory.EVENTS_OFFLINE,\n TrackingEventAction.EVENTS_OFFLINE,\n undefined,\n events.length\n );\n }\n\n localStorage.removeItem(key);\n }\n\n public setIsPwa(isPwa: boolean): void {\n this._tracker.setCustomDimension(CustomDimension.PWA, isPwa ? 'PWA' : 'Web');\n }\n\n public acceptAnalytics(): void {\n this._status.set(AnalyticsStatus.ACCEPTED);\n this._toastService.showToast({\n type: 'info',\n message: 'toast.info.analytics-accepted'\n });\n }\n\n public declineAnalytics(): void {\n this._status.set(AnalyticsStatus.DECLINED);\n }\n\n /**\n * Track a custom event\n * @param category Event category\n * @param action Event action\n * @param name Event name\n */\n public trackEvent(\n category: TrackingEventCategory,\n action: TrackingEventAction,\n name?: TrackingEventName,\n value?: number\n ): void {\n if (this._status() !== AnalyticsStatus.ACCEPTED) {\n this._queueEvent(\n {\n category,\n action,\n name,\n value\n },\n 'disabled'\n );\n return;\n }\n\n if (this._offlineService.isOffline()) {\n this._queueEvent({ category, action, name, value }, 'offline');\n return;\n }\n\n this._tracker.trackEvent(category, action, name, value);\n }\n\n /**\n * Track the export of a palette\n * @param format Format to export\n * @param option Export method\n */\n public trackPaletteExport(format: ExportFormat, option: ExportOption): void {\n const action =\n option === 'copy' ? TrackingEventAction.EXPORT_PALETTE_COPY : TrackingEventAction.EXPORT_PALETTE_FILE;\n\n let name: TrackingEventName | undefined;\n switch (format) {\n case ExportFormat.CSS:\n name = TrackingEventName.EXPORT_PALETTE_CSS;\n break;\n case ExportFormat.SCSS:\n name = TrackingEventName.EXPORT_PALETTE_SCSS;\n break;\n case ExportFormat.LESS:\n name = TrackingEventName.EXPORT_PALETTE_LESS;\n break;\n case ExportFormat.TAILWIND:\n name = TrackingEventName.EXPORT_PALETTE_TAILWIND;\n break;\n default:\n console.warn('Unknown palette export format', format);\n name = TrackingEventName.EXPORT_PALETTE_UNKNOWN;\n }\n\n this.trackEvent(TrackingEventCategory.EXPORT_PALETTE, action, name);\n }\n\n /**\n * Track the generation of a palette\n * @param scheme Palette scheme\n */\n public trackPaletteGeneration(scheme: PaletteScheme): void {\n let name: TrackingEventName | undefined;\n switch (scheme) {\n case PaletteScheme.RAINBOW:\n name = TrackingEventName.GENERATE_PALETTE_RAINBOW;\n break;\n case PaletteScheme.SURPRISE:\n name = TrackingEventName.GENERATE_PALETTE_SURPRISE;\n break;\n case PaletteScheme.MONOCHROME:\n name = TrackingEventName.GENERATE_PALETTE_MONOCHROME;\n break;\n case PaletteScheme.ANALOGOUS:\n name = TrackingEventName.GENERATE_PALETTE_ANALOGOUS;\n break;\n case PaletteScheme.COMPLEMENTARY:\n name = TrackingEventName.GENERATE_PALETTE_COMPLEMENTARY;\n break;\n case PaletteScheme.SPLIT_COMPLEMENTARY:\n name = TrackingEventName.GENERATE_PALETTE_SPLIT_COMPLEMENTARY;\n break;\n case PaletteScheme.TRIADIC:\n name = TrackingEventName.GENERATE_PALETTE_TRIADIC;\n break;\n case PaletteScheme.COMPOUND:\n name = TrackingEventName.GENERATE_PALETTE_COMPOUND;\n break;\n default:\n console.warn('Unknown palette generation scheme', scheme);\n name = TrackingEventName.GENERATE_PALETTE_UNKNOWN;\n }\n\n this.trackEvent(TrackingEventCategory.GENERATE_PALETTE, TrackingEventAction.GENERATE_PALETTE, name);\n }\n}\n\nexport class AnalyticsServiceMock {\n public readonly status = signal(AnalyticsStatus.UNSET).asReadonly();\n\n public setIsPwa(_isPwa: boolean): void {}\n\n public acceptAnalytics(): void {}\n\n public declineAnalytics(): void {}\n\n public trackEvent(_category: TrackingEventCategory, _action: TrackingEventAction, _name?: TrackingEventName): void {}\n\n public trackPaletteExport(_format: ExportFormat, _option: ExportOption): void {}\n\n public trackPaletteGeneration(_scheme: PaletteScheme): void {}\n}\n", - "properties": [ - { - "name": "status", - "defaultValue": "signal(AnalyticsStatus.UNSET).asReadonly()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 291, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [ - { - "name": "acceptAnalytics", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 295, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "declineAnalytics", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 297, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "setIsPwa", - "args": [ - { - "name": "_isPwa", - "type": "boolean", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 293, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_isPwa", - "type": "boolean", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "trackEvent", - "args": [ - { - "name": "_category", - "type": "TrackingEventCategory", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_action", - "type": "TrackingEventAction", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_name", - "type": "TrackingEventName", - "deprecated": false, - "deprecationMessage": "", - "optional": true - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 299, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_category", - "type": "TrackingEventCategory", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_action", - "type": "TrackingEventAction", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_name", - "type": "TrackingEventName", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "trackPaletteExport", - "args": [ - { - "name": "_format", - "type": "ExportFormat", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_option", - "type": "ExportOption", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 301, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_format", - "type": "ExportFormat", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_option", - "type": "ExportOption", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "trackPaletteGeneration", - "args": [ - { - "name": "_scheme", - "type": "PaletteScheme", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 303, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_scheme", - "type": "PaletteScheme", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "Color", - "id": "class-Color-11ae9cac5aede8dd91f0c364bec6b9de5d27fa87b60bc3f5d6d4a9da89ae912b69817f47a0f158d1c7e1b22ef013a8dfef8a64dd94e5b8f63717a6caf519ad25", - "file": "src/app/shared/model/color.model.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Shade } from './shade.model';\n\nexport class Color {\n public name: string;\n public shades: Array;\n\n public constructor(shades: Array, name: string) {\n this.shades = shades;\n this.name = name;\n }\n\n public get hue(): number {\n return this.shades\n .map((shade) => shade.hsl)\n .reduce((hue, hsl) => {\n return hue + hsl.H / this.shades.length;\n }, 0);\n }\n\n public copy(): Color {\n return new Color(\n this.shades.map((shade) => shade.copy()),\n this.name\n );\n }\n\n public static parse(color: string | object): Color {\n if (typeof color === 'string') {\n try {\n color = JSON.parse(color);\n } catch (e) {\n throw new Error(`Could not parse color (not a valid JSON): \"${color}\"`);\n }\n }\n\n if (!(color instanceof Object)) {\n throw new Error(`Could not parse color (not an object): \"${color}\"`);\n }\n\n if (!('shades' in color)) {\n throw new Error(`Could not parse color (missing \"shades\" property): \"${color}\"`);\n }\n\n if (!('name' in color) || typeof color.name !== 'string') {\n throw new Error(`Could not parse color (missing \"name\" property): \"${color}\"`);\n }\n\n const name = color.name;\n\n const shades: Array = [];\n if (!Array.isArray(color.shades)) {\n throw new Error(`Could not parse color (invalid \"shades\" property): \"${color.shades}\"`);\n }\n\n for (const shade of color.shades) {\n try {\n shades.push(Shade.parse(shade));\n } catch (e) {\n throw new Error(`Could not parse color (invalid shade): \"${shade}\"`);\n }\n }\n\n return new Color(shades, name);\n }\n\n public toJSON(): object {\n return {\n name: this.name,\n shades: this.shades.map((shade) => shade.toJSON())\n };\n }\n\n public toString(): string {\n return JSON.stringify(this.toJSON());\n }\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "shades", - "type": "Array", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "line": 5, - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "shades", - "type": "Array", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "properties": [ - { - "name": "name", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 4, - "modifierKind": [ - 125 - ] - }, - { - "name": "shades", - "deprecated": false, - "deprecationMessage": "", - "type": "Array", - "optional": false, - "description": "", - "line": 5, - "modifierKind": [ - 125 - ] - } - ], - "methods": [ - { - "name": "copy", - "args": [], - "optional": false, - "returnType": "Color", - "typeParameters": [], - "line": 20, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "parse", - "args": [ - { - "name": "color", - "type": "string | object", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Color", - "typeParameters": [], - "line": 27, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 126 - ], - "jsdoctags": [ - { - "name": "color", - "type": "string | object", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "toJSON", - "args": [], - "optional": false, - "returnType": "object", - "typeParameters": [], - "line": 66, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "toString", - "args": [], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 73, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - } - ], - "indexSignatures": [], - "extends": [], - "accessors": { - "hue": { - "name": "hue", - "getSignature": { - "name": "hue", - "type": "number", - "returnType": "number", - "line": 12 - } - } - }, - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "ColorEditorServiceMock", - "id": "class-ColorEditorServiceMock-bebdbf0d59d8c1cea50ce79f00d2ec1e27f082823d34ae0701750b2dfc75b1d7758237ccc562c7a31af04f432d7352e868f49bb0143f65cf941263088d34038f", - "file": "src/app/editor/data-access/color-editor.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Dialog } from '@angular/cdk/dialog';\nimport { Injectable, inject, signal } from '@angular/core';\nimport { firstValueFrom, tap } from 'rxjs';\nimport { Color } from '../../shared/model';\nimport { sleep } from '../../shared/utils/sleep';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ColorEditorService {\n private readonly _dialog = inject(Dialog);\n private readonly _isModalOpen = signal(false);\n\n public async openColorEditor(color: Color, shadeIndex?: number): Promise {\n if (this._isModalOpen()) {\n return;\n }\n this._isModalOpen.set(true);\n\n const editor = await import('../editor.component').then((c) => c.EditorComponent);\n const dialogRef = this._dialog.open(editor, {\n backdropClass: 'rp-modal-backdrop',\n data: {\n color,\n shadeIndex\n },\n disableClose: true,\n panelClass: 'rp-modal-panel'\n });\n\n return await firstValueFrom(dialogRef.closed.pipe(tap(() => this._isModalOpen.set(false))));\n }\n}\n\nexport class ColorEditorServiceMock {\n public async openColorEditor(color: Color, _shadeIndex?: number): Promise {\n await sleep(10);\n\n return color.copy();\n }\n}\n", - "properties": [], - "methods": [ - { - "name": "openColorEditor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_shadeIndex", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 36, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_shadeIndex", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "ColorNameServiceMock", - "id": "class-ColorNameServiceMock-018fd140a60e9c39bc07bf5713bec5497d29f4f7d88fc7aca6a0e877ab89a51a82daceb95713891c65620f8c09a813d67c6adf80f81c968bf0a83366b57d7668", - "file": "src/app/shared/data-access/color-name.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { HttpClient } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { firstValueFrom } from 'rxjs';\nimport { Shade } from '../model/shade.model';\nimport { HSLObject } from '../types/color-format';\nimport { ToastService } from './toast.service';\n\ninterface ColorMapEntry {\n hue: number;\n saturation: number;\n lightness: number;\n name: string;\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ColorNameService {\n private readonly _http = inject(HttpClient);\n private readonly _toastService = inject(ToastService);\n\n private colorDictionary?: Array;\n\n public async getColorName(shade: Shade): Promise {\n if (!this.colorDictionary) {\n await this.loadColorMap();\n }\n\n if (!this.colorDictionary) {\n return shade.hex.substring(1);\n }\n\n const name = this.colorDictionary\n .filter((entry) => {\n if (shade.hsl.S === 0) {\n return entry.hue === -1;\n } else {\n return entry.hue !== -1;\n }\n })\n .map((entry) => ({\n name: entry.name,\n distance: this.calculateDistance(shade.hsl, entry)\n }))\n .sort((a, b) => a.distance - b.distance)\n .map((entry) => entry.name)\n .at(0);\n\n if (!name) {\n console.warn('No color name found for', shade.hex);\n return shade.hex.substring(1);\n }\n\n return name.replace(/(\\w)(\\w*)/g, (_, first, rest) => first.toUpperCase() + rest.toLowerCase());\n }\n\n private async loadColorMap(): Promise {\n try {\n const data = await firstValueFrom(this._http.get('/assets/color-dictionary.csv', { responseType: 'text' }));\n\n this.colorDictionary = data\n .split('\\n')\n .filter((_, index) => index > 0)\n .map((line) => line.split(';'))\n .map((entry) => ({\n name: entry[0],\n hue: parseInt(entry[1], 10),\n saturation: parseInt(entry[2], 10),\n lightness: parseInt(entry[3], 10)\n }));\n } catch (error) {\n this._toastService.showToast({\n type: 'error',\n message: 'toast.error.load-color-map'\n });\n }\n }\n\n private calculateDistance(hsl: HSLObject, entry: ColorMapEntry): number {\n return (\n 10 * Math.abs(hsl.H - entry.hue) + 5 * Math.abs(hsl.S - entry.saturation) + Math.abs(hsl.L - entry.lightness)\n );\n }\n}\n\nexport class ColorNameServiceMock {\n public async getColorName(shade: Shade): Promise {\n return shade.hex.substring(1);\n }\n}\n", - "properties": [], - "methods": [ - { - "name": "getColorName", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 87, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "ColorServiceMock", - "id": "class-ColorServiceMock-2002a7ef73112e34b70f8a1fb569570d7a6f93cb2f967f831884914090099e87d408224d3ddc030761633d2b983b163f22768b1d7addd793e4ca30c0b1a01ebe", - "file": "src/app/shared/data-access/color.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, inject } from '@angular/core';\nimport { Value } from '../model';\nimport { Color } from '../model/color.model';\nimport { Shade } from '../model/shade.model';\nimport { ColorNameService } from './color-name.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ColorService {\n private readonly _colorNameService = inject(ColorNameService);\n\n public async randomColor(): Promise {\n const shade = Shade.random();\n const name = await this._colorNameService.getColorName(shade);\n const color = new Color([Shade.random()], name);\n\n this.regenerateShades(color);\n\n return color;\n }\n\n /**\n * Regenerate every shade but the fixed ones.\n * @param color\n */\n public regenerateShades(color: Color): void {\n // Get all fixed shades sorted by perceived brightness\n const shades = color.shades\n .map((shade) => shade.copy())\n .filter((shade) => shade.fixed)\n .sort((a, b) => b.perceivedBrightness - a.perceivedBrightness);\n\n // Give each shade an index\n const indices = [...Array(10).keys()].map((i) => i * 100);\n indices[0] = 50;\n this._distributeIndices(shades, indices);\n\n // Calculate minimal saturation\n const minimum = this._calculateMinSaturation(shades);\n\n // Create \"white\"\n const white = this._createBorderShade(shades[0], minimum, true);\n shades.unshift(white);\n\n // Create \"black\"\n const black = this._createBorderShade(shades[shades.length - 1], minimum, false);\n shades.push(black);\n\n // Create missing shades\n for (const index of indices) {\n // Shade already exists\n if (shades.some((shade) => shade.index === index)) continue;\n\n // Find darker, lighter and third point\n const lighter =\n shades\n .slice()\n .reverse()\n .find((shade) => shade.index < index && shade.fixed) || white;\n const darker = shades.find((shade) => shade.index > index && shade.fixed) || black;\n let third = shades\n .slice()\n .reverse()\n .find((shade) => shade.index < lighter.index && shade.fixed);\n if (!third) third = shades.find((shade) => shade.index > darker.index && shade.fixed) || black;\n\n // Interpolate properties\n const hue =\n this._mapNumbers(\n index,\n lighter.index,\n darker.index,\n darker.hsl.H - lighter.hsl.H > 180 ? lighter.hsl.H + 360 : lighter.hsl.H,\n lighter.hsl.H - darker.hsl.H > 180 ? darker.hsl.H + 360 : darker.hsl.H\n ) % 360;\n const luminosity = this._mapNumbers(index, lighter.index, darker.index, lighter.hsl.L, darker.hsl.L);\n const saturation = this._calculateSaturation(luminosity, lighter, darker, third);\n\n // Add shade\n const shade = new Shade(index, new Value({ H: hue, S: saturation, L: luminosity }), false);\n shades.push(shade);\n }\n\n // Filter \"black\" and \"white\" and sort again by index\n color.shades = shades\n .filter((shade) => !(shade.index === 0 || shade.index === 1200))\n .sort((a, b) => a.index - b.index);\n }\n\n /**\n * Distribute fixed indices from `[50, 100, 200, ..., 900]` to all shades by perceived brightness.\n * Every index will only appear once.\n * This is done by shifting a duplicate index to the next one.\n *\n * **Help wanted:** This algorithm could be improved by calculating penalty points for shifting, generating multiple variants and choosing the one with the fewest penalty points.\n * @param shades\n * @param indices\n */\n private _distributeIndices(shades: Array, indices: Array): void {\n shades.forEach((shade) => {\n const mapBrightness = Math.max(Math.min(1250 - 12.5 * shade.perceivedBrightness, 999), 1);\n const index = indices.reduce((prev, curr) =>\n Math.abs(mapBrightness - curr) < Math.abs(mapBrightness - prev) ? curr : prev\n );\n shade.index = index;\n });\n\n this._offsetIndicesFromLightest(shades);\n }\n\n /**\n * Offset indices from lightest to darkest so no index appears multiple times.\n * @param shades\n */\n private _offsetIndicesFromLightest(shades: Array): void {\n let minIndex = 0;\n for (let i = 0; i < shades.length; i++) {\n const shade = shades[i];\n\n if (minIndex >= shade.index) {\n if (minIndex < 900) {\n shade.index = minIndex + 100 - (minIndex % 100);\n } else {\n shade.index = 900;\n this._offsetIndicesFromDarkest(shades, i);\n }\n }\n\n minIndex = shade.index;\n }\n }\n\n /**\n * Offset indices from darkest to lightest so the index of `shades[start]` is the biggest and no index appears multiple times.\n * @param shades\n * @param start\n */\n private _offsetIndicesFromDarkest(shades: Array, start: number): void {\n let maxIndex = shades[start].index;\n for (let j = start - 1; j >= 0; j--) {\n const brighter = shades[j];\n\n if (maxIndex > brighter.index) return;\n\n brighter.index = maxIndex -= 100;\n }\n }\n\n /**\n * Calculate the lowest saturation using the current lowest offset with an adjusted one.\n *\n * Returns an object including the calculated saturation alongside the brightness of the shade with the lowest saturation.\n * @param shades\n */\n private _calculateMinSaturation(shades: Array): {\n saturation: number;\n brightness: number;\n } {\n const shadeWithLowestSaturation = shades.reduce((prev, curr) => (curr.hsl.S < prev.hsl.S ? curr : prev));\n const differenceFromMiddleSquared = Math.pow(50 - shadeWithLowestSaturation.perceivedBrightness, 2);\n const evenLowerSaturation = Math.max(shadeWithLowestSaturation.hsl.S - 0.01 * differenceFromMiddleSquared, 0);\n return {\n saturation: Math.round((shadeWithLowestSaturation.hsl.S + evenLowerSaturation) / 2),\n brightness: shadeWithLowestSaturation.perceivedBrightness\n };\n }\n\n /**\n * Creates the most extreme shades (\"black\" and \"white\").\n * This is done with respect to the hue of the current lightest and darkest shades alongside the minimal saturation and brightness of the current color.\n * @param shade\n * @param minimum\n * @param white\n * @private\n */\n private _createBorderShade(shade: Shade, minimum: { saturation: number; brightness: number }, white: boolean): Shade {\n const index = white ? 0 : 1000;\n const hue = this._calculateHue(shade, white);\n const saturation = this._calculateMaxSaturation(minimum, shade, index);\n const luminosity = (1000 - index) / 10;\n\n return new Shade(white ? 0 : 1200, new Value({ H: hue, S: saturation, L: luminosity }), true);\n }\n\n /**\n * Calculate the hue of the lightest and darkest shades with the hue of the nearest neighbor in mind.\n * If we want to create a lighter shade, we rotate in direction 60, 180 and 300 degree respectively.\n * If we want to create a darker shade, we rotate in direction 0, 120 and 240 degree respectively.\n * @param shade\n * @param white\n */\n private _calculateHue(shade: Shade, white: boolean): number {\n const hueAdjustmentDirection =\n shade.hsl.H < 60 || (120 < shade.hsl.H && shade.hsl.H < 180) || (240 < shade.hsl.H && shade.hsl.H < 300) ? 1 : -1;\n\n let hue =\n shade.hsl.H + (hueAdjustmentDirection * (white ? 1 : -1) * (white ? shade.index : 1000 - shade.index)) / 60;\n\n if (white) {\n if ((shade.hsl.H <= 60 && hue > 60) || (shade.hsl.H >= 60 && hue < 60)) {\n hue = 60;\n } else if ((shade.hsl.H <= 180 && hue > 180) || (shade.hsl.H >= 180 && hue < 180)) {\n hue = 180;\n } else if ((shade.hsl.H <= 300 && hue > 300) || (shade.hsl.H >= 300 && hue < 300)) {\n hue = 300;\n }\n } else {\n if (\n (((shade.hsl.H > 340 && shade.hsl.H < 360) || shade.hsl.H === 0) && hue > 0 && hue < 20) ||\n (shade.hsl.H >= 0 && shade.hsl.H < 20 && hue < 0)\n ) {\n hue = 0;\n } else if ((shade.hsl.H <= 120 && hue > 120) || (shade.hsl.H >= 120 && hue < 120)) {\n hue = 120;\n } else if ((shade.hsl.H <= 240 && hue > 240) || (shade.hsl.H >= 240 && hue < 240)) {\n hue = 240;\n }\n }\n\n return Math.round(360 + hue) % 360;\n }\n\n /**\n * Calculate the highest saturation based on a neighbor with minimal saturation and the nearest neighbor.\n * @param minimum\n * @param neighbor\n * @param index\n */\n private _calculateMaxSaturation(\n minimum: { saturation: number; brightness: number },\n neighbor: Shade,\n index: number\n ): number {\n if (neighbor.perceivedBrightness === minimum.brightness)\n return Math.round(-0.01 * Math.pow(minimum.saturation, 2) + 2 * minimum.saturation);\n\n const a = (neighbor.hsl.S - minimum.saturation) / Math.pow(neighbor.perceivedBrightness - minimum.brightness, 2);\n const maxSaturation = a * Math.pow(index / 10 - minimum.brightness, 2) + minimum.saturation;\n\n const minAdd = -0.01 * Math.pow(neighbor.hsl.S, 2) + 2 * neighbor.hsl.S;\n\n return Math.min(Math.round(Math.max(maxSaturation, minAdd, 0)), 100);\n }\n\n /**\n * Map numbers on a linear 1 dimensional scale\n * @param x\n * @param in_min\n * @param in_max\n * @param out_min\n * @param out_max\n */\n private _mapNumbers(x: number, in_min: number, in_max: number, out_min: number, out_max: number): number {\n return Math.round(((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min);\n }\n\n /**\n * Calculate the saturation by interpolating a value for the current luminosity on a quadratic 2 dimensional parable defined by three points (shades).\n * @param luminosity\n * @param left\n * @param middle\n * @param right\n */\n private _calculateSaturation(luminosity: number, left: Shade, middle: Shade, right: Shade): number {\n const ll2 = left.hsl.L * left.hsl.L;\n const ml2 = middle.hsl.L * middle.hsl.L;\n const rl2 = right.hsl.L * right.hsl.L;\n\n const delta =\n ll2 * middle.hsl.L +\n left.hsl.L * rl2 +\n ml2 * right.hsl.L -\n middle.hsl.L * rl2 -\n ll2 * right.hsl.L -\n left.hsl.L * ml2 || 0.01;\n const aNumerator =\n left.hsl.S * middle.hsl.L +\n left.hsl.L * right.hsl.S +\n middle.hsl.S * right.hsl.L -\n middle.hsl.L * right.hsl.S -\n left.hsl.S * right.hsl.L -\n left.hsl.L * middle.hsl.S;\n const bNumerator =\n ll2 * middle.hsl.S +\n left.hsl.S * rl2 +\n ml2 * right.hsl.S -\n middle.hsl.S * rl2 -\n ll2 * right.hsl.S -\n left.hsl.S * ml2;\n const cNumerator =\n ll2 * middle.hsl.L * right.hsl.S +\n left.hsl.L * middle.hsl.S * rl2 +\n left.hsl.S * ml2 * right.hsl.L -\n left.hsl.S * middle.hsl.L * rl2 -\n ll2 * middle.hsl.S * right.hsl.L -\n left.hsl.L * ml2 * right.hsl.S;\n\n const x = aNumerator / delta;\n const y = bNumerator / delta;\n const z = cNumerator / delta;\n\n const saturation = x * luminosity * luminosity + y * luminosity + z;\n return Math.max(Math.min(Math.round(saturation), 100), 0);\n }\n}\n\nexport class ColorServiceMock {\n public async randomColor(): Promise {\n return new Color([Shade.random()], 'Random');\n }\n public regenerateShades(_color: Color): void {}\n}\n", - "properties": [], - "methods": [ - { - "name": "randomColor", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 309, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "regenerateShades", - "args": [ - { - "name": "_color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 312, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "CssFormatter", - "id": "class-CssFormatter-ed626a360c929613db5e311ad606e2bb73535a80f601f3de4eeafd46c8224fa835d055a54e6bec1b3c1d4c139285228f2671d25cff86a26a8d534c790f0d422a", - "file": "src/app/shared/formatter/css.formatter.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Formatter } from '../interfaces/formatter.interface';\nimport { Color } from '../model/color.model';\nimport { Palette } from '../model/palette.model';\nimport { Shade } from '../model/shade.model';\n\nexport class CssFormatter implements Formatter {\n public filename = 'colors.css';\n public mimeType = 'text/css';\n\n public formatFile(palette: Palette): string {\n const content = this.formatPalette(palette).replace(/\\n/g, '\\n\\t');\n\n return `:root {\\n\\t${content}\\n}`;\n }\n\n public formatPalette(palette: Palette): string {\n const content = palette.colors.map((color) => this.formatColor(color)).join('\\n\\n');\n\n return `/* Color palette generated by ${window.location.origin} */\\n\\n${content}`;\n }\n\n public formatColor(color: Color): string {\n const name = color.name.replace(/\\s+/g, '-').toLowerCase();\n const shades = color.shades.map((shade) => this.formatShade(shade, name)).join('\\n');\n\n return shades;\n }\n\n public formatShade(shade: Shade, name: string): string {\n return `--${name}-${shade.index}:${shade.index < 100 ? ' ' : ''} ${shade.hex};`;\n }\n}\n", - "properties": [ - { - "name": "filename", - "defaultValue": "'colors.css'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 7, - "modifierKind": [ - 125 - ] - }, - { - "name": "mimeType", - "defaultValue": "'text/css'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 8, - "modifierKind": [ - 125 - ] - } - ], - "methods": [ - { - "name": "formatColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 22, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatFile", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 10, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatPalette", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 16, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatShade", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 29, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [], - "implements": [ - "Formatter" - ] - }, - { - "name": "DialogMock", - "id": "class-DialogMock-0727bdaa2ad6a7230abd414ecfbf9bf6af8a9cc7f9a0aa4b2e5f68a9e3b204314dbb4066f03267ffc794770393a7a4721b83401293680b069092d2153a08b4b2", - "file": "src/app/shared/utils/dialog-mock.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { DialogConfig } from '@angular/cdk/dialog';\nimport { ComponentType } from '@angular/cdk/overlay';\nimport { Observable, Subject } from 'rxjs';\n\nexport class DialogMock {\n private readonly _returnValue: T;\n\n public constructor(returnValue: T) {\n this._returnValue = returnValue;\n }\n\n public open(_component: ComponentType, _config: DialogConfig): { closed: Observable } {\n const closeSubject = new Subject();\n const dialogRef = {\n closed: closeSubject.asObservable()\n };\n\n setTimeout(() => {\n closeSubject.next(this._returnValue);\n }, 10);\n\n return dialogRef;\n }\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "returnValue", - "type": "T", - "deprecated": false, - "deprecationMessage": "" - } - ], - "line": 6, - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "returnValue", - "type": "T", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "properties": [], - "methods": [ - { - "name": "open", - "args": [ - { - "name": "_component", - "type": "ComponentType<>", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_config", - "type": "DialogConfig", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "literal type", - "typeParameters": [], - "line": 12, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_component", - "type": "ComponentType<>", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_config", - "type": "DialogConfig", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "DialogServiceMock", - "id": "class-DialogServiceMock-bcd611c160b0f6984d3fc1d98bb0b229435b5549357e48f4b7862e5028b1d8de65f800ca0a541d75813bb8ff810058f545d897327e9c5ddfac833e0b0ada9de7", - "file": "src/app/shared/data-access/dialog.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class DialogService {\n public async prompt(message: string, defaultValue: string): Promise {\n return window.prompt(message, defaultValue) ?? undefined;\n }\n\n public async confirm(message: string): Promise {\n return window.confirm(message);\n }\n\n public async alert(message: string): Promise {\n window.alert(message);\n }\n}\n\nexport class DialogServiceMock {\n public async prompt(_: string, value: string): Promise {\n return `${value}_test`;\n }\n\n public async confirm(_: string): Promise {\n return true;\n }\n\n public async alert(_: string): Promise {\n return;\n }\n}\n", - "properties": [], - "methods": [ - { - "name": "alert", - "args": [ - { - "name": "_", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 29, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "_", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "confirm", - "args": [ - { - "name": "_", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 25, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "_", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "prompt", - "args": [ - { - "name": "_", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "value", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 21, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "_", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "value", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "ExportServiceMock", - "id": "class-ExportServiceMock-65597d546f11d47e4e92b5779ab3bcd75c9841c826f18120d1021fa66a1a38f2ead4d80d714ca74f8a9dd8e5dc84e5f11f4f90700af6f938d4d6ed3e7156e86c", - "file": "src/app/shared/data-access/export.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, inject } from '@angular/core';\nimport { ExportFormat } from '../constants/export-format';\nimport { CssFormatter } from '../formatter/css.formatter';\nimport { LessFormatter } from '../formatter/less.formatter';\nimport { ScssFormatter } from '../formatter/scss.formatter';\nimport { TailwindFormatter } from '../formatter/tailwind.formatter';\nimport { Formatter } from '../interfaces/formatter.interface';\nimport { Palette } from '../model/palette.model';\nimport { ExportOption } from '../types/export-option';\nimport { AnalyticsService } from './analytics.service';\nimport { ToastService } from './toast.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ExportService {\n private readonly _toastService = inject(ToastService);\n private readonly _analyticsService = inject(AnalyticsService);\n\n public async exportPalette(palette: Palette, format: ExportFormat, option: ExportOption): Promise {\n const formatter = this.getFormatterForFormat(format);\n if (!formatter) {\n this._toastService.showToast({\n type: 'error',\n message: 'export.error.format-not-found'\n });\n return false;\n }\n\n const success = await this._exportPalette(palette, formatter, option);\n\n // Track the export event if successful\n if (success) {\n this._analyticsService.trackPaletteExport(format, option);\n }\n\n return success;\n }\n\n private async _exportPalette(palette: Palette, formatter: Formatter, option: ExportOption): Promise {\n if (option === 'copy') {\n return await this.copy(palette, formatter);\n } else if (option === 'file') {\n return await this.download(palette, formatter);\n }\n\n return false;\n }\n\n public async copy(palette: Palette, formatter: Formatter): Promise {\n const content = formatter.formatPalette(palette);\n\n try {\n await navigator.clipboard.writeText(content);\n return true;\n } catch (error) {\n console.error(error);\n this._toastService.showToast({\n type: 'error',\n message: 'export.error.copy-failed'\n });\n return false;\n }\n }\n\n public async download(palette: Palette, formatter: Formatter): Promise {\n const content = formatter.formatFile(palette);\n const blob = new Blob([content], { type: formatter.mimeType });\n\n const a = document.createElement('a');\n a.href = URL.createObjectURL(blob);\n a.download = formatter.filename;\n a.click();\n\n return true;\n }\n\n private getFormatterForFormat(format: ExportFormat): Formatter | undefined {\n switch (format) {\n case ExportFormat.TAILWIND:\n return new TailwindFormatter();\n case ExportFormat.SCSS:\n return new ScssFormatter();\n case ExportFormat.CSS:\n return new CssFormatter();\n case ExportFormat.LESS:\n return new LessFormatter();\n default:\n return undefined;\n }\n }\n}\n\nexport class ExportServiceMock {\n public async exportPalette(_palette: Palette, _format: ExportFormat, _option: ExportOption): Promise {\n return true;\n }\n\n public async copy(_palette: Palette, _formatter: Formatter): Promise {\n return true;\n }\n\n public async download(_palette: Palette, _formatter: Formatter): Promise {\n return true;\n }\n}\n", - "properties": [], - "methods": [ - { - "name": "copy", - "args": [ - { - "name": "_palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_formatter", - "type": "Formatter", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 99, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "_palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_formatter", - "type": "Formatter", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "download", - "args": [ - { - "name": "_palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_formatter", - "type": "Formatter", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 103, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "_palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_formatter", - "type": "Formatter", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "exportPalette", - "args": [ - { - "name": "_palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_format", - "type": "ExportFormat", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_option", - "type": "ExportOption", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 95, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "_palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_format", - "type": "ExportFormat", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_option", - "type": "ExportOption", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "HomeServiceMock", - "id": "class-HomeServiceMock-64596cf9da442e2a38fac475685f1feef0096cabc8f1f9117a91769618c1a2b12307fe602840675493e122349cc238b8abe7a1ed1b5e39dbaac6ded9d45f41b7", - "file": "src/app/home/data-access/home.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, signal } from '@angular/core';\nimport { PaletteScheme } from '../../shared/constants/palette-scheme';\nimport { LocalStorageKey } from '../../shared/enums/local-storage-keys';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class HomeService {\n public readonly hex = signal('#3B82F6');\n public readonly scheme = signal(PaletteScheme.RAINBOW);\n\n public constructor() {\n this.loadGenerationSettings();\n }\n\n public saveGenerationSettings(): void {\n localStorage.setItem(\n LocalStorageKey.LAST_GENERATION_SETTINGS,\n JSON.stringify({ hex: this.hex(), scheme: this.scheme() })\n );\n }\n\n public loadGenerationSettings(): void {\n const lastGeneration = localStorage.getItem(LocalStorageKey.LAST_GENERATION_SETTINGS);\n\n if (!lastGeneration) {\n return;\n }\n\n try {\n const { hex, scheme } = JSON.parse(lastGeneration);\n\n if (hex) {\n this.hex.set(hex);\n }\n\n if (scheme) {\n this.scheme.set(scheme);\n }\n } catch {\n localStorage.removeItem(LocalStorageKey.LAST_GENERATION_SETTINGS);\n }\n }\n}\n\nexport class HomeServiceMock {\n public readonly hex = signal('#3B82F6');\n public readonly scheme = signal(PaletteScheme.RAINBOW);\n\n public saveGenerationSettings(): void {}\n public loadGenerationSettings(): void {}\n}\n", - "properties": [ - { - "name": "hex", - "defaultValue": "signal('#3B82F6')", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 47, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "scheme", - "defaultValue": "signal(PaletteScheme.RAINBOW)", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 48, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [ - { - "name": "loadGenerationSettings", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 51, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "saveGenerationSettings", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 50, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "LanguageServiceMock", - "id": "class-LanguageServiceMock-1248279049809b33d49b94b2ab4de79d9b364541fad43e677bdefa3529c46939ebb4ffdecd635a527f30b883a3ef8f712844054268fd3c94b3ce043e3f15e159", - "file": "src/app/shared/data-access/language.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { TranslateService } from '@ngx-translate/core';\nimport { firstValueFrom } from 'rxjs';\nimport { LANGUAGE_OPTIONS } from '../../layout/constants/languages';\nimport { Language } from '../../layout/types/language';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class LanguageService {\n private readonly _translateService = inject(TranslateService);\n private readonly _titleService = inject(Title);\n\n private readonly _language = signal('en');\n\n public get language(): Signal {\n return this._language.asReadonly();\n }\n\n public constructor() {\n effect(async () => {\n await firstValueFrom(this._translateService.use(this._language()));\n localStorage.setItem(LocalStorageKey.LANGUAGE, this._language());\n document.documentElement.setAttribute('lang', this._language());\n this._titleService.setTitle(this._translateService.instant('title'));\n });\n\n const storedLanguage = localStorage.getItem(LocalStorageKey.LANGUAGE);\n if (storedLanguage) {\n this._language.set(storedLanguage as Language);\n return;\n }\n\n const browserLang = this._translateService.getBrowserLang();\n if (LANGUAGE_OPTIONS.find((option) => option.value === browserLang)) {\n this._language.set(browserLang as Language);\n } else {\n this._language.set('en');\n }\n }\n\n public setLanguage(language: Language): void {\n this._language.set(language);\n }\n}\n\nexport class LanguageServiceMock {\n public readonly language = signal('en').asReadonly();\n public setLanguage(_language: Language): void {}\n}\n", - "properties": [ - { - "name": "language", - "defaultValue": "signal('en').asReadonly()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 50, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [ - { - "name": "setLanguage", - "args": [ - { - "name": "_language", - "type": "Language", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 51, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_language", - "type": "Language", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "LessFormatter", - "id": "class-LessFormatter-6f90907778d6d0c37c1430835e9c8fab331b7e90f329c5d8a7c28fac831be22740944c03c3ec844abd9ace7fd762f9425fb6f530a4137cc5bfed11ab2872dd9b", - "file": "src/app/shared/formatter/less.formatter.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Formatter } from '../interfaces/formatter.interface';\nimport { Color } from '../model/color.model';\nimport { Palette } from '../model/palette.model';\nimport { Shade } from '../model/shade.model';\n\nexport class LessFormatter implements Formatter {\n public filename = 'colors.less';\n public mimeType = 'text/less';\n\n public formatFile(palette: Palette): string {\n const content = this.formatPalette(palette);\n\n return `// Import the variables into your LESS files with\\n//\\t@import 'colors';\\n\\n${content}`;\n }\n\n public formatPalette(palette: Palette): string {\n const content = palette.colors.map((color) => this.formatColor(color)).join('\\n\\n');\n\n return `// Color palette generated by ${window.location.origin}\\n\\n${content}`;\n }\n\n public formatColor(color: Color): string {\n const name = color.name.replace(/\\s+/g, '-').toLowerCase();\n const shades = color.shades.map((shade) => this.formatShade(shade, name)).join('\\n');\n\n return shades;\n }\n\n public formatShade(shade: Shade, name: string): string {\n return `@${name}-${shade.index}:${shade.index < 100 ? ' ' : ''} ${shade.hex};`;\n }\n}\n", - "properties": [ - { - "name": "filename", - "defaultValue": "'colors.less'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 7, - "modifierKind": [ - 125 - ] - }, - { - "name": "mimeType", - "defaultValue": "'text/less'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 8, - "modifierKind": [ - 125 - ] - } - ], - "methods": [ - { - "name": "formatColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 22, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatFile", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 10, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatPalette", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 16, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatShade", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 29, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [], - "implements": [ - "Formatter" - ] - }, - { - "name": "MatomoTitleInterceptor", - "id": "class-MatomoTitleInterceptor-253dab8f6ee43f5d8b6a441ca79b2eda6f745546e2a565e60c776eb9c28ba7bcba4713b73482c40a02ec6e6926e53f68c5f281d196cd4c15886cc53463d30475", - "file": "src/app/shared/utils/matomo-title-interceptor.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { inject } from '@angular/core';\nimport { NavigationEnd } from '@angular/router';\nimport { MatomoRouterInterceptor, MatomoTracker } from 'ngx-matomo-client';\nimport { Observable } from 'rxjs';\n\nexport class MatomoTitleInterceptor implements MatomoRouterInterceptor {\n private readonly _tracker = inject(MatomoTracker);\n\n public beforePageTrack(event: NavigationEnd): void | Observable | Promise {\n switch (event.urlAfterRedirects) {\n case '/':\n this._tracker.setDocumentTitle('Home');\n break;\n case '/view':\n this._tracker.setDocumentTitle('View');\n break;\n case '/preview':\n this._tracker.setDocumentTitle('Preview');\n break;\n default:\n this._tracker.setDocumentTitle('Unknown');\n console.warn('Unknown page: ', event.urlAfterRedirects);\n break;\n }\n }\n}\n", - "properties": [], - "methods": [ - { - "name": "beforePageTrack", - "args": [ - { - "name": "event", - "type": "NavigationEnd", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void | Observable | Promise", - "typeParameters": [], - "line": 9, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "event", - "type": "NavigationEnd", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [], - "implements": [ - "MatomoRouterInterceptor" - ] - }, - { - "name": "MatomoTrackerMock", - "id": "class-MatomoTrackerMock-dc4de19a2cbbd761091d1f7be1ad68f9f9f2066d1fdf6af41de57a07fb72065e709ed1ba8ef467c1a607de380ec0305cdc3437bca855247b9ed9d4ed390fae0a", - "file": "src/app/shared/utils/matomo-tracker-mock.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "export class MatomoTrackerMock {\n public enableHeartBeatTimer(_delay: number): void {}\n public setCustomDimension(_index: number, _value: string): void {}\n public trackEvent(_category: string, _action: string, _name?: string, _value?: number): void {}\n public setUserId(_userId: string): void {}\n public setCookieConsentGiven(): void {}\n}\n", - "properties": [], - "methods": [ - { - "name": "enableHeartBeatTimer", - "args": [ - { - "name": "_delay", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 3, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_delay", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "setCookieConsentGiven", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 7, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "setCustomDimension", - "args": [ - { - "name": "_index", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_value", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 4, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_index", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_value", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "setUserId", - "args": [ - { - "name": "_userId", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 6, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_userId", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "trackEvent", - "args": [ - { - "name": "_category", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_action", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_name", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "optional": true - }, - { - "name": "_value", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 5, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_category", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_action", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_name", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "text": "param" - } - }, - { - "name": "_value", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "MobileServiceMock", - "id": "class-MobileServiceMock-51f3c2dda58e7caf43064b20bcf150d3ce031cabc1c44a4baa38f648348976ee92b676509402d3c11d66bde1ad5098b9ab24781fe89ee52760bc3ed2990b188f", - "file": "src/app/shared/data-access/mobile.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, Signal, effect, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { fromEvent } from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class MobileService {\n private readonly _resize: Signal;\n private readonly _isMobile = signal(false);\n\n public get resize(): Signal {\n return this._resize;\n }\n\n public get isMobile(): Signal {\n return this._isMobile.asReadonly();\n }\n\n public constructor() {\n const resize$ = fromEvent(window, 'resize');\n this._resize = toSignal(resize$);\n\n effect(\n () => {\n // Reference resize signal to trigger computation on window resize\n if (!this._resize()) {\n return;\n }\n\n this._isMobile.set(window.innerWidth < 640);\n },\n {\n allowSignalWrites: true\n }\n );\n\n this._isMobile.set(window.innerWidth < 640);\n }\n}\n\nexport class MobileServiceMock {\n public readonly isMobile = signal(false).asReadonly();\n public readonly resize = signal(undefined).asReadonly();\n}\n", - "properties": [ - { - "name": "isMobile", - "defaultValue": "signal(false).asReadonly()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 43, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "resize", - "defaultValue": "signal(undefined).asReadonly()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 44, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "OfflineServiceMock", - "id": "class-OfflineServiceMock-eada8a658fc87c05ed94e9251da86de70bcf7f302ec35537d019445889e447f4c4e9db745ebb65f92f6f12fa98c75baf9af0cb9b9ecbfdfe01798cef1a404a49", - "file": "src/app/shared/data-access/offline.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, signal } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BehaviorSubject, debounceTime, fromEvent, map, merge, of } from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class OfflineService {\n private readonly _isOffline = new BehaviorSubject(!navigator.onLine);\n\n public readonly isOffline = toSignal(this._isOffline, {\n initialValue: this._isOffline.value\n });\n\n public constructor() {\n merge(of(null), fromEvent(window, 'online'), fromEvent(window, 'offline'))\n .pipe(\n map(() => navigator.onLine),\n debounceTime(5000)\n )\n .subscribe((isOnline) => {\n this._isOffline.next(!isOnline);\n });\n }\n}\n\nexport class OfflineServiceMock {\n public readonly isOffline = signal(false).asReadonly();\n}\n", - "properties": [ - { - "name": "isOffline", - "defaultValue": "signal(false).asReadonly()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 28, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "Palette", - "id": "class-Palette-b314d07d6b3eb302535ff314a0cc85e41d82c2ce51c74e9983ec152862fec14eb982c12bb3934988b80e0a5eb5636c60665613bb117e8d24ea63b3d4cacb817d", - "file": "src/app/shared/model/palette.model.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Color } from './color.model';\n\nexport class Palette {\n public name: string;\n public readonly colors: Array;\n\n public constructor(name: string, colors: Array) {\n this.colors = colors;\n this.name = name;\n }\n\n public addColor(color: Color): void {\n this.colors.push(color);\n }\n\n public removeColor(color: Color): void {\n const index = this.colors.indexOf(color);\n if (index > -1) {\n this.colors.splice(index, 1);\n }\n }\n\n public static parse(palette: string | object): Palette {\n if (typeof palette === 'string') {\n try {\n palette = JSON.parse(palette);\n } catch (e) {\n throw new Error(`Could not parse palette (not a valid JSON): \"${palette}\"`);\n }\n }\n\n if (!(palette instanceof Object)) {\n throw new Error(`Could not parse palette (not an object): \"${palette}\"`);\n }\n\n if (!('colors' in palette)) {\n throw new Error(`Could not parse palette (missing \"colors\" property): \"${palette}\"`);\n }\n\n let name: string | undefined;\n if ('name' in palette && typeof palette.name === 'string') {\n name = palette.name;\n }\n\n const colors: Array = [];\n if (!Array.isArray(palette.colors)) {\n throw new Error(`Could not parse palette (invalid \"colors\" property): \"${palette.colors}\"`);\n }\n\n for (const color of palette.colors) {\n try {\n colors.push(Color.parse(color));\n } catch (e) {\n throw new Error(`Could not parse palette (invalid color): \"${color}\"`);\n }\n }\n\n return new Palette(name || 'Palette', colors);\n }\n\n public toJSON(): object {\n return {\n name: this.name,\n colors: this.colors.map((color) => color.toJSON())\n };\n }\n\n public toString(): string {\n return JSON.stringify(this.toJSON());\n }\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "colors", - "type": "Array", - "deprecated": false, - "deprecationMessage": "" - } - ], - "line": 5, - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "colors", - "type": "Array", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "properties": [ - { - "name": "colors", - "deprecated": false, - "deprecationMessage": "", - "type": "Array", - "optional": false, - "description": "", - "line": 5, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "name", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 4, - "modifierKind": [ - 125 - ] - } - ], - "methods": [ - { - "name": "addColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 12, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "parse", - "args": [ - { - "name": "palette", - "type": "string | object", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Palette", - "typeParameters": [], - "line": 23, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 126 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "string | object", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "removeColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 16, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "toJSON", - "args": [], - "optional": false, - "returnType": "object", - "typeParameters": [], - "line": 61, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "toString", - "args": [], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 68, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "PaletteServiceMock", - "id": "class-PaletteServiceMock-43199fad572d441e0952f696167cbe59794b813974a7e45c0163132e686e323ae510b8459a65dbe1550d217d44a4cd4c4acfb482986eb7674501f90cb12b41f6", - "file": "src/app/shared/data-access/palette.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { PaletteScheme } from '../constants/palette-scheme';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\nimport { Value } from '../model';\nimport { Color } from '../model/color.model';\nimport { Palette } from '../model/palette.model';\nimport { Shade } from '../model/shade.model';\nimport { ColorNameService } from './color-name.service';\nimport { ColorService } from './color.service';\nimport { ToastService } from './toast.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class PaletteService {\n private readonly _colorService = inject(ColorService);\n private readonly _colorNameService = inject(ColorNameService);\n private readonly _toastService = inject(ToastService);\n\n private readonly _palette = signal(undefined);\n\n public get palette(): Signal {\n return this._palette.asReadonly();\n }\n\n public constructor() {\n this.loadPaletteFromLocalStorage();\n\n effect(() => {\n this._updateVariables();\n });\n }\n\n public loadPaletteFromLocalStorage(): void {\n // Check if there was a palette stored for an app update\n let palette = localStorage.getItem(LocalStorageKey.PALETTE_TMP);\n\n if (palette) {\n // Palette was stored for an update, remove it now\n localStorage.removeItem(LocalStorageKey.PALETTE_TMP);\n } else {\n // Load the palette saved by the user\n palette = localStorage.getItem(LocalStorageKey.PALETTE);\n }\n\n if (palette) {\n try {\n this._palette.set(Palette.parse(palette));\n } catch (e) {\n this._toastService.showToast({\n type: 'error',\n message: 'toast.error.palette-load'\n });\n }\n }\n }\n\n public savePaletteToLocalStorage(upgrade = false): void {\n const palette = this._palette();\n if (palette) {\n if (upgrade) {\n // Store the palette in a different key to reload it in the current state after an app update\n localStorage.setItem(LocalStorageKey.PALETTE_TMP, palette.toString());\n } else {\n localStorage.setItem(LocalStorageKey.PALETTE, palette.toString());\n }\n }\n }\n\n public async generatePalette(hex: string, scheme: PaletteScheme): Promise {\n const palette = await this._generatePalette(hex, scheme);\n\n for (const color of palette.colors) {\n // Get the color name\n color.name = await this._colorNameService.getColorName(color.shades[0]);\n\n // Regenerate the shades\n await this._colorService.regenerateShades(color);\n }\n\n this._palette.set(palette);\n }\n\n private async _generatePalette(hex: string, scheme: PaletteScheme): Promise {\n switch (scheme) {\n case PaletteScheme.RAINBOW:\n return this._generateRainbowPalette(hex);\n case PaletteScheme.MONOCHROME:\n return this._generateMonochromePalette(hex);\n case PaletteScheme.ANALOGOUS:\n return this._generateAnalogousPalette(hex);\n case PaletteScheme.COMPLEMENTARY:\n return this._generateComplementaryPalette(hex);\n case PaletteScheme.SPLIT_COMPLEMENTARY:\n return this._generateSplitPalette(hex);\n case PaletteScheme.TRIADIC:\n return this._generateTriadicPalette(hex);\n case PaletteScheme.COMPOUND:\n return this._generateCompoundPalette(hex);\n default:\n return this._generatePalette(hex, this._getRandomScheme());\n }\n }\n\n private _getRandomScheme(): PaletteScheme {\n const schemes = Object.values(PaletteScheme);\n return schemes[Math.floor(Math.random() * schemes.length)];\n }\n\n private _generateRainbowPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n let index = 0;\n const color = new Color([shade], `${index++}`);\n const rainbow = new Palette('Rainbow', [color]);\n\n /*\n * These are hues that are spaced out across the color wheel\n * to create a rainbow effect\n */\n const rainbowHues = [4, 26, 55, 95, 149, 200, 253];\n let currentHue = rainbowHues.reduce((best, current) =>\n Math.abs(current - shade.hsl.H) < Math.abs(best - shade.hsl.H) ? current : best\n );\n if (currentHue === 253 && shade.hsl.H > 308) {\n currentHue = 4;\n }\n\n for (const hue of rainbowHues) {\n if (hue === currentHue) {\n continue;\n }\n\n const newHue = (shade.hsl.H + (hue - currentHue) + 360) % 360;\n const newSaturation = Math.min(100, Math.max(0, shade.hsl.S - 20 + Math.floor(Math.random() * 40)));\n const newLightness = Math.min(100, Math.max(0, shade.hsl.L - 20 + Math.floor(Math.random() * 40)));\n const newShade = new Shade(-1, new Value({ H: newHue, S: newSaturation, L: newLightness }), true);\n\n rainbow.addColor(new Color([newShade], `${index++}`));\n }\n\n return rainbow;\n }\n\n private _generateMonochromePalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const monochrome = new Palette('Monochrome', []);\n\n monochrome.addColor(new Color([shade], 'Primary'));\n monochrome.addColor(new Color([new Shade(-1, new Value({ H: shade.hsl.H, S: 30, L: 50 }), true)], 'Muted'));\n monochrome.addColor(new Color([new Shade(-1, new Value({ H: shade.hsl.H, S: 2, L: 50 }), true)], 'Gray'));\n\n return monochrome;\n }\n\n private _generateAnalogousPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const analogous = new Palette('Analogous', []);\n\n analogous.addColor(new Color([shade], 'Primary'));\n\n analogous.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 315),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n analogous.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 270),\n S: 25,\n L: 20\n }),\n true\n )\n ],\n 'Secondary Muted'\n )\n );\n\n analogous.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 45),\n S: shade.hsl.S,\n L: 50\n }),\n true\n )\n ],\n 'Accent'\n )\n );\n analogous.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 90),\n S: 25,\n L: 20\n }),\n true\n )\n ],\n 'Accent Muted'\n )\n );\n\n return analogous;\n }\n\n private _generateComplementaryPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const complementary = new Palette('Complementary', []);\n\n complementary.addColor(new Color([shade], 'Primary'));\n\n complementary.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: shade.hsl.H,\n S: 3,\n L: 50\n }),\n true\n )\n ],\n 'Gray'\n )\n );\n\n complementary.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 180),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n\n return complementary;\n }\n\n private _generateSplitPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const split = new Palette('Split', []);\n\n split.addColor(new Color([shade], 'Primary'));\n\n split.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 20),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n split.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 20),\n S: 3,\n L: 50\n }),\n true\n )\n ],\n 'Gray'\n )\n );\n\n split.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 180),\n S: shade.hsl.S,\n L: 80\n }),\n true\n )\n ],\n 'Accent'\n )\n );\n\n return split;\n }\n\n private _generateTriadicPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const triadic = new Palette('Triadic', []);\n\n triadic.addColor(new Color([shade], 'Primary'));\n\n triadic.addColor(new Color([new Shade(-1, new Value({ H: shade.hsl.H, S: 20, L: 30 }), true)], 'Primary Muted'));\n\n triadic.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 120),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n triadic.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 120),\n S: 20,\n L: 30\n }),\n true\n )\n ],\n 'Secondary Muted'\n )\n );\n\n triadic.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 240),\n S: shade.hsl.S,\n L: 80\n }),\n true\n )\n ],\n 'Accent'\n )\n );\n\n return triadic;\n }\n\n private _generateCompoundPalette(hex: string): Palette {\n const shade = new Shade(-1, new Value(hex), true);\n\n const compound = new Palette('Compound', []);\n\n compound.addColor(new Color([shade], 'Primary'));\n\n compound.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 210),\n S: Math.max(shade.hsl.S - 20, 0),\n L: 40\n }),\n true\n )\n ],\n 'Secondary'\n )\n );\n\n compound.addColor(\n new Color(\n [\n new Shade(\n -1,\n new Value({\n H: this._changeHueOnWheel(shade.hsl.H, 150),\n S: shade.hsl.S,\n L: 50\n }),\n true\n )\n ],\n 'Accent'\n )\n );\n\n return compound;\n }\n\n private _updateVariables(): void {\n const root = document.documentElement;\n const activeProperties: Array = [];\n const palette = this._palette();\n\n if (palette) {\n for (const color of palette.colors) {\n const name = color.name.replace(/\\s+/g, '-').toLowerCase();\n\n for (const shade of color.shades) {\n const variable = `--color-${name}-${shade.index}`;\n const value = shade.hex;\n\n root.style.setProperty(variable, value);\n activeProperties.push(variable);\n }\n }\n }\n\n const allProperties = Array.from(root.style);\n for (const property of allProperties) {\n if (!property.startsWith('--color')) {\n continue;\n }\n\n if (!activeProperties.includes(property)) {\n root.style.removeProperty(property);\n }\n }\n }\n\n private _changeHueOnWheel(hue: number, change: number): number {\n let wheel;\n if (hue < 60) wheel = 2 * hue;\n else if (hue < 120) wheel = hue + 60;\n else if (hue < 240) wheel = 0.5 * hue + 120;\n else wheel = hue;\n\n wheel += change;\n wheel %= 360;\n\n let newHue;\n if (wheel < 120) newHue = 0.5 * wheel;\n else if (wheel < 180) newHue = wheel + 300;\n else if (wheel < 240) newHue = 2 * wheel + 120;\n else newHue = wheel;\n\n return newHue % 360;\n }\n}\n\nexport class PaletteServiceMock {\n public palette = signal(new Palette('Mock', [new Color([Shade.random()], 'MockColor')]));\n public loadPaletteFromLocalStorage(): void {}\n public savePaletteToLocalStorage(): void {}\n public generatePalette(_hex: string, _scheme: PaletteScheme): void {}\n}\n", - "properties": [ - { - "name": "palette", - "defaultValue": "signal(new Palette('Mock', [new Color([Shade.random()], 'MockColor')]))", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 494, - "modifierKind": [ - 125 - ] - } - ], - "methods": [ - { - "name": "generatePalette", - "args": [ - { - "name": "_hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "_scheme", - "type": "PaletteScheme", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 497, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "_scheme", - "type": "PaletteScheme", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "loadPaletteFromLocalStorage", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 495, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "savePaletteToLocalStorage", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 496, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "PwaServiceMock", - "id": "class-PwaServiceMock-d68a0ba55aff4e1feb45ae5a07a7394f6c2e4adaf29db2190f401a92c9bb453aabe12b87b46640177ee0eec80c4f235e8f0eb252df7a5a66bb743a0208dc1aee", - "file": "src/app/shared/data-access/pwa.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { SwUpdate, VersionEvent } from '@angular/service-worker';\nimport { TranslateService } from '@ngx-translate/core';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\nimport { TrackingEventAction, TrackingEventCategory } from '../enums/tracking-event';\nimport { IS_RUNNING_TEST } from '../utils/is-running-test';\nimport { AnalyticsService } from './analytics.service';\nimport { DialogService } from './dialog.service';\nimport { PaletteService } from './palette.service';\nimport { ToastService } from './toast.service';\nimport { VersionService } from './version.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class PwaService {\n private readonly _SwUpdate = inject(SwUpdate);\n private readonly _isRunningTest = inject(IS_RUNNING_TEST);\n private readonly _translateService = inject(TranslateService);\n private readonly _analyticsService = inject(AnalyticsService);\n private readonly _toastService = inject(ToastService);\n private readonly _dialogService = inject(DialogService);\n private readonly _paletteService = inject(PaletteService);\n private readonly _versionService = inject(VersionService);\n\n private readonly _isPwa = signal(false);\n\n public get isPwa(): Signal {\n return this._isPwa.asReadonly();\n }\n\n public constructor() {\n effect(() => {\n this._analyticsService.setIsPwa(this._isPwa());\n });\n\n // Check if the app is currently running as a PWA\n if (\n window.matchMedia('(display-mode: standalone)').matches ||\n //@ts-expect-error - Navigator standalone is not a standard web api, but we try to use it anyway\n window.navigator.standalone\n ) {\n this._isPwa.set(true);\n }\n\n // Listen for the app being installed as a PWA\n window.addEventListener('appinstalled', () => {\n this._isPwa.set(true);\n this._analyticsService.trackEvent(TrackingEventCategory.PWA, TrackingEventAction.PWA_INSTALL);\n });\n\n // Listen for updates to the service worker\n this._SwUpdate.versionUpdates.subscribe(async (event) => {\n await this._handleUpdateEvents(event);\n });\n\n // Listen for broken service worker\n this._SwUpdate.unrecoverable.subscribe(() => {\n this._dialogService.alert(this._translateService.instant('pwa.broken'));\n });\n\n // Check if the app is currently updating\n if (localStorage.getItem(LocalStorageKey.UPGRADING)) {\n localStorage.removeItem(LocalStorageKey.UPGRADING);\n this._toastService.showToast({\n type: 'info',\n message: 'pwa.update-success',\n parameters: { version: this._versionService.appVersion }\n });\n }\n }\n\n private async _handleUpdateEvents(event: VersionEvent): Promise {\n // Latest version is already installed\n if (event.type === 'NO_NEW_VERSION_DETECTED') {\n return;\n }\n\n // New version available\n if (event.type === 'VERSION_DETECTED') {\n this._toastService.showToast({\n type: 'info',\n message: 'pwa.update-available',\n parameters: {\n // @ts-expect-error - `appData` is filled by the prebuilt script to contain the current app version\n version: event.version.appData?.version\n }\n });\n return;\n }\n\n // Update installation failed\n if (event.type === 'VERSION_INSTALLATION_FAILED') {\n console.error('PWA update failed', event.error);\n this._analyticsService.trackEvent(TrackingEventCategory.PWA, TrackingEventAction.PWA_UPDATE_FAILED);\n this._toastService.showToast({\n type: 'error',\n message: 'pwa.update-failed',\n parameters: {\n // @ts-expect-error - `appData` is filled by the prebuilt script to contain the current app version\n version: event.version.appData?.version\n }\n });\n return;\n }\n\n // Update was downloaded and can be installed through restart\n const restart = await this._dialogService.confirm(\n this._translateService.instant('pwa.restart', {\n old:\n // @ts-expect-error - `appData` is filled by the prebuilt script to contain the current app version\n event.currentVersion.appData?.version ?? this._versionService.appVersion,\n // @ts-expect-error - `appData` is filled by the prebuilt script to contain the current app version\n new: event.latestVersion.appData?.version\n })\n );\n\n // Continue without updating\n if (!restart) {\n return;\n }\n\n // Save the current palette to local storage\n this._paletteService.savePaletteToLocalStorage(true);\n\n // Set the flag to indicate that the app is currently updating\n localStorage.setItem(LocalStorageKey.UPGRADING, 'true');\n\n // Track the update\n this._analyticsService.trackEvent(TrackingEventCategory.PWA, TrackingEventAction.PWA_UPDATE_COMPLETED);\n\n // Reload the app to apply the update\n if (!this._isRunningTest) {\n document.location.reload();\n }\n }\n}\n\nexport class PwaServiceMock {}\n", - "properties": [], - "methods": [], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "ScssFormatter", - "id": "class-ScssFormatter-e670c5a43aaa13b7d32b7be3e538c5550047d1f7859fe88653d2007704fe5da9cf5d27d279fd07e95aa9d209f7fe4e756dafe586707e2921633964017a16bc8e", - "file": "src/app/shared/formatter/scss.formatter.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Formatter } from '../interfaces/formatter.interface';\nimport { Color } from '../model/color.model';\nimport { Palette } from '../model/palette.model';\nimport { Shade } from '../model/shade.model';\n\nexport class ScssFormatter implements Formatter {\n public filename = '_colors.scss';\n public mimeType = 'text/scss';\n\n public formatFile(palette: Palette): string {\n const content = this.formatPalette(palette);\n\n return `/* Import the variables into your SCSS files with\\n\\t@use 'colors';\\n*/\\n\\n${content}`;\n }\n\n public formatPalette(palette: Palette): string {\n const content = palette.colors.map((color) => this.formatColor(color)).join('\\n\\n');\n\n return `/* Color palette generated by ${window.location.origin} */\\n\\n${content}`;\n }\n\n public formatColor(color: Color): string {\n const name = color.name.replace(/\\s+/g, '-').toLowerCase();\n const shades = color.shades.map((shade) => this.formatShade(shade, name)).join('\\n');\n\n return shades;\n }\n\n public formatShade(shade: Shade, name: string): string {\n return `$${name}-${shade.index}:${shade.index < 100 ? ' ' : ''} ${shade.hex};`;\n }\n}\n", - "properties": [ - { - "name": "filename", - "defaultValue": "'_colors.scss'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 7, - "modifierKind": [ - 125 - ] - }, - { - "name": "mimeType", - "defaultValue": "'text/scss'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 8, - "modifierKind": [ - 125 - ] - } - ], - "methods": [ - { - "name": "formatColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 22, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatFile", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 10, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatPalette", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 16, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatShade", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 29, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "name", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [], - "implements": [ - "Formatter" - ] - }, - { - "name": "Shade", - "id": "class-Shade-bebbc123c9025219fd9f12f0ef9928f641f1e455c57ccef8f6992353b5f3fedc1eebfb1adf7696ab139f029b4344b558bedc9b31b021b44fce8efa1d24af3ef7", - "file": "src/app/shared/model/shade.model.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { HSLObject } from '../types/color-format';\nimport { perceivedBrightnessFromRGB } from '../utils/perceived-brightness';\nimport { Value } from './value.model';\n\nexport class Shade {\n public index: number;\n public fixed: boolean;\n private _value: Value;\n private _perceivedBrightness?: number;\n\n public constructor(index: number, value: Value, fixed = false) {\n this.index = index;\n this._value = value;\n this.fixed = fixed;\n }\n\n public get hslValue(): string {\n const hsl = this._value.HSL;\n return `hsl(${hsl.H}, ${hsl.S}%, ${hsl.L}%)`;\n }\n\n public get hsl(): HSLObject {\n return this._value.HSL;\n }\n\n public get value(): HSLObject {\n return this._value.HSL;\n }\n\n public set hsl(hsl: HSLObject) {\n this._value.HSL = hsl;\n\n this._perceivedBrightness = undefined;\n }\n\n public get hex(): string {\n return this._value.HEX.toUpperCase();\n }\n\n public set hex(hex: string) {\n this._value.HEX = hex;\n\n this._perceivedBrightness = undefined;\n }\n\n public get perceivedBrightness(): number {\n if (this._perceivedBrightness !== undefined) {\n return this._perceivedBrightness;\n }\n\n this._perceivedBrightness = perceivedBrightnessFromRGB(this._value.RGB);\n return this._perceivedBrightness;\n }\n\n public copy(): Shade {\n return new Shade(this.index, new Value(this.hex), this.fixed);\n }\n\n public static random(): Shade {\n const h = Math.floor(Math.random() * 360);\n const s = 30 + Math.floor(Math.random() * 60);\n const l = 25 + Math.floor(Math.random() * 50);\n\n const value = new Value({ H: h, S: s, L: l });\n return new Shade(0, value, true);\n }\n\n public static parse(shade: string | object): Shade {\n if (typeof shade === 'string') {\n try {\n shade = JSON.parse(shade);\n } catch (e) {\n throw new Error(`Could not parse shade (not a valid JSON): \"${shade}\"`);\n }\n }\n\n if (!(shade instanceof Object)) {\n throw new Error(`Could not parse shade (not an object): \"${shade}\"`);\n }\n\n let index = 0;\n if ('index' in shade && typeof shade.index === 'number') {\n index = shade.index;\n }\n\n let fixed = false;\n if ('fixed' in shade && typeof shade.fixed === 'boolean') {\n fixed = shade.fixed;\n }\n\n if (!('value' in shade)) {\n if ('hex' in shade && typeof shade.hex === 'string') {\n return new Shade(index, new Value(shade.hex), fixed);\n }\n\n throw new Error(`Could not parse shade (missing \"value\" property): \"${shade}\"`);\n }\n\n let value: Value | undefined;\n if (typeof shade.value === 'string' && shade.value.match(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) {\n value = new Value(shade.value);\n } else if (typeof shade.value === 'object') {\n const hsl = shade.value as HSLObject;\n if (\n 'H' in hsl &&\n 'S' in hsl &&\n 'L' in hsl &&\n typeof hsl.H === 'number' &&\n (typeof hsl.S === 'number' || typeof hsl.S === 'string') &&\n (typeof hsl.L === 'number' || typeof hsl.L === 'string')\n ) {\n value = new Value(hsl);\n }\n }\n\n if (value === undefined) {\n throw new Error(`Could not parse shade (invalid \"value\" property): \"${shade.value}\"`);\n }\n\n return new Shade(index, value, fixed);\n }\n\n public toJSON(): object {\n return {\n index: this.index,\n fixed: this.fixed,\n value: this._value.HSL\n };\n }\n\n public toString(): string {\n return JSON.stringify(this.toJSON());\n }\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "index", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "value", - "type": "Value", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "fixed", - "type": "", - "deprecated": false, - "deprecationMessage": "", - "defaultValue": "false" - } - ], - "line": 9, - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "index", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "value", - "type": "Value", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "fixed", - "type": "", - "deprecated": false, - "deprecationMessage": "", - "defaultValue": "false", - "tagName": { - "text": "param" - } - } - ] - }, - "properties": [ - { - "name": "fixed", - "deprecated": false, - "deprecationMessage": "", - "type": "boolean", - "optional": false, - "description": "", - "line": 7, - "modifierKind": [ - 125 - ] - }, - { - "name": "index", - "deprecated": false, - "deprecationMessage": "", - "type": "number", - "optional": false, - "description": "", - "line": 6, - "modifierKind": [ - 125 - ] - } - ], - "methods": [ - { - "name": "copy", - "args": [], - "optional": false, - "returnType": "Shade", - "typeParameters": [], - "line": 55, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "parse", - "args": [ - { - "name": "shade", - "type": "string | object", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Shade", - "typeParameters": [], - "line": 68, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 126 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "string | object", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "random", - "args": [], - "optional": false, - "returnType": "Shade", - "typeParameters": [], - "line": 59, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 126 - ] - }, - { - "name": "toJSON", - "args": [], - "optional": false, - "returnType": "object", - "typeParameters": [], - "line": 123, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "toString", - "args": [], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 131, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - } - ], - "indexSignatures": [], - "extends": [], - "accessors": { - "hslValue": { - "name": "hslValue", - "getSignature": { - "name": "hslValue", - "type": "string", - "returnType": "string", - "line": 17 - } - }, - "hsl": { - "name": "hsl", - "setSignature": { - "name": "hsl", - "type": "void", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "hsl", - "type": "HSLObject", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "void", - "line": 30, - "jsdoctags": [ - { - "name": "hsl", - "type": "HSLObject", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "getSignature": { - "name": "hsl", - "type": "", - "returnType": "HSLObject", - "line": 22 - } - }, - "value": { - "name": "value", - "getSignature": { - "name": "value", - "type": "", - "returnType": "HSLObject", - "line": 26 - } - }, - "hex": { - "name": "hex", - "setSignature": { - "name": "hex", - "type": "void", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "void", - "line": 40, - "jsdoctags": [ - { - "name": "hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "getSignature": { - "name": "hex", - "type": "string", - "returnType": "string", - "line": 36 - } - }, - "perceivedBrightness": { - "name": "perceivedBrightness", - "getSignature": { - "name": "perceivedBrightness", - "type": "number", - "returnType": "number", - "line": 46 - } - } - }, - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "SwUpdateMock", - "id": "class-SwUpdateMock-76aa1fea1b729677a8943329ac12b79b9339ff4cc8fc121e3f6c9e8bd62fb2f1944ba01e4231e18d99ed6dd1644f1557b0162648364f10e1650b3d097bc0f923", - "file": "src/app/shared/utils/sw-update-mock.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { VersionEvent } from '@angular/service-worker';\nimport { Observable, Subject } from 'rxjs';\nimport { sleep } from './sleep';\n\nconst oldVersion = { hash: 'OLD', appData: { version: '0.0.0' } };\nconst newVersion = { hash: 'NEW', appData: { version: '1.0.0' } };\n\nexport class SwUpdateMock {\n private readonly _versionUpdates = new Subject();\n public readonly unrecoverable = new Subject();\n\n public get versionUpdates(): Observable {\n return this._versionUpdates.asObservable();\n }\n\n public async emitNoUpdateAvailable(): Promise {\n this._versionUpdates.next({\n type: 'NO_NEW_VERSION_DETECTED',\n version: oldVersion\n });\n\n await sleep(10);\n }\n\n public async emitUpdateAvailable(): Promise {\n this._versionUpdates.next({\n type: 'VERSION_DETECTED',\n version: newVersion\n });\n\n await sleep(10);\n }\n\n public async emitInstallationFailed(): Promise {\n this._versionUpdates.next({\n type: 'VERSION_INSTALLATION_FAILED',\n version: newVersion,\n error: 'Installation failed'\n });\n\n await sleep(10);\n }\n\n public async emitUpdateReady(): Promise {\n this._versionUpdates.next({\n type: 'VERSION_READY',\n currentVersion: oldVersion,\n latestVersion: newVersion\n });\n\n await sleep(10);\n }\n}\n", - "properties": [ - { - "name": "unrecoverable", - "defaultValue": "new Subject()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 10, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [ - { - "name": "emitInstallationFailed", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 34, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "emitNoUpdateAvailable", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 16, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "emitUpdateAvailable", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 25, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "emitUpdateReady", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 44, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - } - ], - "indexSignatures": [], - "extends": [], - "accessors": { - "versionUpdates": { - "name": "versionUpdates", - "getSignature": { - "name": "versionUpdates", - "type": "", - "returnType": "Observable", - "line": 12 - } - } - }, - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "TailwindFormatter", - "id": "class-TailwindFormatter-9c8f3f7432144cb6147fb458632878ba34e3093b2b267d49f8e863b74ff7274da14044a44432944f7a7cf045349a3d8911d0dfac58390d63c3940661a3e5f373", - "file": "src/app/shared/formatter/tailwind.formatter.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Formatter } from '../interfaces/formatter.interface';\nimport { Color } from '../model/color.model';\nimport { Palette } from '../model/palette.model';\nimport { Shade } from '../model/shade.model';\n\nexport class TailwindFormatter implements Formatter {\n public filename = 'tailwind.colors.js';\n public mimeType = 'text/javascript';\n\n public formatFile(palette: Palette): string {\n const content = this.formatPalette(palette).replace(/\\n/g, '\\n\\t');\n\n return `module.exports = {\\n\\t${content}\\n}`;\n }\n\n public formatPalette(palette: Palette): string {\n return palette.colors.map((color) => this.formatColor(color)).join(',\\n');\n }\n\n public formatColor(color: Color): string {\n const name = color.name.replace(/\\s+/g, '-').toLowerCase();\n const shades = color.shades.map((shade) => this.formatShade(shade)).join(',\\n');\n\n return `'${name}': {\\n${shades}\\n}`;\n }\n\n public formatShade(shade: Shade): string {\n return `\\t${shade.index}:${shade.index < 100 ? ' ' : ''} '${shade.hex}'`;\n }\n}\n", - "properties": [ - { - "name": "filename", - "defaultValue": "'tailwind.colors.js'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 7, - "modifierKind": [ - 125 - ] - }, - { - "name": "mimeType", - "defaultValue": "'text/javascript'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 8, - "modifierKind": [ - 125 - ] - } - ], - "methods": [ - { - "name": "formatColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 20, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatFile", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 10, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatPalette", - "args": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 16, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "palette", - "type": "Palette", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "formatShade", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "string", - "typeParameters": [], - "line": 27, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [], - "implements": [ - "Formatter" - ] - }, - { - "name": "ThemeServiceMock", - "id": "class-ThemeServiceMock-706f23df04ad56029f3427f2ceccb50cc2e781f3d80231272ea583e79efc0d87430091fa88fce59558af211b7d75f209c33faaf79fca0a24619d120326759505", - "file": "src/app/shared/data-access/theme.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, Signal, effect, signal } from '@angular/core';\nimport { LocalStorageKey } from '../enums/local-storage-keys';\nimport { Theme } from '../types/theme';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ThemeService {\n private readonly _theme = signal('light');\n\n public get theme(): Signal {\n return this._theme.asReadonly();\n }\n\n public constructor() {\n effect(async () => {\n localStorage.setItem(LocalStorageKey.THEME, this._theme());\n document.documentElement.classList.toggle('dark', this._theme() === 'dark');\n });\n\n const storedTheme = localStorage.getItem(LocalStorageKey.THEME);\n if (storedTheme === 'dark') {\n this._theme.set('dark');\n return;\n } else if (storedTheme === 'light') {\n return;\n }\n\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n if (prefersDark) {\n this._theme.set('dark');\n }\n }\n\n public setTheme(theme: Theme): void {\n this._theme.set(theme);\n }\n}\n\nexport class ThemeServiceMock {\n public readonly theme = signal('light').asReadonly();\n public setTheme(_theme: Theme): void {}\n}\n", - "properties": [ - { - "name": "theme", - "defaultValue": "signal('light').asReadonly()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 41, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [ - { - "name": "setTheme", - "args": [ - { - "name": "_theme", - "type": "Theme", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 42, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "_theme", - "type": "Theme", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "ToastServiceMock", - "id": "class-ToastServiceMock-38eb14cbec2f75a0e59079d1a748d7839c64fc16432c6abdfb9493155d9bc15aa2e91b60eb188b852941e6ff4a94acee9443a9d68e5103a232b4726106e05c93", - "file": "src/app/shared/data-access/toast.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { GlobalPositionStrategy, Overlay, OverlayRef, PositionStrategy } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { Injectable, Signal, effect, inject, signal } from '@angular/core';\nimport { Toast, ToastTimeouts } from '../interfaces/toast.interface';\nimport { ToastComponent } from '../ui/toast/toast.component';\nimport { MobileService } from './mobile.service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ToastService {\n private readonly _overlay = inject(Overlay);\n private readonly _mobileService = inject(MobileService);\n\n private readonly _toast = signal(undefined);\n\n public get toast(): Signal {\n return this._toast.asReadonly();\n }\n\n private _hideToast: ReturnType | undefined;\n private _positionStrategy: PositionStrategy | undefined;\n private _overlayRef: OverlayRef | undefined;\n\n public constructor() {\n // Create an overlay for the toast.\n this._overlayRef = this._overlay.create();\n\n effect(() => {\n // If there is no toast, clear the timeout and return.\n if (!this._toast()) {\n if (this._hideToast) {\n clearTimeout(this._hideToast);\n }\n this._hideToast = undefined;\n return;\n }\n\n // If there was a previous timeout, clear it.\n if (this._hideToast) {\n clearTimeout(this._hideToast);\n this._hideToast = undefined;\n }\n\n // Set a new timeout to hide the toast.\n this._hideToast = setTimeout(\n () => {\n this._toast.set(undefined);\n },\n ToastTimeouts[this._toast()!.type ?? 'default']\n );\n });\n\n // Attach the toast component to the overlay.\n effect(() => {\n if (this._toast()) {\n if (!this._overlayRef) {\n return;\n }\n\n this._overlayRef.detach();\n const toastPortal = new ComponentPortal(ToastComponent);\n const toastComponent = this._overlayRef.attach(toastPortal);\n toastComponent.setInput('toast', this._toast());\n const closeSubscription = toastComponent.instance.close.subscribe(() => {\n this.hideToast();\n closeSubscription.unsubscribe();\n });\n } else {\n this._overlayRef?.detach();\n }\n });\n\n // Update the position strategy based on the device type.\n effect(() => {\n if (this._mobileService.isMobile()) {\n this._positionStrategy = new GlobalPositionStrategy().top('0.5rem').centerHorizontally();\n } else {\n this._positionStrategy = new GlobalPositionStrategy().bottom('0.5rem').end('0.5rem');\n }\n\n this._overlayRef?.updatePositionStrategy(this._positionStrategy);\n });\n }\n\n /**\n * Shows the toast.\n *\n * @param toast The toast to be displayed.\n */\n public showToast(toast: Toast): void {\n this._toast.set(toast);\n }\n\n /**\n * Hides the toast.\n */\n public hideToast(): void {\n this._toast.set(undefined);\n }\n}\n\nexport class ToastServiceMock {\n private readonly _toast = signal({\n type: 'test',\n message: 'test'\n });\n\n public get toast(): Signal {\n return this._toast.asReadonly();\n }\n\n public showToast(toast: Toast): void {\n this._toast.set(toast);\n }\n\n public hideToast(): void {\n this._toast.set(undefined);\n }\n}\n", - "properties": [], - "methods": [ - { - "name": "hideToast", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 117, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "showToast", - "args": [ - { - "name": "toast", - "type": "Toast", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 113, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "toast", - "type": "Toast", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "indexSignatures": [], - "extends": [], - "accessors": { - "toast": { - "name": "toast", - "getSignature": { - "name": "toast", - "type": "", - "returnType": "Signal", - "line": 109 - } - } - }, - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "Value", - "id": "class-Value-445028da527a1dc8a0698900545a2fcdc647a4e4a8d272582ab7a27d25a6beb5c420e3283d2edaa447597c3ec9f2af13ecd8ed9550b2ae7910745cf160e93a07", - "file": "src/app/shared/model/value.model.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { HSLObject, RGBObject } from '../types/color-format';\n\nexport class Value {\n private _hue!: number;\n private _saturation!: number;\n private _lightness!: number;\n private _red!: number;\n private _green!: number;\n private _blue!: number;\n\n public constructor(value: HSLObject | RGBObject | string) {\n if (typeof value === 'string') {\n this.HEX = value;\n } else if ('R' in value) {\n this.RGB = value;\n } else {\n this.HSL = value;\n }\n }\n\n public set HSL(value: HSLObject) {\n if (value.H < 0 || value.H > 360 || value.S < 0 || value.S > 100 || value.L < 0 || value.L > 100) {\n throw `Color values [${value.H}°, ${value.S}%, ${value.L}%] are not in valid ranges.`;\n }\n\n this._hue = value.H;\n this._saturation = value.S;\n this._lightness = value.L;\n\n const rgb = this._HSLtoRGB(this._hue, this._saturation, this._lightness);\n this._red = rgb.R;\n this._green = rgb.G;\n this._blue = rgb.B;\n }\n\n public get HSL(): HSLObject {\n return {\n H: this._hue,\n S: this._saturation,\n L: this._lightness\n };\n }\n\n public set RGB(value: RGBObject) {\n if (value.R < 0 || value.R > 255 || value.G < 0 || value.G > 255 || value.B < 0 || value.B > 255) {\n throw `rgb(${value.R}, ${value.G}, ${value.B}) is not in valid format.`;\n }\n\n this._red = value.R;\n this._green = value.G;\n this._blue = value.B;\n\n const hsl = this._RGBtoHSL(this._red, this._green, this._blue);\n this._hue = hsl.H;\n this._saturation = hsl.S;\n this._lightness = hsl.L;\n }\n\n public get RGB(): RGBObject {\n return {\n R: this._red,\n G: this._green,\n B: this._blue\n };\n }\n\n public set HEX(hex: string) {\n if (!hex.match(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) {\n throw new Error(`Invalid hex color: \"${hex}`);\n }\n\n const rgb = {\n R: parseInt(hex.substring(1, 3), 16),\n G: parseInt(hex.substring(3, 5), 16),\n B: parseInt(hex.substring(5, 7), 16)\n };\n\n this.RGB = rgb;\n }\n\n public get HEX(): string {\n const red = this._red.toString(16).padStart(2, '0');\n const green = this._green.toString(16).padStart(2, '0');\n const blue = this._blue.toString(16).padStart(2, '0');\n\n return `#${red}${green}${blue}`;\n }\n\n private _RGBtoHSL(red: number, green: number, blue: number): HSLObject {\n const r = red / 255;\n const g = green / 255;\n const b = blue / 255;\n\n const cMax = Math.max(r, g, b);\n const cMin = Math.min(r, g, b);\n const delta = cMax - cMin;\n\n const lightness = (cMax + cMin) * 50;\n const saturation = delta === 0 ? 0 : (100 * delta) / (1 - Math.abs(2 * (lightness / 100) - 1));\n\n let hue;\n if (delta === 0) hue = 0;\n else if (cMax === r) hue = 60 * (((g - b) / delta) % 6);\n else if (cMax === g) hue = 60 * ((b - r) / delta + 2);\n else hue = 60 * ((r - g) / delta + 4);\n\n if (hue < 0) hue += 360;\n\n return {\n H: Math.round(hue),\n S: Math.round(saturation),\n L: Math.round(lightness)\n };\n }\n\n private _HSLtoRGB(hue: number, saturation: number, lightness: number): RGBObject {\n const h = hue;\n const s = saturation / 100;\n const l = lightness / 100;\n\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n\n const r = h < 60 || h >= 300 ? c : h < 120 || h >= 240 ? x : 0;\n const g = h >= 240 ? 0 : h < 60 || h >= 180 ? x : c;\n const b = h < 120 ? 0 : h < 180 || h >= 300 ? x : c;\n\n return {\n R: Math.round((r + m) * 255),\n G: Math.round((g + m) * 255),\n B: Math.round((b + m) * 255)\n };\n }\n}\n", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "value", - "type": "HSLObject | RGBObject | string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "line": 9, - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "value", - "type": "HSLObject | RGBObject | string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "properties": [], - "methods": [], - "indexSignatures": [], - "extends": [], - "accessors": { - "HSL": { - "name": "HSL", - "setSignature": { - "name": "HSL", - "type": "void", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "value", - "type": "HSLObject", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "void", - "line": 21, - "jsdoctags": [ - { - "name": "value", - "type": "HSLObject", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "getSignature": { - "name": "HSL", - "type": "", - "returnType": "HSLObject", - "line": 36 - } - }, - "RGB": { - "name": "RGB", - "setSignature": { - "name": "RGB", - "type": "void", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "value", - "type": "RGBObject", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "void", - "line": 44, - "jsdoctags": [ - { - "name": "value", - "type": "RGBObject", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "getSignature": { - "name": "RGB", - "type": "", - "returnType": "RGBObject", - "line": 59 - } - }, - "HEX": { - "name": "HEX", - "setSignature": { - "name": "HEX", - "type": "void", - "deprecated": false, - "deprecationMessage": "", - "args": [ - { - "name": "hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "void", - "line": 67, - "jsdoctags": [ - { - "name": "hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - "getSignature": { - "name": "HEX", - "type": "string", - "returnType": "string", - "line": 81 - } - } - }, - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - }, - { - "name": "VersionServiceMock", - "id": "class-VersionServiceMock-48e8e94506d692f007f368965242f170b3a778567364275688c7e00cf6ba5aaa2de5d7652f55cb355614985bcfe325b5a36a7009088217f39f88532f4cbd075c", - "file": "src/app/shared/data-access/version.service.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "class", - "sourceCode": "import { Injectable, VERSION } from '@angular/core';\nimport packageJson from '../../../../package.json';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class VersionService {\n public readonly angularVersion = VERSION.full;\n public readonly appVersion = packageJson.version;\n\n public constructor() {\n console.info(\n '%cRainbowPalette',\n 'font-weight: bold; font-size: 1.1em; text-decoration: underline; margin-top: 1em;'\n );\n\n const angularVersionLength = this.angularVersion.length;\n const appVersionLength = this.appVersion.length;\n\n const length = Math.max(angularVersionLength, appVersionLength);\n const paddingAngular = ' '.repeat(length - angularVersionLength);\n const paddingApp = ' '.repeat(length - appVersionLength);\n\n console.info(` Angular: ${paddingAngular}${this.angularVersion}\\n App: ${paddingApp}${this.appVersion}`);\n }\n}\n\nexport class VersionServiceMock {\n public readonly angularVersion = '0.0.0';\n public readonly appVersion = '0.0.0';\n}\n", - "properties": [ - { - "name": "angularVersion", - "defaultValue": "'0.0.0'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 29, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "appVersion", - "defaultValue": "'0.0.0'", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "optional": false, - "description": "", - "line": 30, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methods": [], - "indexSignatures": [], - "extends": [], - "inputsClass": [], - "outputsClass": [], - "hostBindings": [], - "hostListeners": [] - } - ], - "directives": [], - "components": [ - { - "name": "AccordionComponent", - "id": "component-AccordionComponent-d244449d2e0c55d251087bdde67947d59fd567e322d11068749d7f61568319e54d42406d9cef4938dfe1041e95fa17ac842e06aae6a54474351671e6ae724c70", - "file": "src/app/shared/ui/accordion/accordion.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-accordion", - "styleUrls": [], - "styles": [ - ":host { display: block; }" - ], - "templateUrl": [ - "./accordion.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "details", - "defaultValue": "input()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

The content of the accordion.\nThis can be a translation key, a string or a SafeHtml object.\nAs an alternative, you can project the content into the component instead.

\n", - "line": 28, - "rawdescription": "\n\nThe content of the accordion.\nThis can be a translation key, a string or a SafeHtml object.\nAs an alternative, you can project the content into the component instead.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "hidden", - "defaultValue": "input(false, { transform: booleanAttribute })", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Whether the accordions content should be hidden initially.\nWhen set to true, the content will not be rendered into the DOM until the accordion is opened.\nThis can be used to hide content like private information from search engines and automated scrapers.

\n", - "line": 34, - "rawdescription": "\n\nWhether the accordions content should be hidden initially.\nWhen set to true, the content will not be rendered into the DOM until the accordion is opened.\nThis can be used to hide content like private information from search engines and automated scrapers.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "summary", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

The summary of the accordion.\nThis can be a translation key or a string.

\n", - "line": 22, - "rawdescription": "\n\nThe summary of the accordion.\nThis can be a translation key or a string.\n", - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [ - { - "name": "open", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 120, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nOpens the accordion.\n", - "description": "

Opens the accordion.

\n", - "modifierKind": [ - 125 - ] - }, - { - "name": "shrink", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 87, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nShrinks the accordion to close it.\n", - "description": "

Shrinks the accordion to close it.

\n", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "toggleAccordion", - "args": [ - { - "name": "$event", - "type": "Event", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 70, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nToggles the accordion open and closed.\n", - "description": "

Toggles the accordion open and closed.

\n", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "$event", - "type": "Event", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, ElementRef, booleanAttribute, input, signal, viewChild } from '@angular/core';\nimport { SafeHtml } from '@angular/platform-browser';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroPlus } from '@ng-icons/heroicons/outline';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { sleep } from '../../utils/sleep';\n\n@Component({\n selector: 'rp-accordion',\n standalone: true,\n imports: [TranslateModule, NgIconComponent],\n templateUrl: './accordion.component.html',\n styles: ':host { display: block; }'\n})\nexport class AccordionComponent {\n protected readonly heroPlus = heroPlus;\n\n /**\n * The summary of the accordion.\n * This can be a translation key or a string.\n */\n public readonly summary = input.required();\n /**\n * The content of the accordion.\n * This can be a translation key, a string or a SafeHtml object.\n * As an alternative, you can project the content into the component instead.\n */\n public readonly details = input();\n /**\n * Whether the accordions content should be hidden initially.\n * When set to true, the content will not be rendered into the DOM until the accordion is opened.\n * This can be used to hide content like private information from search engines and automated scrapers.\n */\n public readonly hidden = input(false, { transform: booleanAttribute });\n\n /**\n * HTML details element reference.\n */\n private readonly _details = viewChild.required>('detailsElement');\n /**\n * HTML summary element reference.\n */\n private readonly _summary = viewChild.required>('summaryElement');\n /**\n * Reference to the content container element.\n */\n private readonly _content = viewChild.required>('contentElement');\n\n /**\n * Whether the accordion is open.\n */\n protected readonly isOpen = signal(false);\n\n /**\n * Native animation object for the accordion.\n */\n private _animation?: Animation;\n /**\n * Whether the accordion is currently closing.\n */\n private _isClosing = false;\n /**\n * Whether the accordion is currently expanding.\n */\n private _isExpanding = false;\n\n /**\n * Toggles the accordion open and closed.\n */\n public toggleAccordion($event: Event): void {\n const htmlElement = $event.target as HTMLElement | null;\n if (htmlElement?.tagName === 'A') {\n return;\n }\n $event.preventDefault();\n\n if (this._isClosing || !this._details().nativeElement.open) {\n this.open();\n } else if (this._isExpanding || this._details().nativeElement.open) {\n this.shrink();\n }\n }\n\n /**\n * Shrinks the accordion to close it.\n */\n public async shrink(): Promise {\n this._isClosing = true;\n\n this._details().nativeElement.style.overflow = 'hidden';\n const startHeight = `${this._details().nativeElement.offsetHeight}px`;\n const endHeight = `calc(${this._summary().nativeElement.offsetHeight}px + 3rem)`;\n\n if (this._animation) {\n this._animation.cancel();\n }\n\n this._animation = this._details().nativeElement.animate(\n {\n height: [startHeight, endHeight]\n },\n {\n duration: 300,\n easing: 'ease-in-out'\n }\n );\n\n this._animation.onfinish = (): void => this._onAnimationFinish(false);\n this._animation.oncancel = (): boolean => (this._isClosing = false);\n\n // Wait for animation to finish before hiding the content\n await sleep(300);\n\n this.isOpen.set(false);\n }\n\n /**\n * Opens the accordion.\n */\n public open(): void {\n this._details().nativeElement.style.overflow = 'hidden';\n this._details().nativeElement.style.height = `${this._details().nativeElement.offsetHeight}px`;\n\n this._details().nativeElement.open = true;\n\n window.requestAnimationFrame(() => this._expand());\n }\n\n /**\n * Expands the accordion to open it.\n */\n private async _expand(): Promise {\n this._isExpanding = true;\n this.isOpen.set(true);\n\n // Wait for the next frame to ensure the content is rendered\n await sleep(1);\n\n const startHeight = `${this._details().nativeElement.offsetHeight}px`;\n const endHeight = `calc(${\n this._summary().nativeElement.offsetHeight + this._content().nativeElement.offsetHeight\n }px + 4rem)`;\n\n if (this._animation) {\n this._animation.cancel();\n }\n\n this._animation = this._details().nativeElement.animate(\n {\n height: [startHeight, endHeight]\n },\n {\n duration: 300,\n easing: 'ease-in-out'\n }\n );\n\n this._animation.onfinish = (): void => this._onAnimationFinish(true);\n this._animation.oncancel = (): boolean => (this._isExpanding = false);\n }\n\n /**\n * Handles the finish of the animation and updates the state of the accordion.\n *\n * @param open Whether the accordion is open.\n */\n private _onAnimationFinish(open: boolean): void {\n this._details().nativeElement.open = open;\n\n this._animation = undefined;\n\n this._isClosing = false;\n this._isExpanding = false;\n\n this._details().nativeElement.style.height = '';\n this._details().nativeElement.style.overflow = '';\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": ":host { display: block; }\n", - "extends": [], - "templateData": "\n \n

\n {{ summary() | translate }}\n

\n\n \n \n \n \n\n \n @if (!hidden() || isOpen()) {\n @if (details()) {\n

\n } @else {\n \n }\n }\n \n\n" - }, - { - "name": "AppComponent", - "id": "component-AppComponent-4d996116fe4367d4c3a0b925b0287d6575fc0219f456946bf295478f663b16df92d40fc37399fc832752125651852dfb5a0635a08e085323be87d5d61b430ada", - "file": "src/app/app.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-root", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./app.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "LayoutComponent", - "type": "component" - }, - { - "name": "LoadingComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, inject } from '@angular/core';\nimport { LayoutComponent } from './layout/layout.component';\nimport { LoadingComponent } from './loading/loading.component';\nimport { VersionService } from './shared/data-access/version.service';\n\n@Component({\n selector: 'rp-root',\n standalone: true,\n imports: [LayoutComponent, LoadingComponent],\n templateUrl: './app.component.html'\n})\nexport class AppComponent {\n // This service gets injected here to print the version number in the console on startup.\n private readonly _versionService = inject(VersionService);\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "@defer {\n \n} @placeholder (minimum 2s) {\n \n}\n" - }, - { - "name": "ButtonComponent", - "id": "component-ButtonComponent-86e2311c54fbca7431c12e070d0fd5b0ebe343eec0553a392f182ba0e04a8f86613233d26ee47718fd0e7a65078f580c76a1fcc928ce55de81193d6618025bc4", - "file": "src/stories/button.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "storybook-button", - "styleUrls": [ - "./button.css" - ], - "styles": [], - "template": "", - "templateUrl": [], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [ - { - "name": "backgroundColor", - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nWhat background color to use\n", - "description": "

What background color to use

\n", - "line": 29, - "type": "string", - "decorators": [] - }, - { - "name": "label", - "defaultValue": "'Button'", - "deprecated": false, - "deprecationMessage": "", - "jsdoctags": [ - { - "pos": 798, - "end": 811, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 334, - "tagName": { - "pos": 799, - "end": 807, - "flags": 16842752, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "required" - }, - "comment": "" - } - ], - "rawdescription": "\n\nButton contents\n\n", - "description": "

Button contents

\n", - "line": 43, - "type": "string", - "decorators": [] - }, - { - "name": "primary", - "defaultValue": "false", - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nIs this the principal call to action on the page?\n", - "description": "

Is this the principal call to action on the page?

\n", - "line": 23, - "type": "boolean", - "decorators": [] - }, - { - "name": "size", - "defaultValue": "'medium'", - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nHow large should the button be?\n", - "description": "

How large should the button be?

\n", - "line": 35, - "type": "\"small\" | \"medium\" | \"large\"", - "decorators": [] - } - ], - "outputsClass": [ - { - "name": "onClick", - "defaultValue": "new EventEmitter()", - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nOptional click handler\n", - "description": "

Optional click handler

\n", - "line": 49, - "type": "EventEmitter" - } - ], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "CommonModule", - "type": "module" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { CommonModule } from '@angular/common';\nimport { Component, Input, Output, EventEmitter } from '@angular/core';\n\n@Component({\n selector: 'storybook-button',\n standalone: true,\n imports: [CommonModule],\n template: ` \n {{ label }}\n `,\n styleUrls: ['./button.css'],\n})\nexport class ButtonComponent {\n /**\n * Is this the principal call to action on the page?\n */\n @Input()\n primary = false;\n\n /**\n * What background color to use\n */\n @Input()\n backgroundColor?: string;\n\n /**\n * How large should the button be?\n */\n @Input()\n size: 'small' | 'medium' | 'large' = 'medium';\n\n /**\n * Button contents\n *\n * @required\n */\n @Input()\n label = 'Button';\n\n /**\n * Optional click handler\n */\n @Output()\n onClick = new EventEmitter();\n\n public get classes(): string[] {\n const mode = this.primary ? 'storybook-button--primary' : 'storybook-button--secondary';\n\n return ['storybook-button', `storybook-button--${this.size}`, mode];\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": [ - { - "data": ".storybook-button {\n font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-weight: 700;\n border: 0;\n border-radius: 3em;\n cursor: pointer;\n display: inline-block;\n line-height: 1;\n}\n.storybook-button--primary {\n color: white;\n background-color: #1ea7fd;\n}\n.storybook-button--secondary {\n color: #333;\n background-color: transparent;\n box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;\n}\n.storybook-button--small {\n font-size: 12px;\n padding: 10px 16px;\n}\n.storybook-button--medium {\n font-size: 14px;\n padding: 11px 20px;\n}\n.storybook-button--large {\n font-size: 16px;\n padding: 12px 24px;\n}\n", - "styleUrl": "./button.css" - } - ], - "stylesData": "", - "extends": [], - "accessors": { - "classes": { - "name": "classes", - "getSignature": { - "name": "classes", - "type": "[]", - "returnType": "string[]", - "line": 51 - } - } - } - }, - { - "name": "ColorInputComponent", - "id": "component-ColorInputComponent-dee86219c04084afd90d531e8e360314f9d63abe0b5de3268e9e6d952ac36570db1e45b8028cc709adcf550df2066b924bc47bdf331363b7644b44ea11d06c96", - "file": "src/app/shared/ui/color-input/color-input.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-color-input", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./color-input.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "hex", - "defaultValue": "model.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Hex color value.

\n", - "line": 27, - "rawdescription": "\n\nHex color value.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "isValid", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Whether the hex color value is valid.

\n", - "line": 31, - "rawdescription": "\n\nWhether the hex color value is valid.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "placeholder", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Placeholder to display in the input field.

\n", - "line": 19, - "rawdescription": "\n\nPlaceholder to display in the input field.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "tooltip", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Tooltip to display on the eye dropper icon.

\n", - "line": 23, - "rawdescription": "\n\nTooltip to display on the eye dropper icon.\n", - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, ElementRef, computed, effect, input, model, output, viewChild } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroEyeDropperMini } from '@ng-icons/heroicons/mini';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { perceivedBrightnessFromHex } from '../../utils/perceived-brightness';\n\n@Component({\n selector: 'rp-color-input',\n standalone: true,\n imports: [TranslateModule, NgIconComponent],\n templateUrl: './color-input.component.html'\n})\nexport class ColorInputComponent {\n protected readonly heroEyeDropperMini = heroEyeDropperMini;\n\n /**\n * Placeholder to display in the input field.\n */\n public readonly placeholder = input.required();\n /**\n * Tooltip to display on the eye dropper icon.\n */\n public readonly tooltip = input.required();\n /**\n * Hex color value.\n */\n public readonly hex = model.required();\n /**\n * Whether the hex color value is valid.\n */\n public readonly isValid = output();\n\n /**\n * Reference to the hex input element.\n */\n private readonly _hexInput = viewChild.required>('hexInput');\n\n /**\n * Whether the color is light.\n */\n protected readonly isColorLight = computed(() => {\n return perceivedBrightnessFromHex(this.hex()) > 51;\n });\n\n public constructor() {\n effect(() => {\n this._hexInput().nativeElement.value = this.hex();\n });\n }\n\n /**\n * Update the hex color value.\n *\n * @param hex Value to update the hex color to.\n */\n protected updateHex(hex: string): void {\n // Remove leading / trailing whitespace and non-hex characters\n hex = hex.trim().replace(/[^0-9a-fA-F]/g, '');\n\n // Add leading hash if missing\n if (!hex.startsWith('#')) {\n hex = `#${hex}`;\n }\n\n // Normalize to uppercase and remove extra characters\n hex = hex.toUpperCase().slice(0, 7);\n\n // Set color if it is a valid hex color\n if (hex.length === 4 || hex.length === 7) {\n this.hex.set(hex);\n this.isValid.emit(true);\n } else {\n this.isValid.emit(false);\n }\n\n // Update input value\n this._hexInput().nativeElement.value = hex;\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 43, - "modifierKind": [ - 125 - ] - }, - "extends": [], - "templateData": "
\n \n \n \n \n
\n\n \n {{ placeholder() }}\n \n \n\n \n\n \n \n\n" - }, - { - "name": "DropdownMenuComponent", - "id": "component-DropdownMenuComponent-c3aeadba5635e5fcd79b90985fdc68795a09e0bbfee504936014c7f1f81873f49e9d219ad6c3900273b7a002b4c6d1aa04d180eb9855c3449710d5615b00d523", - "file": "src/app/shared/ui/dropdown-menu/dropdown-menu.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-dropdown-menu", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./dropdown-menu.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "closeOnScroll", - "defaultValue": "input(true, {\n transform: booleanAttribute\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Whether the dropdown menu should close when the user scrolls outside of the menu.

\n", - "line": 43, - "rawdescription": "\n\nWhether the dropdown menu should close when the user scrolls outside of the menu.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "disabled", - "defaultValue": "input(false, {\n transform: booleanAttribute\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

If the dropdown menu is disabled it will not open when the user clicks on the trigger.

\n", - "line": 37, - "rawdescription": "\n\nIf the dropdown menu is disabled it will not open when the user clicks on the trigger.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "highlightSelection", - "defaultValue": "input(false, {\n transform: booleanAttribute\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Whether the selected item should be highlighted in the dropdown menu.

\n", - "line": 49, - "rawdescription": "\n\nWhether the selected item should be highlighted in the dropdown menu.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "items", - "defaultValue": "input.required>()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

The items that are displayed in the dropdown menu.

\n", - "line": 29, - "rawdescription": "\n\nThe items that are displayed in the dropdown menu.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "itemTemplate", - "defaultValue": "contentChild>('itemTemplate')", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

The template that is used to render the items in the dropdown menu.\nYou can use this to customize the appearance of the items.\nThe template context contains the item that is currently being rendered.

\n", - "line": 84, - "rawdescription": "\n\nThe template that is used to render the items in the dropdown menu.\nYou can use this to customize the appearance of the items.\nThe template context contains the item that is currently being rendered.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "maxHeight", - "defaultValue": "input('16rem')", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Maximum height of the dropdown menu.\nThis does not include the height of the title but only the menu items.\nIf the height of the menu items exceeds this value, the menu will be scrollable.

\n", - "line": 70, - "rawdescription": "\n\nMaximum height of the dropdown menu.\nThis does not include the height of the title but only the menu items.\nIf the height of the menu items exceeds this value, the menu will be scrollable.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "maxWidth", - "defaultValue": "input('40rem')", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Maximum width of the dropdown menu.

\n", - "line": 59, - "rawdescription": "\n\nMaximum width of the dropdown menu.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "minHeight", - "defaultValue": "input()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Minimum height of the dropdown menu.\nThis does not include the height of the title but only the menu items.

\n", - "line": 64, - "rawdescription": "\n\nMinimum height of the dropdown menu.\nThis does not include the height of the title but only the menu items.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "minWidth", - "defaultValue": "input('12rem')", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Minimum width of the dropdown menu.

\n", - "line": 55, - "rawdescription": "\n\nMinimum width of the dropdown menu.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "selectedItem", - "defaultValue": "model(undefined)", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

The currently selected item in the dropdown menu.\nThis can be pre-selected by setting this input to the value of the item.\nThis will also be updated when the user selects an item in the dropdown menu.

\n", - "line": 77, - "rawdescription": "\n\nThe currently selected item in the dropdown menu.\nThis can be pre-selected by setting this input to the value of the item.\nThis will also be updated when the user selects an item in the dropdown menu.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "title", - "defaultValue": "input()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Title on top of the dropdown menu.

\n", - "line": 33, - "rawdescription": "\n\nTitle on top of the dropdown menu.\n", - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "CdkMenuTrigger" - }, - { - "name": "CdkMenu" - }, - { - "name": "CdkMenuGroup" - }, - { - "name": "CdkMenuItemRadio" - }, - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "CommonModule", - "type": "module" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { CdkMenu, CdkMenuGroup, CdkMenuItemRadio, CdkMenuTrigger } from '@angular/cdk/menu';\nimport { ConnectedPosition } from '@angular/cdk/overlay';\nimport { CommonModule } from '@angular/common';\nimport {\n Component,\n ElementRef,\n TemplateRef,\n booleanAttribute,\n contentChild,\n effect,\n input,\n model,\n signal,\n viewChild\n} from '@angular/core';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { Subscription } from 'rxjs';\n\n@Component({\n selector: 'rp-dropdown-menu',\n standalone: true,\n imports: [CdkMenuTrigger, CdkMenu, CdkMenuGroup, CdkMenuItemRadio, TranslateModule, CommonModule],\n templateUrl: './dropdown-menu.component.html'\n})\nexport class DropdownMenuComponent {\n /**\n * The items that are displayed in the dropdown menu.\n */\n public readonly items = input.required>();\n /**\n * Title on top of the dropdown menu.\n */\n public readonly title = input();\n /**\n * If the dropdown menu is disabled it will not open when the user clicks on the trigger.\n */\n public readonly disabled = input(false, {\n transform: booleanAttribute\n });\n /**\n * Whether the dropdown menu should close when the user scrolls outside of the menu.\n */\n public readonly closeOnScroll = input(true, {\n transform: booleanAttribute\n });\n /**\n * Whether the selected item should be highlighted in the dropdown menu.\n */\n public readonly highlightSelection = input(false, {\n transform: booleanAttribute\n });\n /**\n * Minimum width of the dropdown menu.\n */\n public readonly minWidth = input('12rem');\n /**\n * Maximum width of the dropdown menu.\n */\n public readonly maxWidth = input('40rem');\n /**\n * Minimum height of the dropdown menu.\n * This does not include the height of the title but only the menu items.\n */\n public readonly minHeight = input();\n /**\n * Maximum height of the dropdown menu.\n * This does not include the height of the title but only the menu items.\n * If the height of the menu items exceeds this value, the menu will be scrollable.\n */\n public readonly maxHeight = input('16rem');\n\n /**\n * The currently selected item in the dropdown menu.\n * This can be pre-selected by setting this input to the value of the item.\n * This will also be updated when the user selects an item in the dropdown menu.\n */\n public readonly selectedItem = model(undefined);\n\n /**\n * The template that is used to render the items in the dropdown menu.\n * You can use this to customize the appearance of the items.\n * The template context contains the item that is currently being rendered.\n */\n public readonly itemTemplate = contentChild>('itemTemplate');\n\n // View Child Signals\n private readonly _trigger = viewChild(CdkMenuTrigger);\n private readonly _menu = viewChild>('menuGroup');\n\n // Internal Signals and Properties\n private readonly _isOpen = signal(false);\n private readonly _abortControllers: Array = [];\n private _openedSubscription?: Subscription;\n private _closedSubscription?: Subscription;\n\n protected readonly menuPositions: Array = [\n {\n originX: 'end',\n originY: 'bottom',\n overlayX: 'end',\n overlayY: 'top',\n offsetY: 8\n },\n {\n originX: 'start',\n originY: 'bottom',\n overlayX: 'start',\n overlayY: 'top',\n offsetY: 8\n },\n {\n originX: 'end',\n originY: 'top',\n overlayX: 'end',\n overlayY: 'bottom',\n offsetY: -8\n },\n {\n originX: 'start',\n originY: 'top',\n overlayX: 'start',\n overlayY: 'bottom',\n offsetY: -8\n }\n ];\n\n public constructor() {\n /**\n * Effect to handle the opened and closed subscriptions of the trigger.\n * These subscriptions are used to update the isOpen signal when the\n * menu is opened or closed.\n */\n effect(() => {\n if (this._openedSubscription) {\n this._openedSubscription.unsubscribe();\n }\n if (this._closedSubscription) {\n this._closedSubscription.unsubscribe();\n }\n\n if (this._trigger()) {\n this._openedSubscription = this._trigger()!.opened.subscribe(() => {\n this._isOpen.set(true);\n });\n this._closedSubscription = this._trigger()!.closed.subscribe(() => {\n this._isOpen.set(false);\n });\n }\n });\n\n /**\n * Effect to handle closing the menu when the user scrolls outside of the menu.\n * This effect will add an event listener to the window scroll event when the\n * menu is open (and {@link closeOnScroll} is true).\n * These event listeners are also automatically removed when the menu is closed again.\n */\n effect(() => {\n if (this._isOpen()) {\n if (!this.closeOnScroll()) {\n return;\n }\n\n const abortController = new AbortController();\n this._abortControllers.push(abortController);\n\n window.addEventListener(\n 'scroll',\n (event: Event) => {\n if (event.target === this._menu()?.nativeElement) {\n return;\n }\n\n this._trigger()?.close();\n },\n {\n capture: true,\n signal: abortController.signal\n }\n );\n } else {\n while (this._abortControllers.length > 0) {\n this._abortControllers.shift()?.abort();\n }\n }\n });\n }\n\n protected select(item: T): void {\n this.selectedItem.set(item);\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 125, - "modifierKind": [ - 125 - ] - }, - "extends": [], - "templateData": "\n \n\n\n\n \n @if (title()) {\n
\n

\n {{ title()! | translate }}\n

\n
\n }\n\n \n @for (item of items(); track $index) {\n \n \n \n }\n \n \n
\n\n\n {{ item | translate }}\n\n" - }, - { - "name": "EditorComponent", - "id": "component-EditorComponent-f260c205f451722dc0e6e0cb1c12495dd7060f0e6b1268fc718f1eb209b4ee5a68bbf7431f9ee9d68b9e91d4ae4ed32b3bc5b71fb8bb6dac5506c5ee8ed0a0e3", - "file": "src/app/editor/editor.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-editor", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./editor.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [ - { - "name": "unfixShade", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "$event", - "type": "MouseEvent", - "deprecated": false, - "deprecationMessage": "", - "optional": true - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 64, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "$event", - "type": "MouseEvent", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "update", - "args": [ - { - "name": "type", - "type": "UpdateType", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "value", - "type": "string | number", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 84, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "type", - "type": "UpdateType", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "value", - "type": "string | number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "ColorInputComponent", - "type": "component" - }, - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "DecimalPipe", - "type": "pipe" - }, - { - "name": "EditorRangeComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';\nimport { DecimalPipe } from '@angular/common';\nimport { Component, ElementRef, computed, effect, inject, signal, viewChild } from '@angular/core';\nimport { TranslateModule, TranslateService } from '@ngx-translate/core';\nimport { ColorService } from '../shared/data-access/color.service';\nimport { Color, Shade } from '../shared/model';\nimport { ColorInputComponent } from '../shared/ui/color-input/color-input.component';\nimport { textColor } from '../shared/utils/text-color';\nimport { EditorRangeComponent } from './ui/editor-range/editor-range.component';\n\nexport enum UpdateType {\n HEX = 'hex',\n HUE = 'hue',\n SATURATION = 'saturation',\n LIGHTNESS = 'lightness'\n}\n\n@Component({\n selector: 'rp-editor',\n standalone: true,\n imports: [ColorInputComponent, TranslateModule, DecimalPipe, EditorRangeComponent],\n templateUrl: './editor.component.html'\n})\nexport class EditorComponent {\n protected readonly UpdateType = UpdateType;\n protected readonly textColor = textColor;\n\n private readonly _data = inject<{ color: Color; shadeIndex?: number }>(DIALOG_DATA);\n private readonly _dialogRef = inject(DialogRef);\n private readonly _colorService = inject(ColorService);\n private readonly _translateService = inject(TranslateService);\n\n protected readonly color = signal(this._data.color.copy());\n protected readonly shadeIndex = signal(this._data.shadeIndex ?? 0);\n\n protected readonly shade = computed(() => {\n const selectedShade = this.color().shades.find((shade) => shade.index === this.shadeIndex());\n if (selectedShade) {\n return selectedShade;\n }\n\n const fixedShade = this.color().shades.find((shade) => shade.fixed);\n if (fixedShade) {\n return fixedShade;\n }\n\n return this.color().shades[0];\n });\n\n private readonly _editor = viewChild.required>('editor');\n\n public constructor() {\n effect(() => {\n this._editor().nativeElement.style.setProperty('--editor-hue', `${this.shade().hsl.H}`);\n this._editor().nativeElement.style.setProperty('--editor-saturation', `${this.shade().hsl.S}%`);\n this._editor().nativeElement.style.setProperty('--editor-lightness', `${this.shade().hsl.L}%`);\n });\n }\n\n protected changeShade(index: number): void {\n this.shadeIndex.set(index);\n }\n\n public unfixShade(shade: Shade, $event?: MouseEvent): void {\n if ($event) {\n $event.stopPropagation();\n $event.preventDefault();\n }\n\n if (!shade.fixed) {\n return;\n }\n\n const atLeastOneFixedLeft = this.color().shades.some((s) => s.fixed && s !== shade);\n\n if (!atLeastOneFixedLeft) {\n return;\n }\n\n shade.fixed = false;\n this._updateColor();\n }\n\n public update(type: UpdateType, value: string | number): void {\n const shade = this.shade();\n shade.fixed = true;\n\n switch (type) {\n case UpdateType.HEX:\n shade.hex = value as string;\n break;\n case UpdateType.HUE:\n shade.hsl = { ...shade.hsl, H: value as number };\n break;\n case UpdateType.SATURATION:\n shade.hsl = { ...shade.hsl, S: value as number };\n break;\n case UpdateType.LIGHTNESS:\n shade.hsl = { ...shade.hsl, L: value as number };\n break;\n }\n\n this._updateColor();\n\n const editedShade = this.color().shades.find((s) => s.hex === shade.hex);\n this.shadeIndex.set(editedShade?.index ?? -1);\n }\n\n protected cancel(): void {\n this._dialogRef.close(undefined);\n this.color.set(this._data.color.copy());\n }\n\n protected save(): void {\n this._dialogRef.close(this.color());\n }\n\n private _updateColor(): void {\n const updatedColor = this.color().copy();\n this._colorService.regenerateShades(updatedColor);\n this.color.set(updatedColor);\n }\n\n protected getTooltip(shade: Shade, selected: boolean): string {\n const tooltips: Array = [];\n\n if (!selected) {\n tooltips.push(this._translateService.instant('editor.shades'));\n }\n\n if (shade.fixed) {\n tooltips.push(this._translateService.instant('editor.unfix'));\n }\n\n return tooltips.join('\\n');\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 50, - "modifierKind": [ - 125 - ] - }, - "extends": [], - "templateData": "\n

{{ color().name }}

\n\n \n \n\n
\n \n\n \n\n \n
\n\n \n @for (shadeInRow of color().shades; track shadeInRow.hex) {\n \n {{ shadeInRow.fixed ? 'â– ' : '' }}\n \n }\n \n\n
\n \n {{ 'common.cancel' | translate }}\n \n\n \n {{ 'common.save' | translate }}\n \n
\n\n" - }, - { - "name": "EditorRangeComponent", - "id": "component-EditorRangeComponent-b906548526d7449ba81cc3a6f66a478ddf4ed4088efa6cd75e628122ce3073443d6bd1ec3bd6a412ed4daeb5e49c573f409245ea59fde03f9215bdedd29e377e", - "file": "src/app/editor/ui/editor-range/editor-range.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-editor-range", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./editor-range.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "key", - "defaultValue": "input.required<'hue' | 'saturation' | 'lightness'>()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Key to identify the type of value being adjusted

\n", - "line": 24, - "rawdescription": "\n\nKey to identify the type of value being adjusted\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "label", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Label to display above the range input

\n", - "line": 16, - "rawdescription": "\n\nLabel to display above the range input\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "max", - "defaultValue": "input(100, {\n transform: numberAttribute\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Maximum value of the range input

\n", - "line": 38, - "rawdescription": "\n\nMaximum value of the range input\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "min", - "defaultValue": "input(0, {\n transform: numberAttribute\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Minimum value of the range input

\n", - "line": 32, - "rawdescription": "\n\nMinimum value of the range input\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "tooltip", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Tooltip to display when hovering over the range input

\n", - "line": 20, - "rawdescription": "\n\nTooltip to display when hovering over the range input\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "value", - "defaultValue": "model.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Current value of the range input

\n", - "line": 28, - "rawdescription": "\n\nCurrent value of the range input\n", - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "DecimalPipe", - "type": "pipe" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { DecimalPipe } from '@angular/common';\nimport { Component, computed, input, model, numberAttribute } from '@angular/core';\nimport { hueToWheel, wheelToHue } from '../../utils/color-wheel';\n\n@Component({\n selector: 'rp-editor-range',\n standalone: true,\n imports: [DecimalPipe],\n templateUrl: './editor-range.component.html',\n styleUrl: './editor-range.component.css'\n})\nexport class EditorRangeComponent {\n /**\n * Label to display above the range input\n */\n public readonly label = input.required();\n /**\n * Tooltip to display when hovering over the range input\n */\n public readonly tooltip = input.required();\n /**\n * Key to identify the type of value being adjusted\n */\n public readonly key = input.required<'hue' | 'saturation' | 'lightness'>();\n /**\n * Current value of the range input\n */\n public readonly value = model.required();\n /**\n * Minimum value of the range input\n */\n public readonly min = input(0, {\n transform: numberAttribute\n });\n /**\n * Maximum value of the range input\n */\n public readonly max = input(100, {\n transform: numberAttribute\n });\n\n protected readonly transformedValue = computed(() => {\n if (this.key() === 'hue') {\n return hueToWheel(this.value());\n } else if (this.key() === 'lightness') {\n return 100 - this.value();\n } else {\n return this.value();\n }\n });\n\n protected updateValue(value: number | string): void {\n if (typeof value === 'string') {\n value = parseFloat(value);\n }\n\n if (this.key() === 'hue') {\n this.value.set(wheelToHue(value));\n } else if (this.key() === 'lightness') {\n this.value.set(100 - value);\n } else {\n this.value.set(value);\n }\n }\n}\n", - "styleUrl": "./editor-range.component.css", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "
\n \n \n {{ label() }}\n \n\n \n {{ value() | number: '1.0-0' }}\n \n \n\n \n
\n" - }, - { - "name": "ExportDownloadComponent", - "id": "component-ExportDownloadComponent-480f4dfba610aa341615352457ef57d24817e55b9f7f974eca62ced0a6b3cedce6d3932331b21f136e6337ee6d546a4828a5776db067804aac9c3e9db0c7a854", - "file": "src/app/export/ui/export-download/export-download.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-export-download", - "styleUrls": [], - "styles": [ - "\n :host {\n display: block;\n }\n " - ], - "templateUrl": [ - "./export-download.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "choseDownloadFormat", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 22, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "exportFormat", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 20, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, input, output } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroArrowDownTray, heroDocumentDuplicate } from '@ng-icons/heroicons/outline';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { ExportFormat } from '../../../shared/constants/export-format';\nimport { ExportOption } from '../../../shared/types/export-option';\n\n@Component({\n selector: 'rp-export-download',\n standalone: true,\n imports: [TranslateModule, NgIconComponent],\n templateUrl: './export-download.component.html',\n styles: `\n :host {\n display: block;\n }\n `\n})\nexport class ExportDownloadComponent {\n public readonly exportFormat = input.required();\n\n public readonly choseDownloadFormat = output();\n\n protected readonly heroArrowDownTray = heroArrowDownTray;\n protected readonly heroDocumentDuplicate = heroDocumentDuplicate;\n\n protected download(): void {\n this.choseDownloadFormat.emit('file');\n }\n\n protected copy(): void {\n this.choseDownloadFormat.emit('copy');\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "\n :host {\n display: block;\n }\n \n", - "extends": [], - "templateData": "
\n

\n {{ 'export.download.title' | translate }}\n

\n\n

\n {{ 'export.download.description.' + exportFormat() | translate }}\n

\n\n \n \n \n \n \n {{ 'export.download.copy' | translate }}\n \n\n \n \n \n \n {{ 'export.download.file' | translate }}\n \n \n
\n" - }, - { - "name": "ExportFormatComponent", - "id": "component-ExportFormatComponent-2f4e95371e23f14ce8dfc5e75195e677fc60d8c7200231be0cfea3291964552298938ecbc5d87a636a46bc16e42a1d946c73609b0dddacf9ca2c77a397fadb22", - "file": "src/app/export/ui/export-format/export-format.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-export-format", - "styleUrls": [], - "styles": [ - ":host { display: block; }" - ], - "templateUrl": [ - "./export-format.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "choseExportFormat", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 14, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, output } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { EXPORT_FORMATS, ExportFormat } from '../../../shared/constants/export-format';\n\n@Component({\n selector: 'rp-export-format',\n standalone: true,\n imports: [TranslateModule, NgIconComponent],\n templateUrl: './export-format.component.html',\n styles: ':host { display: block; }'\n})\nexport class ExportFormatComponent {\n public readonly choseExportFormat = output();\n\n protected readonly formats = EXPORT_FORMATS;\n\n protected choseFormat(format: ExportFormat): void {\n this.choseExportFormat.emit(format);\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": ":host { display: block; }\n", - "extends": [], - "templateData": "
\n

\n {{ 'export.format.title' | translate }}\n

\n\n

\n {{ 'export.format.description' | translate }}\n

\n\n \n @for (format of formats; track format.format) {\n \n \n \n \n {{ format.label | translate }}\n \n }\n \n
\n" - }, - { - "name": "ExportModalComponent", - "id": "component-ExportModalComponent-0c5f0935a9a7996fbde0b492ad62f6df1bfe839f5702053940bec368d2d9431e03c7c970491ce6c0628cba33177b83843605bb86eff7b79d769c3ed15c9a6603", - "file": "src/app/export/export-modal.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-export-modal", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./export-modal.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "ExportFormatComponent", - "type": "component" - }, - { - "name": "ExportDownloadComponent", - "type": "component" - }, - { - "name": "ExportSuccessComponent", - "type": "component" - }, - { - "name": "RequestFormatComponent", - "type": "component" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';\nimport { Component, inject, signal } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroArrowLeftMini, heroXMarkMini } from '@ng-icons/heroicons/mini';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { ExportFormat } from '../shared/constants/export-format';\nimport { AnalyticsService } from '../shared/data-access/analytics.service';\nimport { ExportService } from '../shared/data-access/export.service';\nimport { ToastService } from '../shared/data-access/toast.service';\nimport { TrackingEventAction, TrackingEventCategory } from '../shared/enums/tracking-event';\nimport { Palette } from '../shared/model';\nimport { ExportOption } from '../shared/types/export-option';\nimport { ExportDownloadComponent } from './ui/export-download/export-download.component';\nimport { ExportFormatComponent } from './ui/export-format/export-format.component';\nimport { ExportSuccessComponent } from './ui/export-success/export-success.component';\nimport { RequestFormatComponent } from './ui/request-format/request-format.component';\n\nenum ExportModalState {\n FORMAT = 'format',\n DOWNLOAD = 'download',\n SUCCESS = 'success'\n}\n\n@Component({\n selector: 'rp-export-modal',\n standalone: true,\n imports: [\n TranslateModule,\n ExportFormatComponent,\n ExportDownloadComponent,\n ExportSuccessComponent,\n RequestFormatComponent,\n NgIconComponent\n ],\n templateUrl: './export-modal.component.html'\n})\nexport class ExportModalComponent {\n protected readonly ExportModalState = ExportModalState;\n protected readonly ExportFormat = ExportFormat;\n\n private readonly _data = inject<{ palette: Palette }>(DIALOG_DATA);\n private readonly _dialogRef = inject(DialogRef);\n private readonly _toastService = inject(ToastService);\n private readonly _exportService = inject(ExportService);\n private readonly _analyticsService = inject(AnalyticsService);\n\n protected readonly palette = signal(this._data.palette);\n protected readonly state = signal(ExportModalState.FORMAT);\n protected readonly format = signal(undefined);\n protected readonly downloadOption = signal(undefined);\n\n protected readonly heroXMarkMini = heroXMarkMini;\n protected readonly heroArrowLeftMini = heroArrowLeftMini;\n\n protected choseFormat(format: ExportFormat): void {\n this.format.set(format);\n this.state.set(ExportModalState.DOWNLOAD);\n\n if (format === ExportFormat.OTHER) {\n this._analyticsService.trackEvent(\n TrackingEventCategory.EXPORT_PALETTE,\n TrackingEventAction.EXPORT_PALETTE_REQUEST_FORMAT\n );\n }\n }\n\n protected async choseDownloadFormat(action: ExportOption): Promise {\n this.downloadOption.set(action);\n\n const format = this.format();\n if (!format) {\n this._toastService.showToast({\n type: 'error',\n message: 'export.error.format-not-found'\n });\n this.close();\n return;\n }\n\n const success = await this._exportService.exportPalette(this.palette(), format, action);\n\n if (success) {\n this.state.set(ExportModalState.SUCCESS);\n } else {\n this.close();\n }\n }\n\n protected back(): void {\n switch (this.state()) {\n case ExportModalState.DOWNLOAD:\n this.state.set(ExportModalState.FORMAT);\n break;\n case ExportModalState.SUCCESS:\n this.state.set(ExportModalState.DOWNLOAD);\n break;\n }\n }\n\n protected close(): void {\n this._dialogRef.close();\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "\n
\n @if (state() !== ExportModalState.FORMAT) {\n \n \n\n \n {{ 'common.back' | translate }}\n \n \n }\n\n \n \n\n \n {{ 'common.back' | translate }}\n \n \n
\n\n @switch (state()) {\n @case (ExportModalState.FORMAT) {\n \n }\n\n @case (ExportModalState.DOWNLOAD) {\n @if (format(); as format) {\n @if (format === ExportFormat.OTHER) {\n \n } @else {\n \n }\n }\n }\n\n @case (ExportModalState.SUCCESS) {\n @if (format(); as format) {\n @if (downloadOption(); as downloadOption) {\n \n }\n }\n }\n }\n\n" - }, - { - "name": "ExportSuccessComponent", - "id": "component-ExportSuccessComponent-d9d5dca3ae15a241844978c53528449e4f6737d3dabb64f459c28ec3a57c7c7a2f34bc39e3120be0037426ea4bffb0ab7413e88e6259fcf4514f62123a581c34", - "file": "src/app/export/ui/export-success/export-success.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-export-success", - "styleUrls": [], - "styles": [ - "\n :host {\n display: block;\n }\n " - ], - "templateUrl": [ - "./export-success.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "exportFormat", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 20, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "exportOption", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 21, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "openDocumentation", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 23, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, computed, input, output } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { string_to_unicode_variant as toUnicodeVariant } from 'string-to-unicode-variant';\nimport { ExportFormat } from '../../../shared/constants/export-format';\nimport { ExportOption } from '../../../shared/types/export-option';\n\n@Component({\n selector: 'rp-export-success',\n standalone: true,\n imports: [TranslateModule, NgIconComponent],\n templateUrl: './export-success.component.html',\n styles: `\n :host {\n display: block;\n }\n `\n})\nexport class ExportSuccessComponent {\n public readonly exportFormat = input.required();\n public readonly exportOption = input.required();\n\n public readonly openDocumentation = output();\n\n protected readonly description = computed(() => {\n return `export.success.description.${this.exportFormat()}.${this.exportOption()}`;\n });\n\n protected readonly descriptionParams = computed<{\n config?: string;\n docs: string;\n file?: string;\n link?: string;\n root?: string;\n usage?: string;\n }>(() => {\n const option = this.exportOption();\n\n if (option === 'copy') {\n switch (this.exportFormat()) {\n case ExportFormat.CSS:\n return {\n docs: 'https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties#using_the_root_pseudo-class',\n root: toUnicodeVariant(':root', 'm'),\n usage: toUnicodeVariant('color: var(--color-100);', 'm')\n };\n case ExportFormat.SCSS:\n return {\n docs: 'https://sass-lang.com/documentation/variables',\n usage: toUnicodeVariant('color: $color-500;', 'm')\n };\n case ExportFormat.LESS:\n return {\n docs: 'https://lesscss.org/features/#variables-feature-overview',\n usage: toUnicodeVariant('color: @color-500;', 'm')\n };\n case ExportFormat.TAILWIND:\n return {\n config: toUnicodeVariant('tailwind.config.js', 'm'),\n docs: 'https://tailwindcss.com/docs/customizing-colors#color-object-syntax'\n };\n default:\n return { docs: '' };\n }\n } else {\n switch (this.exportFormat()) {\n case ExportFormat.CSS:\n return {\n docs: 'https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties#using_the_root_pseudo-class',\n file: toUnicodeVariant('colors.css', 'm'),\n link: toUnicodeVariant('', 'm'),\n usage: toUnicodeVariant('color: var(--color-100);', 'm')\n };\n case ExportFormat.SCSS:\n return {\n docs: 'https://sass-lang.com/documentation/at-rules/use',\n file: toUnicodeVariant('_colors.scss', 'm'),\n link: toUnicodeVariant(\"@use 'colors'\", 'm'),\n usage: toUnicodeVariant('color: $color-500;', 'm')\n };\n case ExportFormat.LESS:\n return {\n docs: 'https://lesscss.org/features/#import-atrules-feature',\n file: toUnicodeVariant('colors.less', 'm'),\n link: toUnicodeVariant(\"@import 'colors.less'\", 'm'),\n usage: toUnicodeVariant('color: @color-500;', 'm')\n };\n case ExportFormat.TAILWIND:\n return {\n config: toUnicodeVariant('tailwind.config.js', 'm'),\n docs: 'https://tailwindcss.com/docs/customizing-colors#using-the-default-colors',\n file: toUnicodeVariant('tailwind.colors.js', 'm'),\n import: toUnicodeVariant(\"colors: require('./tailwind.colors'),\", 'm')\n };\n default:\n return { docs: '' };\n }\n }\n });\n\n protected openDocs(): void {\n this.openDocumentation.emit();\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "\n :host {\n display: block;\n }\n \n", - "extends": [], - "templateData": "
\n

\n {{ 'export.success.title' | translate }}\n

\n\n

\n\n
\n \n {{ 'export.success.documentation' | translate }}\n \n
\n
\n" - }, - { - "name": "HeaderComponent", - "id": "component-HeaderComponent-cad94b233ec8e80ceb541e7972c5f9737e08237b4e137112f3987ca48ee0cce5c0d57798d41daa5d4d24d6bdbb2df1606ab68cca2aa0a1785e38f99f3cac0729", - "file": "src/stories/header.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "storybook-header", - "styleUrls": [ - "./header.css" - ], - "styles": [], - "template": "
\n
\n \n \n \n \n \n \n \n

Acme

\n
\n
\n
\n \n Welcome, {{ user.name }}!\n \n \n
\n
\n \n \n
\n
\n
\n
", - "templateUrl": [], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [ - { - "name": "user", - "defaultValue": "null", - "deprecated": false, - "deprecationMessage": "", - "line": 69, - "type": "User | null", - "decorators": [] - } - ], - "outputsClass": [ - { - "name": "onCreateAccount", - "defaultValue": "new EventEmitter()", - "deprecated": false, - "deprecationMessage": "", - "line": 78, - "type": "EventEmitter" - }, - { - "name": "onLogin", - "defaultValue": "new EventEmitter()", - "deprecated": false, - "deprecationMessage": "", - "line": 72, - "type": "EventEmitter" - }, - { - "name": "onLogout", - "defaultValue": "new EventEmitter()", - "deprecated": false, - "deprecationMessage": "", - "line": 75, - "type": "EventEmitter" - } - ], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "CommonModule", - "type": "module" - }, - { - "name": "ButtonComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { ButtonComponent } from './button.component';\nimport type { User } from './user';\n\n@Component({\n selector: 'storybook-header',\n standalone: true,\n imports: [CommonModule, ButtonComponent],\n template: `
\n
\n
\n \n \n \n \n \n \n \n

Acme

\n
\n
\n
\n \n Welcome, {{ user.name }}!\n \n \n
\n
\n \n \n
\n
\n
\n
`,\n styleUrls: ['./header.css'],\n})\nexport class HeaderComponent {\n @Input()\n user: User | null = null;\n\n @Output()\n onLogin = new EventEmitter();\n\n @Output()\n onLogout = new EventEmitter();\n\n @Output()\n onCreateAccount = new EventEmitter();\n}\n", - "assetsDirs": [], - "styleUrlsData": [ - { - "data": ".storybook-header {\n font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n padding: 15px 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n}\n\n.storybook-header svg {\n display: inline-block;\n vertical-align: top;\n}\n\n.storybook-header h1 {\n font-weight: 700;\n font-size: 20px;\n line-height: 1;\n margin: 6px 0 6px 10px;\n display: inline-block;\n vertical-align: top;\n}\n\n.storybook-header button + button {\n margin-left: 10px;\n}\n\n.storybook-header .welcome {\n color: #333;\n font-size: 14px;\n margin-right: 10px;\n}\n", - "styleUrl": "./header.css" - } - ], - "stylesData": "", - "extends": [] - }, - { - "name": "HomeComponent", - "id": "component-HomeComponent-0cbdf2db057c2451121c2a4557c5fc1febe4aa156a694000ec2fdfc6db952b137e1ba9e3adbc5beb5e847deef1787c885dcab756e9afc8e49a707c5a38bd3f48", - "file": "src/app/home/home.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-home", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./home.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [ - { - "name": "generatePalette", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 25, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "HomeGeneratorComponent", - "type": "component" - }, - { - "name": "HomeManualComponent", - "type": "component" - }, - { - "name": "HomeSupportComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, inject } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { AnalyticsService } from '../shared/data-access/analytics.service';\nimport { PaletteService } from '../shared/data-access/palette.service';\nimport { HomeService } from './data-access/home.service';\nimport { HomeGeneratorComponent } from './ui/home-generator/home-generator.component';\nimport { HomeManualComponent } from './ui/home-manual/home-manual.component';\nimport { HomeSupportComponent } from './ui/home-support/home-support.component';\n\n@Component({\n selector: 'rp-home',\n standalone: true,\n imports: [HomeGeneratorComponent, HomeManualComponent, HomeSupportComponent],\n templateUrl: './home.component.html'\n})\nexport default class HomeComponent {\n private readonly _homeService = inject(HomeService);\n private readonly _paletteService = inject(PaletteService);\n private readonly _analyticsService = inject(AnalyticsService);\n private readonly _router = inject(Router);\n\n protected readonly hex = this._homeService.hex;\n protected readonly scheme = this._homeService.scheme;\n\n public async generatePalette(): Promise {\n this._homeService.saveGenerationSettings();\n\n this._paletteService.generatePalette(this.hex(), this.scheme());\n this._analyticsService.trackPaletteGeneration(this.scheme());\n\n await this._router.navigate(['/view']);\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "\n\n
\n
\n
\n\n\n\n
\n
\n
\n\n\n" - }, - { - "name": "HomeGeneratorComponent", - "id": "component-HomeGeneratorComponent-08bf815fec6689a3e44a4ff84ba144d6311fd4d7af86b44e78e24f1967b9f31929201be37e95cde23793bef2e7262f9e6740edee3e68bf4bf3f4d1636c700671", - "file": "src/app/home/ui/home-generator/home-generator.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-home-generator", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./home-generator.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "generate", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 21, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "hex", - "defaultValue": "model('#3B82F6')", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 16, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "scheme", - "defaultValue": "model(PaletteScheme.RAINBOW)", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 17, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "DropdownMenuComponent", - "type": "component" - }, - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "ColorInputComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, computed, model, output, signal } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroChevronDownMini } from '@ng-icons/heroicons/mini';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { PALETTE_SCHEMES, PaletteScheme } from '../../../shared/constants/palette-scheme';\nimport { ColorInputComponent } from '../../../shared/ui/color-input/color-input.component';\nimport { DropdownMenuComponent } from '../../../shared/ui/dropdown-menu/dropdown-menu.component';\n\n@Component({\n selector: 'rp-home-generator',\n standalone: true,\n imports: [TranslateModule, DropdownMenuComponent, NgIconComponent, ColorInputComponent],\n templateUrl: './home-generator.component.html'\n})\nexport class HomeGeneratorComponent {\n public readonly hex = model('#3B82F6');\n public readonly scheme = model(PaletteScheme.RAINBOW);\n\n protected readonly isValid = signal(true);\n\n public readonly generate = output();\n\n protected readonly heroChevronDownMini = heroChevronDownMini;\n\n protected get schemeOptions(): Array<{\n value: PaletteScheme;\n label: string;\n }> {\n return PALETTE_SCHEMES;\n }\n\n protected readonly selectedScheme = computed(() =>\n this.schemeOptions.find((option) => option.value === this.scheme())\n );\n\n protected setScheme(value: PaletteScheme | undefined): void {\n if (value === undefined) {\n return;\n }\n\n this.scheme.set(value);\n }\n\n protected generatePalette(): void {\n this.generate.emit();\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "
\n
\n

\n {{ 'home.generator.title' | translate }}\n

\n\n

\n {{ 'home.generator.description' | translate }}\n

\n
\n\n
\n \n\n \n \n {{ 'home.generator.generate' | translate }}\n \n\n \n \n \n \n {{ 'home.generator.scheme' | translate }}\n \n \n\n \n {{ scheme.label | translate }}\n \n \n \n
\n
\n" - }, - { - "name": "HomeManualComponent", - "id": "component-HomeManualComponent-87ca8127370638bb866191c8b6139a9bb79f262bcf7cfbb0d8e1b94bc1adc67d51fcd19a6db914a401f45ecc6ed1ece6ead7c1ebf28df05c0df1a9b4ca06f057", - "file": "src/app/home/ui/home-manual/home-manual.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-home-manual", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./home-manual.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "TranslateModule", - "type": "module" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroAdjustmentsHorizontal, heroArchiveBoxArrowDown, heroSwatch } from '@ng-icons/heroicons/outline';\nimport { TranslateModule } from '@ngx-translate/core';\n\n@Component({\n selector: 'rp-home-manual',\n standalone: true,\n imports: [NgIconComponent, TranslateModule],\n templateUrl: './home-manual.component.html'\n})\nexport class HomeManualComponent {\n protected readonly heroSwatch = heroSwatch;\n protected readonly heroAdjustmentsHorizontal = heroAdjustmentsHorizontal;\n protected readonly heroArchiveBoxArrowDown = heroArchiveBoxArrowDown;\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "
\n
\n

\n {{ 'home.manual.title' | translate }}\n

\n\n

\n {{ 'home.manual.description' | translate }}\n

\n
\n\n
\n \n \n\n

\n {{ 'home.manual.generate.title' | translate }}\n

\n\n

\n {{ 'home.manual.generate.description' | translate }}\n

\n \n\n \n \n\n

\n {{ 'home.manual.edit.title' | translate }}\n

\n\n

\n {{ 'home.manual.edit.description' | translate }}\n

\n \n\n \n \n\n

\n {{ 'home.manual.download.title' | translate }}\n

\n\n

\n {{ 'home.manual.download.description' | translate }}\n

\n \n
\n\n
\n

\n
\n
\n" - }, - { - "name": "HomeSupportComponent", - "id": "component-HomeSupportComponent-d623435a054ba3ec1bc019bbb67a7c747d11ba8cabfffea6001bb0b594284bb8b408b58c82b346858fba1adf30d003d180dad2234d14a44504b8a79ada60a523", - "file": "src/app/home/ui/home-support/home-support.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-home-support", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./home-support.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "AccordionComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroChevronDown, heroPlus } from '@ng-icons/heroicons/outline';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { AccordionComponent } from '../../../shared/ui/accordion/accordion.component';\n\n@Component({\n selector: 'rp-home-support',\n standalone: true,\n imports: [TranslateModule, NgIconComponent, AccordionComponent],\n templateUrl: './home-support.component.html'\n})\nexport class HomeSupportComponent {\n protected readonly heroChevronDown = heroChevronDown;\n protected readonly heroPlus = heroPlus;\n\n protected readonly githubLink = `\n GitHub\n `;\n protected readonly discordLink = `\n Discord\n `;\n\n protected readonly roadmapFeatures = [15, 17, 18, 19, 22];\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "
\n
\n

\n {{ 'home.support.title' | translate }}\n

\n
\n\n
\n \n\n \n

\n {{ 'home.support.roadmap.description' | translate }}\n

\n\n
\n
    \n @for (issue of roadmapFeatures; track issue) {\n
  • \n {{ 'home.support.roadmap.issue.' + issue | translate }}\n (#{{ issue }})\n
  • \n }\n
\n
\n
\n
\n
\n" - }, - { - "name": "ImprintComponent", - "id": "component-ImprintComponent-2ab08c23cc15c3f55fad796da89dd381241a444a5165b16c3ae88315625bc8eb9dc5ee640285fd50db78742224eef8f3bce62db5993aee1c58a320457d392fd1", - "file": "src/app/imprint/imprint.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-imprint", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./imprint.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "AccordionComponent", - "type": "component" - }, - { - "name": "RouterLink" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { ViewportScroller } from '@angular/common';\nimport { Component, computed, inject } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { NavigationEnd, Router, RouterLink } from '@angular/router';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { filter } from 'rxjs';\nimport { AnalyticsService, AnalyticsStatus } from '../shared/data-access/analytics.service';\nimport { LanguageService } from '../shared/data-access/language.service';\nimport { AccordionComponent } from '../shared/ui/accordion/accordion.component';\nimport { sleep } from '../shared/utils/sleep';\n\n@Component({\n selector: 'rp-imprint',\n standalone: true,\n imports: [TranslateModule, AccordionComponent, RouterLink],\n templateUrl: './imprint.component.html'\n})\nexport default class ImprintComponent {\n private readonly _router = inject(Router);\n private readonly _viewportScroller = inject(ViewportScroller);\n private readonly _analyticsService = inject(AnalyticsService);\n private readonly _languageService = inject(LanguageService);\n\n private readonly _anchorScrolling = this._router.events\n .pipe(\n takeUntilDestroyed(),\n filter((event): event is NavigationEnd => event instanceof NavigationEnd)\n )\n .subscribe(async (event) => {\n if (event.urlAfterRedirects.includes('#')) {\n // Wait for scroll to finish\n await sleep(10);\n\n // Calculate new scroll position to account for header\n const position = this._viewportScroller.getScrollPosition();\n const headerHeight = document.documentElement.style.getPropertyValue('--header-height');\n const offset = headerHeight ? parseInt(headerHeight) : 0;\n\n // Scroll to new position\n this._viewportScroller.scrollToPosition([position[0], position[1] - offset - 32]);\n }\n });\n\n protected readonly matomo =\n 'Matomo';\n\n protected readonly analyticsEnabled = computed(() => {\n return this._analyticsService.status() === AnalyticsStatus.ACCEPTED;\n });\n\n protected readonly german = computed(() => {\n return this._languageService.language() === 'de';\n });\n\n protected enableAnalytics(): void {\n this._analyticsService.acceptAnalytics();\n }\n\n protected disableAnalytics(): void {\n this._analyticsService.declineAnalytics();\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "
\n \n {{ 'imprint.title' | translate }}\n \n\n \n

\n Luis Wolf
\n Johann-von-Korb-Straße 30a
\n 97753 Karlstadt
\n {{ 'imprint.contact.germany' | translate }}\n

\n\n

\n {{ 'imprint.contact.title' | translate }}\n

\n\n

\n {{ 'imprint.contact.methods' | translate }}\n

\n\n

\n {{ 'imprint.contact.telephone' | translate }}: 01523 / 4171102
\n {{ 'imprint.contact.email' | translate }}:\n \n contact@pawcode.de\n \n

\n\n

\n {{ 'imprint.contact.note' | translate }}\n \n § 5 TMG\n \n

\n\n

\n {{ 'imprint.contact.hidden' | translate }}\n

\n \n\n

\n {{ 'imprint.notes' | translate }}\n

\n\n \n \n {{ 'imprint.copyright.title' | translate }}\n \n\n
    \n
  • \n {{ 'imprint.copyright.idea' | translate }}\n \n pawcode Development\n \n
  • \n
  • \n {{ 'imprint.copyright.logo' | translate }}\n \n Sam Peniak\n \n
  • \n
  • \n {{ 'imprint.copyright.design' | translate }}\n \n HyperUI\n \n
  • \n
  • \n {{ 'imprint.copyright.icons' | translate }}\n \n Heroicons\n \n
  • \n
  • \n {{ 'imprint.copyright.icons-branded' | translate }}\n \n Simple Icons\n \n
  • \n
\n \n\n \n

\n Haftungsausschuss\n @if (!german()) {\n ({{ 'imprint.german-only' | translate }})\n }\n

\n

Haftung für Inhalte

\n

\n Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und\n Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir gemäß\n \n § 7 Abs. 1 TMG\n \n für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach\n \n §§ 8 bis 10 TMG\n \n sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu\n überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen zur\n Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt.\n Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung\n möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.\n

\n\n

Haftung für Links

\n

\n Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb\n können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Die verlinkten Seiten wurden zum Zeitpunkt der\n Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht\n erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte\n einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links\n umgehend entfernen.\n

\n\n

Urheberrecht

\n

\n Die durch den Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen\n Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des\n Urheberrechts bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien\n dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet. Soweit die Inhalte auf dieser\n Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte\n Dritter oben unter\n Copyright\n erwähnt. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen\n entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.\n

\n\n

Datenschutz

\n

\n Die Nutzung unserer Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren\n Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder E-Mail Adresse) erhoben werden, erfolgt dies,\n soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Ihre ausdrückliche Zustimmung nicht an\n Dritte weitergegeben.\n

\n

\n Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail)\n Sicherheitslücken ausweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.\n

\n

\n Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten durch Dritte zur Übersendung von\n nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit ausdrücklich widersprochen. Die\n Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von\n Werbeinformationen, etwa durch Spam-Mails, vor.\n

\n\n

Verbraucherstreitbeilegung

\n

\n Der Betreiber dieser Website erklärt:
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren\n vor einer Verbraucherschlichtungsstelle teilzunehmen.\n

\n

\n Angaben gemäß\n \n § 36 VSBG\n \n

\n \n\n \n \n {{ 'imprint.analytics.title' | translate }}\n \n\n

\n {{ 'imprint.analytics.usage.title' | translate }}\n

\n

\n

\n\n

\n {{ 'imprint.analytics.data.title' | translate }}\n

\n
    \n
  • {{ 'imprint.analytics.data.country' | translate }}
  • \n
  • {{ 'imprint.analytics.data.device' | translate }}
  • \n
  • {{ 'imprint.analytics.data.os' | translate }}
  • \n
  • {{ 'imprint.analytics.data.browser' | translate }}
  • \n
  • {{ 'imprint.analytics.data.time' | translate }}
  • \n
  • {{ 'imprint.analytics.data.events' | translate }}
  • \n
\n\n @if (analyticsEnabled()) {\n

\n {{ 'imprint.analytics.disable.title' | translate }}\n

\n

\n {{ 'imprint.analytics.disable.description' | translate }}\n

\n\n \n {{ 'imprint.analytics.disable.button' | translate }}\n \n } @else {\n

\n {{ 'imprint.analytics.enable.title' | translate }}\n

\n

\n {{ 'imprint.analytics.enable.description' | translate }}\n

\n\n \n {{ 'imprint.analytics.enable.button' | translate }}\n \n }\n \n
\n" - }, - { - "name": "LayoutAnalyticsConsentComponent", - "id": "component-LayoutAnalyticsConsentComponent-80aa8ef700ec0edc97eee4f9767292e59231b62ffbfa84f56f1b4960de26a29c4c92df87e2c9b383636cbb663ec10e66acb95010961e3f88a4ae0c2aed91f003", - "file": "src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-layout-analytics-consent", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./layout-analytics-consent.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "consent", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 15, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "height", - "defaultValue": "computed(() => {\n return this._container().nativeElement.offsetHeight;\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 19, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [ - { - "name": "accept", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 23, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - }, - { - "name": "decline", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 27, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "RouterLink" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, ElementRef, computed, output, viewChild } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { TranslateModule } from '@ngx-translate/core';\n\n@Component({\n selector: 'rp-layout-analytics-consent',\n standalone: true,\n imports: [TranslateModule, RouterLink],\n templateUrl: './layout-analytics-consent.component.html',\n styleUrl: './layout-analytics-consent.component.css'\n})\nexport class LayoutAnalyticsConsentComponent {\n protected readonly matomo = 'Matomo';\n\n public readonly consent = output();\n\n private readonly _container = viewChild.required>('container');\n\n public readonly height = computed(() => {\n return this._container().nativeElement.offsetHeight;\n });\n\n public accept(): void {\n this.consent.emit(true);\n }\n\n public decline(): void {\n this.consent.emit(false);\n }\n}\n", - "styleUrl": "./layout-analytics-consent.component.css", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "\n \n {{ 'layout.analytics.consent.title' | translate }}\n \n\n

\n\n \n {{ 'layout.analytics.consent.more' | translate }}\n \n\n
\n \n {{ 'layout.analytics.consent.decline' | translate }}\n \n\n \n {{ 'layout.analytics.consent.accept' | translate }}\n \n
\n\n" - }, - { - "name": "LayoutComponent", - "id": "component-LayoutComponent-7fb6a498e6c77e23c97879b75b1998e5f1fbee81a3f8428fb6ddd1c8f7aab5143cec6ed22f140a8b5cecc1e4dfc673711aeb8c73008e58509db0263c59116fcf", - "file": "src/app/layout/layout.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-layout", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./layout.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [ - { - "name": "analyticsConsent", - "args": [ - { - "name": "consent", - "type": "boolean", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 123, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "consent", - "type": "boolean", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "changeLanguage", - "args": [ - { - "name": "language", - "type": "Language", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 115, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "language", - "type": "Language", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "changeTheme", - "args": [ - { - "name": "theme", - "type": "Theme", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 119, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125 - ], - "jsdoctags": [ - { - "name": "theme", - "type": "Theme", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "ngAfterViewInit", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 105, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "LayoutNavigationComponent", - "type": "component" - }, - { - "name": "CommonModule", - "type": "module" - }, - { - "name": "LayoutOptionsComponent", - "type": "component" - }, - { - "name": "LayoutFooterComponent", - "type": "component" - }, - { - "name": "RouterOutlet" - }, - { - "name": "LayoutAnalyticsConsentComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { CommonModule } from '@angular/common';\nimport { AfterViewInit, Component, ElementRef, computed, effect, inject, signal, viewChild } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\nimport { heroAdjustmentsHorizontalSolid, heroRectangleGroupSolid, heroSwatchSolid } from '@ng-icons/heroicons/solid';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { AnalyticsService, AnalyticsStatus } from '../shared/data-access/analytics.service';\nimport { LanguageService } from '../shared/data-access/language.service';\nimport { MobileService } from '../shared/data-access/mobile.service';\nimport { PwaService } from '../shared/data-access/pwa.service';\nimport { ThemeService } from '../shared/data-access/theme.service';\nimport { Theme } from '../shared/types/theme';\nimport { sleep } from '../shared/utils/sleep';\nimport { Language } from './types/language';\nimport { NavigationEntry } from './types/navigation-entry';\nimport { LayoutAnalyticsConsentComponent } from './ui/layout-analytics-consent/layout-analytics-consent.component';\nimport { LayoutFooterComponent } from './ui/layout-footer/layout-footer.component';\nimport { LayoutNavigationComponent } from './ui/layout-navigation/layout-navigation.component';\nimport { LayoutOptionsComponent } from './ui/layout-options/layout-options.component';\n\n@Component({\n selector: 'rp-layout',\n standalone: true,\n imports: [\n TranslateModule,\n LayoutNavigationComponent,\n CommonModule,\n LayoutOptionsComponent,\n LayoutFooterComponent,\n RouterOutlet,\n LayoutAnalyticsConsentComponent\n ],\n templateUrl: './layout.component.html',\n styleUrl: './layout.component.css'\n})\nexport class LayoutComponent implements AfterViewInit {\n private readonly _mobileService = inject(MobileService);\n private readonly _languageService = inject(LanguageService);\n private readonly _themeService = inject(ThemeService);\n private readonly _analyticsService = inject(AnalyticsService);\n // This service gets injected into the layout to initialize the PWA service after the layout has been initialized.\n private readonly _pwaService = inject(PwaService);\n\n private readonly _header = viewChild.required>('header');\n private readonly _footer = viewChild>('footer');\n private readonly _bottomNavigation = viewChild>('bottomNavigation');\n private readonly _analytics = viewChild(LayoutAnalyticsConsentComponent);\n\n protected readonly navigationEntries: Array = [\n {\n title: 'layout.navigation.generate.title',\n path: '/',\n icon: heroSwatchSolid,\n description: 'layout.navigation.generate.description'\n },\n {\n title: 'layout.navigation.view.title',\n path: '/view',\n icon: heroAdjustmentsHorizontalSolid,\n description: 'layout.navigation.view.description'\n },\n {\n title: 'layout.navigation.preview.title',\n path: '/preview',\n icon: heroRectangleGroupSolid,\n description: 'layout.navigation.preview.description'\n }\n ];\n protected readonly isMobile = this._mobileService.isMobile;\n protected readonly language = this._languageService.language;\n protected readonly theme = this._themeService.theme;\n protected readonly initialized = signal(false);\n\n private readonly _resize = this._mobileService.resize;\n\n protected readonly logoAsset = computed(() => {\n return this.theme() === 'dark' ? '/assets/rainbow-palette-light.svg' : '/assets/rainbow-palette-dark.svg';\n });\n\n protected readonly showAnalyticsConsent = computed(() => {\n return this._analyticsService.status() === AnalyticsStatus.UNSET;\n });\n\n public constructor() {\n effect(() => {\n if (!this.initialized()) {\n return;\n }\n\n // Reference resize signal to trigger computation on window resize\n this._resize();\n\n document.documentElement.style.setProperty('--header-height', `${this._header().nativeElement.offsetHeight}px`);\n\n const footerHeight = this._footer()?.nativeElement.offsetHeight ?? 0;\n document.documentElement.style.setProperty('--footer-height', `${footerHeight}px`);\n\n const bottomNavigationHeight = this._bottomNavigation()?.nativeElement.offsetHeight ?? 0;\n document.documentElement.style.setProperty('--bottom-navigation-height', `${bottomNavigationHeight}px`);\n\n const analyticsHeight = this._analytics()?.height() ?? 0;\n document.documentElement.style.setProperty('--analytics-consent-height', `${analyticsHeight}px`);\n });\n }\n\n public async ngAfterViewInit(): Promise {\n /*\n * Wait for header and footer heights to be resolved\n * before rendering the main content with the correct\n * padding.\n */\n await sleep(100);\n this.initialized.set(true);\n }\n\n public changeLanguage(language: Language): void {\n this._languageService.setLanguage(language);\n }\n\n public changeTheme(theme: Theme): void {\n this._themeService.setTheme(theme);\n }\n\n public analyticsConsent(consent: boolean): void {\n if (consent) {\n this._analyticsService.acceptAnalytics();\n } else {\n this._analyticsService.declineAnalytics();\n }\n }\n}\n", - "styleUrl": "./layout.component.css", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "constructorObj": { - "name": "constructor", - "description": "", - "deprecated": false, - "deprecationMessage": "", - "args": [], - "line": 81, - "modifierKind": [ - 125 - ] - }, - "extends": [], - "implements": [ - "AfterViewInit" - ], - "templateData": "
\n \n

\n Rainbow Palette\n \n

\n\n @if (!isMobile()) {\n \n }\n\n \n \n\n @if (initialized()) {\n \n
\n \n
\n\n \n \n \n \n }\n @if (showAnalyticsConsent()) {\n \n }\n @if (isMobile()) {\n \n \n
\n }\n\n\n\n \n\n" - }, - { - "name": "LayoutFooterComponent", - "id": "component-LayoutFooterComponent-ea9738f51dc90c3d49cd5c2686555d75ff8657bc5ee5bba546a46c871b042d0aecc18d774a28d78ae22436d2a733a69c8a8ff3eb869df69c3d1feb77fd42d798", - "file": "src/app/layout/ui/layout-footer/layout-footer.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-layout-footer", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./layout-footer.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "logoAsset", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 17, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "RouterLink" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, inject, input } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { simpleDiscord, simpleGithub, simpleInstagram, simpleTwitter, simpleYoutube } from '@ng-icons/simple-icons';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { VersionService } from '../../../shared/data-access/version.service';\n\n@Component({\n selector: 'rp-layout-footer',\n standalone: true,\n imports: [TranslateModule, NgIconComponent, RouterLink],\n templateUrl: './layout-footer.component.html'\n})\nexport class LayoutFooterComponent {\n private readonly _versionService = inject(VersionService);\n\n public readonly logoAsset = input.required();\n\n protected readonly simpleDiscord = simpleDiscord;\n protected readonly simpleGithub = simpleGithub;\n protected readonly simpleInstagram = simpleInstagram;\n protected readonly simpleTwitter = simpleTwitter;\n protected readonly simpleYoutube = simpleYoutube;\n\n protected readonly version = this._versionService.appVersion;\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "\n
\n
\n \n \n {{ version }}\n \n
\n\n

\n {{ 'layout.footer.description' | translate }}\n

\n\n
    \n
  • \n \n {{ 'layout.footer.github' | translate }}\n \n
  • \n\n
  • \n \n {{ 'layout.footer.website' | translate }}\n \n
  • \n\n
  • \n \n {{ 'layout.footer.imprint' | translate }}\n \n
  • \n\n
  • \n \n {{ 'layout.footer.analytics' | translate }}\n \n
  • \n
\n\n
    \n
  • \n \n GitHub\n \n \n
  • \n\n
  • \n \n Instagram\n \n \n
  • \n\n
  • \n \n Discord\n \n \n
  • \n\n
  • \n \n X\n \n \n
  • \n\n
  • \n \n YouTube\n \n \n
  • \n
\n
\n\n" - }, - { - "name": "LayoutNavigationComponent", - "id": "component-LayoutNavigationComponent-709c566a502f2d901885afb0f14fe5f0d022f04023b44427cf0578bb751020b82a7793753398eb2602b126fba7188b9286ddf3471a059194d100b256a59233bb", - "file": "src/app/layout/ui/layout-navigation/layout-navigation.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-layout-navigation", - "styleUrls": [], - "styles": [ - ":host { display: inline-block; width: 100%; }" - ], - "templateUrl": [ - "./layout-navigation.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "navigationEntries", - "defaultValue": "input.required>()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 17, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "RouterLink" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, inject, input } from '@angular/core';\nimport { Router, RouterLink } from '@angular/router';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { NavigationEntry } from '../../types/navigation-entry';\n\n@Component({\n selector: 'rp-layout-navigation',\n standalone: true,\n imports: [TranslateModule, NgIconComponent, RouterLink],\n templateUrl: './layout-navigation.component.html',\n styles: ':host { display: inline-block; width: 100%; }'\n})\nexport class LayoutNavigationComponent {\n private readonly _router = inject(Router);\n\n public readonly navigationEntries = input.required>();\n\n protected get currentPath(): string {\n return this._router.url;\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": ":host { display: inline-block; width: 100%; }\n", - "extends": [], - "templateData": "\n @for (navEntry of navigationEntries(); track navEntry.path) {\n \n \n \n {{ navEntry.title | translate }}\n \n \n }\n\n" - }, - { - "name": "LayoutOptionsComponent", - "id": "component-LayoutOptionsComponent-ed4021246a26ab11064cd560a27eee02ac54c745cd88422a802eeb9db7a7e7d50aa8b3b765b1d42c309ce035f4333bc980d19bdff6d199ab4edcd8d87083ca0a", - "file": "src/app/layout/ui/layout-options/layout-options.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [ - { - "name": "provideIcons({\n heroSunSolid, heroMoonSolid\n})" - } - ], - "selector": "rp-layout-options", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./layout-options.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "language", - "defaultValue": "model.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 24, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "theme", - "defaultValue": "model.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 25, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "DropdownMenuComponent", - "type": "component" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, computed, model } from '@angular/core';\nimport { NgIconComponent, provideIcons } from '@ng-icons/core';\nimport { heroMoonSolid, heroSunSolid } from '@ng-icons/heroicons/solid';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { Theme, ThemeOption } from '../../../shared/types/theme';\nimport { DropdownMenuComponent } from '../../../shared/ui/dropdown-menu/dropdown-menu.component';\nimport { LANGUAGE_OPTIONS } from '../../constants/languages';\nimport { THEME_OPTIONS } from '../../constants/themes';\nimport { Language, LanguageOption } from '../../types/language';\n\n@Component({\n selector: 'rp-layout-options',\n standalone: true,\n imports: [TranslateModule, DropdownMenuComponent, NgIconComponent],\n templateUrl: './layout-options.component.html',\n providers: [\n provideIcons({\n heroSunSolid,\n heroMoonSolid\n })\n ]\n})\nexport class LayoutOptionsComponent {\n public readonly language = model.required();\n public readonly theme = model.required();\n\n protected readonly currentLanguage = computed(() => {\n return this.languageOptions.find((option) => option.value === this.language()) ?? this.languageOptions[0];\n });\n\n protected readonly currentTheme = computed(() => {\n return this.themeOptions.find((option) => option.value === this.theme()) ?? this.themeOptions[0];\n });\n\n protected get languageOptions(): Array {\n return LANGUAGE_OPTIONS;\n }\n\n protected get themeOptions(): Array {\n return THEME_OPTIONS;\n }\n\n protected changeLanguage(language: Language): void {\n this.language.set(language);\n }\n\n protected changeTheme(theme: Theme): void {\n this.theme.set(theme);\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "
\n \n \n \n\n \n {{ currentLanguage().label }}\n \n \n\n \n
\n \n\n \n {{ language.label }}\n \n
\n \n \n\n \n \n \n\n \n {{ currentTheme().label | translate }}\n \n \n\n \n
\n \n \n \n\n \n {{ theme.label | translate }}\n \n
\n \n \n
\n" - }, - { - "name": "LoadingComponent", - "id": "component-LoadingComponent-db195c4988c98fe001719b1c8bcbfff62c921ccee41b6c61ff37f66971fcbff99204928b3ddc24433f8231c9b29f5187812bd3430de138a29d7156b5f0c88217", - "file": "src/app/loading/loading.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-loading", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./loading.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, computed, inject } from '@angular/core';\nimport { MobileService } from '../shared/data-access/mobile.service';\nimport { ThemeService } from '../shared/data-access/theme.service';\n\n@Component({\n selector: 'rp-loading',\n standalone: true,\n imports: [],\n templateUrl: './loading.component.html',\n styleUrl: './loading.component.css'\n})\nexport class LoadingComponent {\n private readonly _themeService = inject(ThemeService);\n private readonly _mobileService = inject(MobileService);\n\n protected readonly logoAsset = computed(() => {\n return this._themeService.theme() === 'dark'\n ? '/assets/rainbow-palette-light.svg'\n : '/assets/rainbow-palette-dark.svg';\n });\n\n protected readonly isMobile = this._mobileService.isMobile;\n}\n", - "styleUrl": "./loading.component.css", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "\n
\n

\n Rainbow Palette\n \n

\n\n
\n Loading Rainbow Palette\n\n \n \n \n
\n
\n\n" - }, - { - "name": "NoPaletteComponent", - "id": "component-NoPaletteComponent-01ef189de88ec08d02cdb4ddb7164716f17201ec452d5a35ba3abc2d3e13bb3b349df24db8991937db7c6ccf1925cb24202b76d33dad616cd604b20ff2a96d68", - "file": "src/app/shared/ui/no-palette/no-palette.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-no-palette", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./no-palette.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "parent", - "defaultValue": "input.required<'view' | 'preview'>()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

The parent component of the no-palette component.\nThis determines the text that is displayed in the no-palette component.

\n", - "line": 21, - "rawdescription": "\n\nThe parent component of the no-palette component.\nThis determines the text that is displayed in the no-palette component.\n", - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "RouterLink" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, input } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroArrowUturnLeft } from '@ng-icons/heroicons/outline';\nimport { TranslateModule } from '@ngx-translate/core';\n\n@Component({\n selector: 'rp-no-palette',\n standalone: true,\n imports: [TranslateModule, NgIconComponent, RouterLink],\n templateUrl: './no-palette.component.html',\n styleUrl: './no-palette.component.css'\n})\nexport class NoPaletteComponent {\n protected readonly heroArrowUturnLeft = heroArrowUturnLeft;\n\n /**\n * The parent component of the no-palette component.\n * This determines the text that is displayed in the no-palette component.\n */\n public readonly parent = input.required<'view' | 'preview'>();\n}\n", - "styleUrl": "./no-palette.component.css", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "\n \n

\n {{ 'no-palette.title' | translate }}\n

\n\n

\n {{ 'no-palette.description.' + parent() | translate }}\n

\n\n \n \n {{ 'no-palette.link' | translate }}\n \n \n\n" - }, - { - "name": "PageComponent", - "id": "component-PageComponent-d32b0f19628254ea3fc5526c4d4564ada1cc83cf936759006d90a46f5874b02a3359e331bddda20b22e74a616a6443c660602ba74dd38ebe1160759e0de6f7ba", - "file": "src/stories/page.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "storybook-page", - "styleUrls": [ - "./page.css" - ], - "styles": [], - "template": "
\n
\n

Pages in Storybook

\n

\n We recommend building UIs with a\n \n component-driven\n \n process starting with atomic components and ending with pages.\n

\n

\n Render pages with mock data. This makes it easy to build and review page states without\n needing to navigate to them in your app. Here are some handy patterns for managing page data\n in Storybook:\n

\n
    \n
  • \n Use a higher-level connected component. Storybook helps you compose such data from the\n \"args\" of child component stories\n
  • \n
  • \n Assemble data in the page component from your services. You can mock these services out\n using Storybook.\n
  • \n
\n

\n Get a guided tutorial on component-driven development at\n \n Storybook tutorials\n \n . Read more in the\n docs \n .\n

\n
\n Tip Adjust the width of the canvas with the\n \n \n \n \n \n Viewports addon in the toolbar\n
\n
\n
", - "templateUrl": [], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "user", - "defaultValue": "null", - "deprecated": false, - "deprecationMessage": "", - "type": "User | null", - "optional": false, - "description": "", - "line": 69 - } - ], - "methodsClass": [ - { - "name": "doCreateAccount", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 79, - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "doLogin", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 75, - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "doLogout", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 71, - "deprecated": false, - "deprecationMessage": "" - } - ], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "CommonModule", - "type": "module" - }, - { - "name": "HeaderComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { HeaderComponent } from './header.component';\nimport type { User } from './user';\n\n@Component({\n selector: 'storybook-page',\n standalone: true,\n imports: [CommonModule, HeaderComponent],\n template: `
\n \n
\n

Pages in Storybook

\n

\n We recommend building UIs with a\n \n component-driven\n \n process starting with atomic components and ending with pages.\n

\n

\n Render pages with mock data. This makes it easy to build and review page states without\n needing to navigate to them in your app. Here are some handy patterns for managing page data\n in Storybook:\n

\n
    \n
  • \n Use a higher-level connected component. Storybook helps you compose such data from the\n \"args\" of child component stories\n
  • \n
  • \n Assemble data in the page component from your services. You can mock these services out\n using Storybook.\n
  • \n
\n

\n Get a guided tutorial on component-driven development at\n \n Storybook tutorials\n \n . Read more in the\n docs \n .\n

\n
\n Tip Adjust the width of the canvas with the\n \n \n \n \n \n Viewports addon in the toolbar\n
\n
\n
`,\n styleUrls: ['./page.css'],\n})\nexport class PageComponent {\n user: User | null = null;\n\n doLogout() {\n this.user = null;\n }\n\n doLogin() {\n this.user = { name: 'Jane Doe' };\n }\n\n doCreateAccount() {\n this.user = { name: 'Jane Doe' };\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": [ - { - "data": ".storybook-page {\n font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 24px;\n padding: 48px 20px;\n margin: 0 auto;\n max-width: 600px;\n color: #333;\n}\n\n.storybook-page h2 {\n font-weight: 700;\n font-size: 32px;\n line-height: 1;\n margin: 0 0 4px;\n display: inline-block;\n vertical-align: top;\n}\n\n.storybook-page p {\n margin: 1em 0;\n}\n\n.storybook-page a {\n text-decoration: none;\n color: #1ea7fd;\n}\n\n.storybook-page ul {\n padding-left: 30px;\n margin: 1em 0;\n}\n\n.storybook-page li {\n margin-bottom: 8px;\n}\n\n.storybook-page .tip {\n display: inline-block;\n border-radius: 1em;\n font-size: 11px;\n line-height: 12px;\n font-weight: 700;\n background: #e7fdd8;\n color: #66bf3c;\n padding: 4px 12px;\n margin-right: 10px;\n vertical-align: top;\n}\n\n.storybook-page .tip-wrapper {\n font-size: 13px;\n line-height: 20px;\n margin-top: 40px;\n margin-bottom: 40px;\n}\n\n.storybook-page .tip-wrapper svg {\n display: inline-block;\n height: 12px;\n width: 12px;\n margin-right: 4px;\n vertical-align: top;\n margin-top: 3px;\n}\n\n.storybook-page .tip-wrapper svg path {\n fill: #1ea7fd;\n}\n", - "styleUrl": "./page.css" - } - ], - "stylesData": "", - "extends": [] - }, - { - "name": "PreviewComponent", - "id": "component-PreviewComponent-40089e9d4aca73d16e45e048f800e3db205ec1df2af1814f53abaff153ba62e76398644ffe50ff9eda383564761d959ffeeb555da39a7c9bc27a1f70a97d97ec", - "file": "src/app/preview/preview.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-preview", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./preview.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "RouterLink" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroArrowUturnLeft, heroWrenchScrewdriver } from '@ng-icons/heroicons/outline';\nimport { TranslateModule } from '@ngx-translate/core';\n\n@Component({\n selector: 'rp-preview',\n standalone: true,\n imports: [TranslateModule, NgIconComponent, RouterLink],\n templateUrl: './preview.component.html',\n styleUrl: './preview.component.css'\n})\nexport default class PreviewComponent {\n protected readonly heroArrowUturnLeft = heroArrowUturnLeft;\n protected readonly heroWrenchScrewdriver = heroWrenchScrewdriver;\n\n protected readonly links = {\n github: 'GitHub',\n discord: 'Discord'\n };\n}\n", - "styleUrl": "./preview.component.css", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "\n \n \n\n

\n {{ 'preview.title' | translate }}\n

\n\n

\n\n \n \n {{ 'preview.link' | translate }}\n \n \n\n" - }, - { - "name": "RequestFormatComponent", - "id": "component-RequestFormatComponent-d427318ff4c7cbe925afe4039a1a33cde038709b15699f5336e4c67a9b4b18e7cd1534ee732e0b64320f90ce63d971d6571ba376d3dcffe6898d84227d80a29e", - "file": "src/app/export/ui/request-format/request-format.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-request-format", - "styleUrls": [], - "styles": [ - "\n :host {\n display: block;\n }\n " - ], - "templateUrl": [ - "./request-format.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { EXPORT_FORMATS } from '../../../shared/constants/export-format';\n\n@Component({\n selector: 'rp-request-format',\n standalone: true,\n imports: [TranslateModule, NgIconComponent],\n templateUrl: './request-format.component.html',\n styles: `\n :host {\n display: block;\n }\n `\n})\nexport class RequestFormatComponent {\n protected formats = EXPORT_FORMATS;\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "\n :host {\n display: block;\n }\n \n", - "extends": [], - "templateData": "
\n
\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n
\n\n

\n {{ 'export.request-format.title' | translate }}\n

\n\n

\n {{ 'export.request-format.description' | translate }}\n

\n\n
\n \n {{ 'export.request-format.open-issue' | translate }}\n \n
\n
\n" - }, - { - "name": "ToastComponent", - "id": "component-ToastComponent-78018fa8a441d7e3012981813706ae97cf976abc1641e8a6e04f265c9a726e7260cc8cd08090d1e3b349a9692432af89cdcb204168cd3b0e88f28bd17a6d8372", - "file": "src/app/shared/ui/toast/toast.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-toast", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./toast.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "close", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

Output that emits when the toast should be closed.

\n", - "line": 27, - "rawdescription": "\n\nOutput that emits when the toast should be closed.\n", - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "toast", - "defaultValue": "input.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "

The toast to display containing the type of toast,\nmessage to display, and parameters for the message\nif it is a translation key.

\n", - "line": 22, - "rawdescription": "\n\nThe toast to display containing the type of toast,\nmessage to display, and parameters for the message\nif it is a translation key.\n", - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [ - { - "name": "closeToast", - "args": [], - "optional": false, - "returnType": "void", - "typeParameters": [], - "line": 50, - "deprecated": false, - "deprecationMessage": "", - "rawdescription": "\n\nEmits the close event to close the toast.\n", - "description": "

Emits the close event to close the toast.

\n", - "modifierKind": [ - 125 - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "TranslateModule", - "type": "module" - }, - { - "name": "NgIconComponent", - "type": "component" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, computed, input, output } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroXMarkMini } from '@ng-icons/heroicons/mini';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { Toast } from '../../interfaces/toast.interface';\n\n@Component({\n selector: 'rp-toast',\n standalone: true,\n imports: [TranslateModule, NgIconComponent],\n templateUrl: './toast.component.html',\n styleUrl: './toast.component.css'\n})\nexport class ToastComponent {\n protected readonly heroXMarkMini = heroXMarkMini;\n\n /**\n * The toast to display containing the type of toast,\n * message to display, and parameters for the message\n * if it is a translation key.\n */\n public readonly toast = input.required();\n\n /**\n * Output that emits when the toast should be closed.\n */\n public readonly close = output();\n\n /**\n * Tailwind classes for the toast colors.\n */\n protected readonly colors = computed(() => {\n switch (this.toast().type) {\n case 'success':\n return 'bg-green-50 border-green-500 text-green-800 dark:bg-green-900 dark:border-green-600 dark:text-green-100';\n case 'error':\n return 'bg-red-50 border-red-500 text-red-800 dark:bg-red-900 dark:border-red-600 dark:text-red-100';\n case 'warning':\n return 'bg-yellow-50 border-yellow-500 text-yellow-800 dark:bg-yellow-900 dark:border-yellow-600 dark:text-yellow-100';\n case 'info':\n return 'bg-blue-50 border-blue-500 text-blue-800 dark:bg-blue-900 dark:border-blue-600 dark:text-blue-100';\n default:\n return 'bg-neutral-50 border-neutral-500 text-neutral-800 dark:bg-neutral-900 dark:border-neutral-600 dark:text-neutral-100';\n }\n });\n\n /**\n * Emits the close event to close the toast.\n */\n public closeToast(): void {\n this.close.emit();\n }\n}\n", - "styleUrl": "./toast.component.css", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "@if (toast(); as toast) {\n \n \n {{ toast.message | translate: toast.parameters }}\n \n\n \n \n \n \n}\n" - }, - { - "name": "ViewComponent", - "id": "component-ViewComponent-fe2ccc8324a4b7fcd97c506b678052a93f618ccd7d011c9179273411b1112187eeac469597fcbbed91410d031e57e16d554c2273018570cdad4292ce4819f0b8", - "file": "src/app/view/view.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-view", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./view.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [], - "methodsClass": [ - { - "name": "addColor", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 165, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "copyToClipboard", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 175, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "editColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "shadeIndex", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 136, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - }, - { - "name": "shadeIndex", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "optional": true, - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "exportPalette", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 109, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "removeColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 146, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "renameColor", - "args": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 118, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ], - "jsdoctags": [ - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "renamePalette", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 58, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - }, - { - "name": "savePalette", - "args": [], - "optional": false, - "returnType": "Promise", - "typeParameters": [], - "line": 84, - "deprecated": false, - "deprecationMessage": "", - "modifierKind": [ - 125, - 134 - ] - } - ], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "ViewPaletteComponent", - "type": "component" - }, - { - "name": "NoPaletteComponent", - "type": "component" - }, - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "TranslateModule", - "type": "module" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, computed, inject, signal } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport {\n heroArrowDownTrayMini,\n heroArrowPathMini,\n heroBookmarkMini,\n heroPencilSquareMini,\n heroPlusMini\n} from '@ng-icons/heroicons/mini';\nimport { TranslateModule, TranslateService } from '@ngx-translate/core';\nimport { string_to_unicode_variant as toUnicodeVariant } from 'string-to-unicode-variant';\nimport { ColorEditorService } from '../editor/data-access/color-editor.service';\nimport { ExportModalService } from '../export/data-access/export-modal.service';\nimport { AnalyticsService } from '../shared/data-access/analytics.service';\nimport { ColorService } from '../shared/data-access/color.service';\nimport { DialogService } from '../shared/data-access/dialog.service';\nimport { PaletteService } from '../shared/data-access/palette.service';\nimport { ToastService } from '../shared/data-access/toast.service';\nimport { TrackingEventAction, TrackingEventCategory } from '../shared/enums/tracking-event';\nimport { Color, Shade } from '../shared/model';\nimport { NoPaletteComponent } from '../shared/ui/no-palette/no-palette.component';\nimport { IS_RUNNING_TEST } from '../shared/utils/is-running-test';\nimport { sleep } from '../shared/utils/sleep';\nimport { ViewPaletteComponent } from './ui/view-palette/view-palette.component';\n\n@Component({\n selector: 'rp-view',\n standalone: true,\n imports: [ViewPaletteComponent, NoPaletteComponent, NgIconComponent, TranslateModule],\n templateUrl: './view.component.html'\n})\nexport default class ViewComponent {\n private readonly _isRunningTest = inject(IS_RUNNING_TEST);\n private readonly _colorEditorService = inject(ColorEditorService);\n private readonly _toastService = inject(ToastService);\n private readonly _paletteService = inject(PaletteService);\n private readonly _colorService = inject(ColorService);\n private readonly _translateService = inject(TranslateService);\n private readonly _dialogService = inject(DialogService);\n private readonly _exportService = inject(ExportModalService);\n private readonly _analyticsService = inject(AnalyticsService);\n\n protected readonly heroPencilSquareMini = heroPencilSquareMini;\n protected readonly heroPlusMini = heroPlusMini;\n protected readonly heroArrowDownTrayMini = heroArrowDownTrayMini;\n\n protected readonly palette = this._paletteService.palette;\n protected readonly saving = signal(false);\n\n protected readonly saveIcon = computed(() => {\n if (this.saving()) {\n return heroArrowPathMini;\n } else {\n return heroBookmarkMini;\n }\n });\n\n public async renamePalette(): Promise {\n const palette = this.palette();\n if (!palette) {\n return;\n }\n\n const newName = await this._dialogService.prompt(\n this._translateService.instant('view.palette.rename'),\n palette.name\n );\n\n if (!newName || newName === palette.name) {\n return;\n }\n\n if (newName) {\n palette.name = newName;\n }\n\n this._toastService.showToast({\n type: 'success',\n message: 'view.palette.renamed',\n parameters: { name: newName }\n });\n }\n\n public async savePalette(): Promise {\n if (this.saving()) {\n return;\n }\n\n this.saving.set(true);\n\n this._paletteService.savePaletteToLocalStorage();\n this._analyticsService.trackEvent(\n TrackingEventCategory.SAVE_PALETTE,\n TrackingEventAction.SAVE_PALETTE_LOCAL_STORAGE\n );\n\n if (!this._isRunningTest) {\n // Simulate a delay to show the saving icon for a few seconds in production\n await sleep(3000);\n }\n\n this.saving.set(false);\n this._toastService.showToast({\n type: 'success',\n message: 'view.palette.saved'\n });\n }\n\n public async exportPalette(): Promise {\n const palette = this.palette();\n if (!palette) {\n return;\n }\n\n await this._exportService.openExportModal(palette);\n }\n\n public async renameColor(color: Color): Promise {\n const newName = await this._dialogService.prompt(this._translateService.instant('view.color.rename'), color.name);\n\n if (!newName || newName === color.name) {\n return;\n }\n\n if (newName) {\n color.name = newName;\n }\n\n this._toastService.showToast({\n type: 'success',\n message: 'view.color.renamed',\n parameters: { name: newName }\n });\n }\n\n public async editColor(color: Color, shadeIndex?: number): Promise {\n const updatedColor = await this._colorEditorService.openColorEditor(color, shadeIndex);\n\n if (!updatedColor) {\n return;\n }\n\n color.shades = updatedColor.shades;\n }\n\n public async removeColor(color: Color): Promise {\n const name = color.name;\n const shouldRemove = await this._dialogService.confirm(\n this._translateService.instant('view.color.remove', {\n color: name\n })\n );\n\n if (shouldRemove) {\n this.palette()?.removeColor(color);\n\n this._toastService.showToast({\n type: 'info',\n message: 'view.color.removed',\n parameters: { color: name }\n });\n }\n }\n\n public async addColor(): Promise {\n const palette = this.palette();\n if (!palette) {\n return;\n }\n\n const color = await this._colorService.randomColor();\n palette.addColor(color);\n }\n\n public async copyToClipboard(shade: Shade): Promise {\n try {\n await navigator.clipboard.writeText(shade.hex);\n\n this._toastService.showToast({\n type: 'success',\n message: 'view.color.copy.success',\n parameters: { color: toUnicodeVariant(shade.hex, 'm') }\n });\n } catch (error) {\n this._toastService.showToast({\n type: 'error',\n message: 'view.color.copy.error'\n });\n }\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "@if (palette(); as palette) {\n
\n
\n

\n {{ palette.name }}\n

\n\n
\n \n \n \n {{ 'common.rename' | translate }}\n \n \n\n \n \n \n {{ 'common.export' | translate }}\n \n \n\n \n \n \n \n\n \n {{ (saving() ? 'common.saving' : 'common.save') | translate }}\n \n \n
\n
\n\n \n\n
\n \n \n \n {{ 'view.color.add' | translate }}\n \n \n
\n
\n} @else {\n \n}\n" - }, - { - "name": "ViewPaletteComponent", - "id": "component-ViewPaletteComponent-01cdbf37825214ec42758d23c8fb08b9dc5f6d09dadb3c67366cca543f3b8a6e948c9f772ed46329db20e9f1034029704afff2a8077ab12f135a5b9eb9deef13", - "file": "src/app/view/ui/view-palette/view-palette.component.ts", - "encapsulation": [], - "entryComponents": [], - "inputs": [], - "outputs": [], - "providers": [], - "selector": "rp-view-palette", - "styleUrls": [], - "styles": [], - "templateUrl": [ - "./view-palette.component.html" - ], - "viewProviders": [], - "hostDirectives": [], - "inputsClass": [], - "outputsClass": [], - "propertiesClass": [ - { - "name": "copyShade", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 29, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "editColor", - "defaultValue": "output<{\n color: Color;\n shadeIndex?: number;\n }>()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 24, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "palette", - "defaultValue": "model.required()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 21, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "removeColor", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 28, - "modifierKind": [ - 125, - 148 - ] - }, - { - "name": "renameColor", - "defaultValue": "output()", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "optional": false, - "description": "", - "line": 23, - "modifierKind": [ - 125, - 148 - ] - } - ], - "methodsClass": [], - "deprecated": false, - "deprecationMessage": "", - "hostBindings": [], - "hostListeners": [], - "standalone": true, - "imports": [ - { - "name": "NgIconComponent", - "type": "component" - }, - { - "name": "TranslateModule", - "type": "module" - } - ], - "description": "", - "rawdescription": "\n", - "type": "component", - "sourceCode": "import { Component, model, output } from '@angular/core';\nimport { NgIconComponent } from '@ng-icons/core';\nimport { heroAdjustmentsHorizontalMini, heroPencilSquareMini, heroTrashMini } from '@ng-icons/heroicons/mini';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { Color, Palette, Shade } from '../../../shared/model';\nimport { textColor } from '../../../shared/utils/text-color';\n\n@Component({\n selector: 'rp-view-palette',\n standalone: true,\n imports: [NgIconComponent, TranslateModule],\n templateUrl: './view-palette.component.html'\n})\nexport class ViewPaletteComponent {\n protected readonly textColor = textColor;\n\n protected readonly heroPencilSquareMini = heroPencilSquareMini;\n protected readonly heroAdjustmentsHorizontalMin = heroAdjustmentsHorizontalMini;\n protected readonly heroTrashMini = heroTrashMini;\n\n public readonly palette = model.required();\n\n public readonly renameColor = output();\n public readonly editColor = output<{\n color: Color;\n shadeIndex?: number;\n }>();\n public readonly removeColor = output();\n public readonly copyShade = output();\n\n protected rename(color: Color): void {\n this.renameColor.emit(color);\n }\n\n protected edit(color: Color, shadeIndex?: number): void {\n this.editColor.emit({ color, shadeIndex });\n }\n\n protected remove(color: Color): void {\n this.removeColor.emit(color);\n }\n\n protected copyToClipboard(shade: Shade, $event?: MouseEvent): void {\n if ($event) {\n $event.stopPropagation();\n $event.preventDefault();\n }\n\n this.copyShade.emit(shade);\n }\n}\n", - "assetsDirs": [], - "styleUrlsData": "", - "stylesData": "", - "extends": [], - "templateData": "
\n @for (color of palette().colors; track $index) {\n
\n
\n

\n {{ color.name }}\n

\n\n \n \n \n {{ 'common.rename' | translate }}\n \n \n \n\n \n \n {{ 'common.edit' | translate }}\n \n \n \n\n \n \n {{ 'common.delete' | translate }}\n \n \n \n
\n
\n\n
\n @for (shade of color.shades; track shade.index) {\n \n \n \n {{ shade.index }}\n \n
\n \n {{ shade.hex.toUpperCase().substring(1) }}\n \n
\n \n }\n
\n \n }\n\n" - } - ], - "modules": [], - "miscellaneous": { - "variables": [ - { - "name": "appConfig", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/app.config.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "ApplicationConfig", - "defaultValue": "{\n providers: [\n provideRouter(\n routes,\n withInMemoryScrolling({\n anchorScrolling: 'enabled',\n scrollPositionRestoration: 'top'\n }),\n withRouterConfig({\n onSameUrlNavigation: 'reload'\n })\n ),\n provideHttpClient(withFetch()),\n importProvidersFrom(\n TranslateModule.forRoot({\n defaultLanguage: 'en',\n loader: {\n provide: TranslateLoader,\n useFactory: (http: HttpClient) => new TranslateHttpLoader(http),\n deps: [HttpClient]\n }\n })\n ),\n provideMatomo(\n {\n siteId: 1,\n trackerUrl: 'https://analytics.apps.pawcode.de/',\n enableJSErrorTracking: true,\n acceptDoNotTrack: true,\n requireConsent: MatomoConsentMode.TRACKING,\n disabled: isDevMode(),\n runOutsideAngularZone: true\n },\n withRouter({\n delay: 1000,\n trackPageTitle: false\n }),\n withRouterInterceptors([MatomoTitleInterceptor])\n ),\n provideServiceWorker('ngsw-worker.js', {\n enabled: !isDevMode(),\n registrationStrategy: 'registerWhenStable:30000'\n })\n ]\n}" - }, - { - "name": "EXPORT_FORMATS", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/constants/export-format.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "Array", - "defaultValue": "[\n {\n format: ExportFormat.CSS,\n label: 'export.format.css',\n icon: simpleCss3,\n color: '#1572B6'\n },\n {\n format: ExportFormat.SCSS,\n label: 'export.format.scss',\n icon: simpleSass,\n color: '#CC6699'\n },\n {\n format: ExportFormat.LESS,\n label: 'export.format.less',\n icon: simpleLess,\n color: '#1D365D'\n },\n {\n format: ExportFormat.TAILWIND,\n label: 'export.format.tailwind',\n icon: simpleTailwindcss,\n color: '#06B6D4'\n },\n {\n format: ExportFormat.OTHER,\n label: 'export.format.other',\n icon: heroPlusCircle,\n color: '#171717'\n }\n]" - }, - { - "name": "IS_RUNNING_TEST", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/utils/is-running-test.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "defaultValue": "new InjectionToken(\n 'Boolean indicating if the application is running in a test environment. This is used in some places to skip certain timeouts to speed up test execution. This is used in some places to skip certain timeouts to speed up test execution..',\n {\n providedIn: 'root',\n factory: (): boolean => false\n }\n)", - "rawdescription": "Boolean indicating if the application is running in a test environment.\nThis is used in some places to skip certain timeouts to speed up test execution.", - "description": "

Boolean indicating if the application is running in a test environment.\nThis is used in some places to skip certain timeouts to speed up test execution.

\n" - }, - { - "name": "LANGUAGE_OPTIONS", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/layout/constants/languages.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "Array", - "defaultValue": "[\n { value: 'en', label: 'English', flag: 'us' },\n { value: 'de', label: 'Deutsch', flag: 'de' }\n]" - }, - { - "name": "newVersion", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/utils/sw-update-mock.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "object", - "defaultValue": "{ hash: 'NEW', appData: { version: '1.0.0' } }" - }, - { - "name": "oldVersion", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/utils/sw-update-mock.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "object", - "defaultValue": "{ hash: 'OLD', appData: { version: '0.0.0' } }" - }, - { - "name": "PALETTE_SCHEMES", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/constants/palette-scheme.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "[]", - "defaultValue": "[\n { value: PaletteScheme.RAINBOW, label: 'scheme.rainbow' },\n { value: PaletteScheme.SURPRISE, label: 'scheme.surprise' },\n { value: PaletteScheme.MONOCHROME, label: 'scheme.monochrome' },\n { value: PaletteScheme.ANALOGOUS, label: 'scheme.analogous' },\n { value: PaletteScheme.COMPLEMENTARY, label: 'scheme.complementary' },\n {\n value: PaletteScheme.SPLIT_COMPLEMENTARY,\n label: 'scheme.split-complementary'\n },\n { value: PaletteScheme.TRIADIC, label: 'scheme.triadic' },\n { value: PaletteScheme.COMPOUND, label: 'scheme.compound' }\n]" - }, - { - "name": "sky", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/constants/tailwind-colors.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "defaultValue": "new Color(\n [\n new Shade(50, new Value('#f0f9ff'), true),\n new Shade(100, new Value('#e0f2fe'), true),\n new Shade(200, new Value('#bae6fd'), true),\n new Shade(300, new Value('#7dd3fc'), true),\n new Shade(400, new Value('#38bdf8'), true),\n new Shade(500, new Value('#0ea5e9'), true),\n new Shade(600, new Value('#0284c7'), true),\n new Shade(700, new Value('#0369a1'), true),\n new Shade(800, new Value('#075985'), true),\n new Shade(900, new Value('#0c4a6e'), true)\n ],\n 'Sky'\n)" - }, - { - "name": "THEME_OPTIONS", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/layout/constants/themes.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "Array", - "defaultValue": "[\n { value: 'light', label: 'theme.light', icon: heroSunSolid },\n { value: 'dark', label: 'theme.dark', icon: heroMoonSolid }\n]" - }, - { - "name": "ToastTimeouts", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/interfaces/toast.interface.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "object", - "defaultValue": "{\n success: 3000,\n error: 10000,\n warning: 5000,\n info: 5000,\n default: 5000,\n test: 10\n}" - } - ], - "functions": [ - { - "name": "createStory", - "file": "src/app/shared/utils/storybook.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "args": [ - { - "name": "story", - "type": "InputStory", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "StoryObj", - "jsdoctags": [ - { - "name": "story", - "type": "InputStory", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "hueToWheel", - "file": "src/app/editor/utils/color-wheel.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "args": [ - { - "name": "hue", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "number", - "jsdoctags": [ - { - "name": "hue", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "perceivedBrightnessFromHex", - "file": "src/app/shared/utils/perceived-brightness.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "

Returns the perceived brightness of a color in the range 0-100.

\n", - "args": [ - { - "name": "hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "number", - "jsdoctags": [ - { - "name": { - "pos": 136, - "end": 139, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "hex" - }, - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 130, - "end": 135, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

The color to calculate the perceived brightness of.

\n" - } - ] - }, - { - "name": "perceivedBrightnessFromRGB", - "file": "src/app/shared/utils/perceived-brightness.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "

Returns the perceived brightness of a color in the range 0-100.

\n", - "args": [ - { - "name": "rgb", - "type": "RGBObject", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "number", - "jsdoctags": [ - { - "name": "rgb", - "type": "RGBObject", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "sleep", - "file": "src/app/shared/utils/sleep.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "

Sleep for a given amount of time

\n", - "args": [ - { - "name": "ms", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "Promise", - "jsdoctags": [ - { - "name": { - "pos": 50, - "end": 52, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "ms" - }, - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 44, - "end": 49, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

time to sleep in milliseconds

\n" - } - ] - }, - { - "name": "textColor", - "file": "src/app/shared/utils/text-color.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "

Extracts the text color from a shade based on the perceived brightness of the shade to have a good contrast with the background color.

\n", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "string", - "jsdoctags": [ - { - "name": { - "pos": 197, - "end": 202, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "shade" - }, - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 191, - "end": 196, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

The shade to extract the text color from.

\n" - }, - { - "name": { - "pos": 255, - "end": 260, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "color" - }, - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 249, - "end": 254, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

The color the shade belongs to to determine the contrasting text color.

\n" - } - ] - }, - { - "name": "wheelToHue", - "file": "src/app/editor/utils/color-wheel.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "args": [ - { - "name": "wheel", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "number", - "jsdoctags": [ - { - "name": "wheel", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "typealiases": [ - { - "name": "ExportOption", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "\"copy\" | \"file\"", - "file": "src/app/shared/types/export-option.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 192 - }, - { - "name": "HSLObject", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "literal type", - "file": "src/app/shared/types/color-format.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 187 - }, - { - "name": "InputStory", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "", - "file": "src/app/shared/utils/storybook.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 193 - }, - { - "name": "Language", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "\"en\" | \"de\"", - "file": "src/app/layout/types/language.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "

The language type.

\n", - "kind": 192 - }, - { - "name": "RGBObject", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "literal type", - "file": "src/app/shared/types/color-format.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 187 - }, - { - "name": "Theme", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "\"light\" | \"dark\"", - "file": "src/app/shared/types/theme.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "

A theme type.

\n", - "kind": 192 - }, - { - "name": "TrackingEvent", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "literal type", - "file": "src/app/shared/data-access/analytics.service.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 187 - } - ], - "enumerations": [ - { - "name": "AnalyticsStatus", - "childs": [ - { - "name": "UNSET", - "deprecated": false, - "deprecationMessage": "", - "value": "UNSET" - }, - { - "name": "ACCEPTED", - "deprecated": false, - "deprecationMessage": "", - "value": "ACCEPTED" - }, - { - "name": "DECLINED", - "deprecated": false, - "deprecationMessage": "", - "value": "DECLINED" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/shared/data-access/analytics.service.ts" - }, - { - "name": "CustomDimension", - "childs": [ - { - "name": "LANGUAGE", - "deprecated": false, - "deprecationMessage": "", - "value": 1 - }, - { - "name": "THEME", - "deprecated": false, - "deprecationMessage": "", - "value": 2 - }, - { - "name": "PWA", - "deprecated": false, - "deprecationMessage": "", - "value": 3 - }, - { - "name": "VERSION", - "deprecated": false, - "deprecationMessage": "", - "value": 4 - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/shared/data-access/analytics.service.ts" - }, - { - "name": "ExportFormat", - "childs": [ - { - "name": "CSS", - "deprecated": false, - "deprecationMessage": "", - "value": "css" - }, - { - "name": "SCSS", - "deprecated": false, - "deprecationMessage": "", - "value": "scss" - }, - { - "name": "LESS", - "deprecated": false, - "deprecationMessage": "", - "value": "less" - }, - { - "name": "TAILWIND", - "deprecated": false, - "deprecationMessage": "", - "value": "tailwind" - }, - { - "name": "OTHER", - "deprecated": false, - "deprecationMessage": "", - "value": "other" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/shared/constants/export-format.ts" - }, - { - "name": "ExportModalState", - "childs": [ - { - "name": "FORMAT", - "deprecated": false, - "deprecationMessage": "", - "value": "format" - }, - { - "name": "DOWNLOAD", - "deprecated": false, - "deprecationMessage": "", - "value": "download" - }, - { - "name": "SUCCESS", - "deprecated": false, - "deprecationMessage": "", - "value": "success" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/export/export-modal.component.ts" - }, - { - "name": "LocalStorageKey", - "childs": [ - { - "name": "ANALYTICS", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_ANALYTICS" - }, - { - "name": "EVENTS_DISABLED", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_EVENTS_DISABLED" - }, - { - "name": "EVENTS_OFFLINE", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_EVENTS_OFFLINE" - }, - { - "name": "LAST_GENERATION_SETTINGS", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_LAST_GENERATION_SETTINGS" - }, - { - "name": "LANGUAGE", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_LANGUAGE" - }, - { - "name": "PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_PALETTE" - }, - { - "name": "PALETTE_TMP", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_PALETTE_TMP" - }, - { - "name": "THEME", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_THEME" - }, - { - "name": "UPGRADING", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_UPGRADING" - }, - { - "name": "USER_ID", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_USER_ID" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Keys used to store data in local storage

\n", - "file": "src/app/shared/enums/local-storage-keys.ts" - }, - { - "name": "PaletteScheme", - "childs": [ - { - "name": "RAINBOW", - "deprecated": false, - "deprecationMessage": "", - "value": "rainbow" - }, - { - "name": "SURPRISE", - "deprecated": false, - "deprecationMessage": "", - "value": "surprise" - }, - { - "name": "MONOCHROME", - "deprecated": false, - "deprecationMessage": "", - "value": "monochrome" - }, - { - "name": "ANALOGOUS", - "deprecated": false, - "deprecationMessage": "", - "value": "analogous" - }, - { - "name": "COMPLEMENTARY", - "deprecated": false, - "deprecationMessage": "", - "value": "complementary" - }, - { - "name": "SPLIT_COMPLEMENTARY", - "deprecated": false, - "deprecationMessage": "", - "value": "split-complementary" - }, - { - "name": "TRIADIC", - "deprecated": false, - "deprecationMessage": "", - "value": "triadic" - }, - { - "name": "COMPOUND", - "deprecated": false, - "deprecationMessage": "", - "value": "compound" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/shared/constants/palette-scheme.ts" - }, - { - "name": "TrackingEventAction", - "childs": [ - { - "name": "EVENTS_DISABLED", - "deprecated": false, - "deprecationMessage": "", - "value": "EVENTS_DISABLED" - }, - { - "name": "EVENTS_OFFLINE", - "deprecated": false, - "deprecationMessage": "", - "value": "EVENTS_OFFLINE" - }, - { - "name": "EXPORT_PALETTE_COPY", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_COPY" - }, - { - "name": "EXPORT_PALETTE_FILE", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_FILE" - }, - { - "name": "EXPORT_PALETTE_REQUEST_FORMAT", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_REQUEST_FORMAT" - }, - { - "name": "GENERATE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE" - }, - { - "name": "PWA_INSTALL", - "deprecated": false, - "deprecationMessage": "", - "value": "PWA_INSTALL" - }, - { - "name": "PWA_UPDATE_COMPLETED", - "deprecated": false, - "deprecationMessage": "", - "value": "PWA_UPDATE_COMPLETED" - }, - { - "name": "PWA_UPDATE_FAILED", - "deprecated": false, - "deprecationMessage": "", - "value": "PWA_UPDATE_FAILED" - }, - { - "name": "SAVE_PALETTE_LOCAL_STORAGE", - "deprecated": false, - "deprecationMessage": "", - "value": "SAVE_PALETTE_LOCAL_STORAGE" - }, - { - "name": "TEST", - "deprecated": false, - "deprecationMessage": "", - "value": "TEST_ACTION" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Tracking event actions for Matomo

\n", - "file": "src/app/shared/enums/tracking-event.ts" - }, - { - "name": "TrackingEventCategory", - "childs": [ - { - "name": "EVENTS_DISABLED", - "deprecated": false, - "deprecationMessage": "", - "value": "EVENTS_DISABLED" - }, - { - "name": "EVENTS_OFFLINE", - "deprecated": false, - "deprecationMessage": "", - "value": "EVENTS_OFFLINE" - }, - { - "name": "EXPORT_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE" - }, - { - "name": "GENERATE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE" - }, - { - "name": "PWA", - "deprecated": false, - "deprecationMessage": "", - "value": "PWA" - }, - { - "name": "SAVE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "SAVE_PALETTE" - }, - { - "name": "TEST", - "deprecated": false, - "deprecationMessage": "", - "value": "TEST_CATEGORY" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Tracking event categories for Matomo

\n", - "file": "src/app/shared/enums/tracking-event.ts" - }, - { - "name": "TrackingEventName", - "childs": [ - { - "name": "EXPORT_PALETTE_CSS", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_CSS" - }, - { - "name": "EXPORT_PALETTE_LESS", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_LESS" - }, - { - "name": "EXPORT_PALETTE_SCSS", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_SCSS" - }, - { - "name": "EXPORT_PALETTE_TAILWIND", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_TAILWIND" - }, - { - "name": "EXPORT_PALETTE_UNKNOWN", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_UNKNOWN" - }, - { - "name": "GENERATE_PALETTE_ANALOGOUS", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_ANALOGOUS" - }, - { - "name": "GENERATE_PALETTE_COMPLEMENTARY", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_COMPLEMENTARY" - }, - { - "name": "GENERATE_PALETTE_COMPOUND", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_COMPOUND" - }, - { - "name": "GENERATE_PALETTE_MONOCHROME", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_MONOCHROME" - }, - { - "name": "GENERATE_PALETTE_RAINBOW", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_RAINBOW" - }, - { - "name": "GENERATE_PALETTE_SPLIT_COMPLEMENTARY", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_SPLIT_COMPLEMENTARY" - }, - { - "name": "GENERATE_PALETTE_SURPRISE", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_SURPRISE" - }, - { - "name": "GENERATE_PALETTE_TRIADIC", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_TRIADIC" - }, - { - "name": "GENERATE_PALETTE_UNKNOWN", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_UNKNOWN" - }, - { - "name": "TEST", - "deprecated": false, - "deprecationMessage": "", - "value": "TEST_NAME" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Tracking event names for Matomo

\n", - "file": "src/app/shared/enums/tracking-event.ts" - }, - { - "name": "TrackingGoal", - "childs": [ - { - "name": "EXPORT_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": 1 - }, - { - "name": "VISIT_GITHUB", - "deprecated": false, - "deprecationMessage": "", - "value": 2 - }, - { - "name": "INSTALL_PWA", - "deprecated": false, - "deprecationMessage": "", - "value": 4 - }, - { - "name": "GENERATE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": 5 - }, - { - "name": "SAVE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": 6 - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Tracking goals for Matomo

\n", - "file": "src/app/shared/enums/tracking-goal.ts" - }, - { - "name": "UpdateType", - "childs": [ - { - "name": "HEX", - "deprecated": false, - "deprecationMessage": "", - "value": "hex" - }, - { - "name": "HUE", - "deprecated": false, - "deprecationMessage": "", - "value": "hue" - }, - { - "name": "SATURATION", - "deprecated": false, - "deprecationMessage": "", - "value": "saturation" - }, - { - "name": "LIGHTNESS", - "deprecated": false, - "deprecationMessage": "", - "value": "lightness" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/editor/editor.component.ts" - } - ], - "groupedVariables": { - "src/app/app.config.ts": [ - { - "name": "appConfig", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/app.config.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "ApplicationConfig", - "defaultValue": "{\n providers: [\n provideRouter(\n routes,\n withInMemoryScrolling({\n anchorScrolling: 'enabled',\n scrollPositionRestoration: 'top'\n }),\n withRouterConfig({\n onSameUrlNavigation: 'reload'\n })\n ),\n provideHttpClient(withFetch()),\n importProvidersFrom(\n TranslateModule.forRoot({\n defaultLanguage: 'en',\n loader: {\n provide: TranslateLoader,\n useFactory: (http: HttpClient) => new TranslateHttpLoader(http),\n deps: [HttpClient]\n }\n })\n ),\n provideMatomo(\n {\n siteId: 1,\n trackerUrl: 'https://analytics.apps.pawcode.de/',\n enableJSErrorTracking: true,\n acceptDoNotTrack: true,\n requireConsent: MatomoConsentMode.TRACKING,\n disabled: isDevMode(),\n runOutsideAngularZone: true\n },\n withRouter({\n delay: 1000,\n trackPageTitle: false\n }),\n withRouterInterceptors([MatomoTitleInterceptor])\n ),\n provideServiceWorker('ngsw-worker.js', {\n enabled: !isDevMode(),\n registrationStrategy: 'registerWhenStable:30000'\n })\n ]\n}" - } - ], - "src/app/shared/constants/export-format.ts": [ - { - "name": "EXPORT_FORMATS", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/constants/export-format.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "Array", - "defaultValue": "[\n {\n format: ExportFormat.CSS,\n label: 'export.format.css',\n icon: simpleCss3,\n color: '#1572B6'\n },\n {\n format: ExportFormat.SCSS,\n label: 'export.format.scss',\n icon: simpleSass,\n color: '#CC6699'\n },\n {\n format: ExportFormat.LESS,\n label: 'export.format.less',\n icon: simpleLess,\n color: '#1D365D'\n },\n {\n format: ExportFormat.TAILWIND,\n label: 'export.format.tailwind',\n icon: simpleTailwindcss,\n color: '#06B6D4'\n },\n {\n format: ExportFormat.OTHER,\n label: 'export.format.other',\n icon: heroPlusCircle,\n color: '#171717'\n }\n]" - } - ], - "src/app/shared/utils/is-running-test.ts": [ - { - "name": "IS_RUNNING_TEST", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/utils/is-running-test.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "defaultValue": "new InjectionToken(\n 'Boolean indicating if the application is running in a test environment. This is used in some places to skip certain timeouts to speed up test execution. This is used in some places to skip certain timeouts to speed up test execution..',\n {\n providedIn: 'root',\n factory: (): boolean => false\n }\n)", - "rawdescription": "Boolean indicating if the application is running in a test environment.\nThis is used in some places to skip certain timeouts to speed up test execution.", - "description": "

Boolean indicating if the application is running in a test environment.\nThis is used in some places to skip certain timeouts to speed up test execution.

\n" - } - ], - "src/app/layout/constants/languages.ts": [ - { - "name": "LANGUAGE_OPTIONS", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/layout/constants/languages.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "Array", - "defaultValue": "[\n { value: 'en', label: 'English', flag: 'us' },\n { value: 'de', label: 'Deutsch', flag: 'de' }\n]" - } - ], - "src/app/shared/utils/sw-update-mock.ts": [ - { - "name": "newVersion", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/utils/sw-update-mock.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "object", - "defaultValue": "{ hash: 'NEW', appData: { version: '1.0.0' } }" - }, - { - "name": "oldVersion", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/utils/sw-update-mock.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "object", - "defaultValue": "{ hash: 'OLD', appData: { version: '0.0.0' } }" - } - ], - "src/app/shared/constants/palette-scheme.ts": [ - { - "name": "PALETTE_SCHEMES", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/constants/palette-scheme.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "[]", - "defaultValue": "[\n { value: PaletteScheme.RAINBOW, label: 'scheme.rainbow' },\n { value: PaletteScheme.SURPRISE, label: 'scheme.surprise' },\n { value: PaletteScheme.MONOCHROME, label: 'scheme.monochrome' },\n { value: PaletteScheme.ANALOGOUS, label: 'scheme.analogous' },\n { value: PaletteScheme.COMPLEMENTARY, label: 'scheme.complementary' },\n {\n value: PaletteScheme.SPLIT_COMPLEMENTARY,\n label: 'scheme.split-complementary'\n },\n { value: PaletteScheme.TRIADIC, label: 'scheme.triadic' },\n { value: PaletteScheme.COMPOUND, label: 'scheme.compound' }\n]" - } - ], - "src/app/shared/constants/tailwind-colors.ts": [ - { - "name": "sky", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/constants/tailwind-colors.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "", - "defaultValue": "new Color(\n [\n new Shade(50, new Value('#f0f9ff'), true),\n new Shade(100, new Value('#e0f2fe'), true),\n new Shade(200, new Value('#bae6fd'), true),\n new Shade(300, new Value('#7dd3fc'), true),\n new Shade(400, new Value('#38bdf8'), true),\n new Shade(500, new Value('#0ea5e9'), true),\n new Shade(600, new Value('#0284c7'), true),\n new Shade(700, new Value('#0369a1'), true),\n new Shade(800, new Value('#075985'), true),\n new Shade(900, new Value('#0c4a6e'), true)\n ],\n 'Sky'\n)" - } - ], - "src/app/layout/constants/themes.ts": [ - { - "name": "THEME_OPTIONS", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/layout/constants/themes.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "Array", - "defaultValue": "[\n { value: 'light', label: 'theme.light', icon: heroSunSolid },\n { value: 'dark', label: 'theme.dark', icon: heroMoonSolid }\n]" - } - ], - "src/app/shared/interfaces/toast.interface.ts": [ - { - "name": "ToastTimeouts", - "ctype": "miscellaneous", - "subtype": "variable", - "file": "src/app/shared/interfaces/toast.interface.ts", - "deprecated": false, - "deprecationMessage": "", - "type": "object", - "defaultValue": "{\n success: 3000,\n error: 10000,\n warning: 5000,\n info: 5000,\n default: 5000,\n test: 10\n}" - } - ] - }, - "groupedFunctions": { - "src/app/shared/utils/storybook.ts": [ - { - "name": "createStory", - "file": "src/app/shared/utils/storybook.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "args": [ - { - "name": "story", - "type": "InputStory", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "StoryObj", - "jsdoctags": [ - { - "name": "story", - "type": "InputStory", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "src/app/editor/utils/color-wheel.ts": [ - { - "name": "hueToWheel", - "file": "src/app/editor/utils/color-wheel.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "args": [ - { - "name": "hue", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "number", - "jsdoctags": [ - { - "name": "hue", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - }, - { - "name": "wheelToHue", - "file": "src/app/editor/utils/color-wheel.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "args": [ - { - "name": "wheel", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "number", - "jsdoctags": [ - { - "name": "wheel", - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "src/app/shared/utils/perceived-brightness.ts": [ - { - "name": "perceivedBrightnessFromHex", - "file": "src/app/shared/utils/perceived-brightness.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "

Returns the perceived brightness of a color in the range 0-100.

\n", - "args": [ - { - "name": "hex", - "type": "string", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "number", - "jsdoctags": [ - { - "name": { - "pos": 136, - "end": 139, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "hex" - }, - "type": "string", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 130, - "end": 135, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

The color to calculate the perceived brightness of.

\n" - } - ] - }, - { - "name": "perceivedBrightnessFromRGB", - "file": "src/app/shared/utils/perceived-brightness.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "

Returns the perceived brightness of a color in the range 0-100.

\n", - "args": [ - { - "name": "rgb", - "type": "RGBObject", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "number", - "jsdoctags": [ - { - "name": "rgb", - "type": "RGBObject", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "text": "param" - } - } - ] - } - ], - "src/app/shared/utils/sleep.ts": [ - { - "name": "sleep", - "file": "src/app/shared/utils/sleep.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "

Sleep for a given amount of time

\n", - "args": [ - { - "name": "ms", - "type": "number", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "Promise", - "jsdoctags": [ - { - "name": { - "pos": 50, - "end": 52, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "ms" - }, - "type": "number", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 44, - "end": 49, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

time to sleep in milliseconds

\n" - } - ] - } - ], - "src/app/shared/utils/text-color.ts": [ - { - "name": "textColor", - "file": "src/app/shared/utils/text-color.ts", - "ctype": "miscellaneous", - "subtype": "function", - "deprecated": false, - "deprecationMessage": "", - "description": "

Extracts the text color from a shade based on the perceived brightness of the shade to have a good contrast with the background color.

\n", - "args": [ - { - "name": "shade", - "type": "Shade", - "deprecated": false, - "deprecationMessage": "" - }, - { - "name": "color", - "type": "Color", - "deprecated": false, - "deprecationMessage": "" - } - ], - "returnType": "string", - "jsdoctags": [ - { - "name": { - "pos": 197, - "end": 202, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "shade" - }, - "type": "Shade", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 191, - "end": 196, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

The shade to extract the text color from.

\n" - }, - { - "name": { - "pos": 255, - "end": 260, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "color" - }, - "type": "Color", - "deprecated": false, - "deprecationMessage": "", - "tagName": { - "pos": 249, - "end": 254, - "flags": 16777216, - "modifierFlagsCache": 0, - "transformFlags": 0, - "kind": 80, - "escapedText": "param" - }, - "comment": "

The color the shade belongs to to determine the contrasting text color.

\n" - } - ] - } - ] - }, - "groupedEnumerations": { - "src/app/shared/data-access/analytics.service.ts": [ - { - "name": "AnalyticsStatus", - "childs": [ - { - "name": "UNSET", - "deprecated": false, - "deprecationMessage": "", - "value": "UNSET" - }, - { - "name": "ACCEPTED", - "deprecated": false, - "deprecationMessage": "", - "value": "ACCEPTED" - }, - { - "name": "DECLINED", - "deprecated": false, - "deprecationMessage": "", - "value": "DECLINED" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/shared/data-access/analytics.service.ts" - }, - { - "name": "CustomDimension", - "childs": [ - { - "name": "LANGUAGE", - "deprecated": false, - "deprecationMessage": "", - "value": 1 - }, - { - "name": "THEME", - "deprecated": false, - "deprecationMessage": "", - "value": 2 - }, - { - "name": "PWA", - "deprecated": false, - "deprecationMessage": "", - "value": 3 - }, - { - "name": "VERSION", - "deprecated": false, - "deprecationMessage": "", - "value": 4 - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/shared/data-access/analytics.service.ts" - } - ], - "src/app/shared/constants/export-format.ts": [ - { - "name": "ExportFormat", - "childs": [ - { - "name": "CSS", - "deprecated": false, - "deprecationMessage": "", - "value": "css" - }, - { - "name": "SCSS", - "deprecated": false, - "deprecationMessage": "", - "value": "scss" - }, - { - "name": "LESS", - "deprecated": false, - "deprecationMessage": "", - "value": "less" - }, - { - "name": "TAILWIND", - "deprecated": false, - "deprecationMessage": "", - "value": "tailwind" - }, - { - "name": "OTHER", - "deprecated": false, - "deprecationMessage": "", - "value": "other" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/shared/constants/export-format.ts" - } - ], - "src/app/export/export-modal.component.ts": [ - { - "name": "ExportModalState", - "childs": [ - { - "name": "FORMAT", - "deprecated": false, - "deprecationMessage": "", - "value": "format" - }, - { - "name": "DOWNLOAD", - "deprecated": false, - "deprecationMessage": "", - "value": "download" - }, - { - "name": "SUCCESS", - "deprecated": false, - "deprecationMessage": "", - "value": "success" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/export/export-modal.component.ts" - } - ], - "src/app/shared/enums/local-storage-keys.ts": [ - { - "name": "LocalStorageKey", - "childs": [ - { - "name": "ANALYTICS", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_ANALYTICS" - }, - { - "name": "EVENTS_DISABLED", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_EVENTS_DISABLED" - }, - { - "name": "EVENTS_OFFLINE", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_EVENTS_OFFLINE" - }, - { - "name": "LAST_GENERATION_SETTINGS", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_LAST_GENERATION_SETTINGS" - }, - { - "name": "LANGUAGE", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_LANGUAGE" - }, - { - "name": "PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_PALETTE" - }, - { - "name": "PALETTE_TMP", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_PALETTE_TMP" - }, - { - "name": "THEME", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_THEME" - }, - { - "name": "UPGRADING", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_UPGRADING" - }, - { - "name": "USER_ID", - "deprecated": false, - "deprecationMessage": "", - "value": "RP_USER_ID" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Keys used to store data in local storage

\n", - "file": "src/app/shared/enums/local-storage-keys.ts" - } - ], - "src/app/shared/constants/palette-scheme.ts": [ - { - "name": "PaletteScheme", - "childs": [ - { - "name": "RAINBOW", - "deprecated": false, - "deprecationMessage": "", - "value": "rainbow" - }, - { - "name": "SURPRISE", - "deprecated": false, - "deprecationMessage": "", - "value": "surprise" - }, - { - "name": "MONOCHROME", - "deprecated": false, - "deprecationMessage": "", - "value": "monochrome" - }, - { - "name": "ANALOGOUS", - "deprecated": false, - "deprecationMessage": "", - "value": "analogous" - }, - { - "name": "COMPLEMENTARY", - "deprecated": false, - "deprecationMessage": "", - "value": "complementary" - }, - { - "name": "SPLIT_COMPLEMENTARY", - "deprecated": false, - "deprecationMessage": "", - "value": "split-complementary" - }, - { - "name": "TRIADIC", - "deprecated": false, - "deprecationMessage": "", - "value": "triadic" - }, - { - "name": "COMPOUND", - "deprecated": false, - "deprecationMessage": "", - "value": "compound" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/shared/constants/palette-scheme.ts" - } - ], - "src/app/shared/enums/tracking-event.ts": [ - { - "name": "TrackingEventAction", - "childs": [ - { - "name": "EVENTS_DISABLED", - "deprecated": false, - "deprecationMessage": "", - "value": "EVENTS_DISABLED" - }, - { - "name": "EVENTS_OFFLINE", - "deprecated": false, - "deprecationMessage": "", - "value": "EVENTS_OFFLINE" - }, - { - "name": "EXPORT_PALETTE_COPY", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_COPY" - }, - { - "name": "EXPORT_PALETTE_FILE", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_FILE" - }, - { - "name": "EXPORT_PALETTE_REQUEST_FORMAT", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_REQUEST_FORMAT" - }, - { - "name": "GENERATE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE" - }, - { - "name": "PWA_INSTALL", - "deprecated": false, - "deprecationMessage": "", - "value": "PWA_INSTALL" - }, - { - "name": "PWA_UPDATE_COMPLETED", - "deprecated": false, - "deprecationMessage": "", - "value": "PWA_UPDATE_COMPLETED" - }, - { - "name": "PWA_UPDATE_FAILED", - "deprecated": false, - "deprecationMessage": "", - "value": "PWA_UPDATE_FAILED" - }, - { - "name": "SAVE_PALETTE_LOCAL_STORAGE", - "deprecated": false, - "deprecationMessage": "", - "value": "SAVE_PALETTE_LOCAL_STORAGE" - }, - { - "name": "TEST", - "deprecated": false, - "deprecationMessage": "", - "value": "TEST_ACTION" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Tracking event actions for Matomo

\n", - "file": "src/app/shared/enums/tracking-event.ts" - }, - { - "name": "TrackingEventCategory", - "childs": [ - { - "name": "EVENTS_DISABLED", - "deprecated": false, - "deprecationMessage": "", - "value": "EVENTS_DISABLED" - }, - { - "name": "EVENTS_OFFLINE", - "deprecated": false, - "deprecationMessage": "", - "value": "EVENTS_OFFLINE" - }, - { - "name": "EXPORT_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE" - }, - { - "name": "GENERATE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE" - }, - { - "name": "PWA", - "deprecated": false, - "deprecationMessage": "", - "value": "PWA" - }, - { - "name": "SAVE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": "SAVE_PALETTE" - }, - { - "name": "TEST", - "deprecated": false, - "deprecationMessage": "", - "value": "TEST_CATEGORY" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Tracking event categories for Matomo

\n", - "file": "src/app/shared/enums/tracking-event.ts" - }, - { - "name": "TrackingEventName", - "childs": [ - { - "name": "EXPORT_PALETTE_CSS", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_CSS" - }, - { - "name": "EXPORT_PALETTE_LESS", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_LESS" - }, - { - "name": "EXPORT_PALETTE_SCSS", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_SCSS" - }, - { - "name": "EXPORT_PALETTE_TAILWIND", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_TAILWIND" - }, - { - "name": "EXPORT_PALETTE_UNKNOWN", - "deprecated": false, - "deprecationMessage": "", - "value": "EXPORT_PALETTE_UNKNOWN" - }, - { - "name": "GENERATE_PALETTE_ANALOGOUS", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_ANALOGOUS" - }, - { - "name": "GENERATE_PALETTE_COMPLEMENTARY", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_COMPLEMENTARY" - }, - { - "name": "GENERATE_PALETTE_COMPOUND", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_COMPOUND" - }, - { - "name": "GENERATE_PALETTE_MONOCHROME", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_MONOCHROME" - }, - { - "name": "GENERATE_PALETTE_RAINBOW", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_RAINBOW" - }, - { - "name": "GENERATE_PALETTE_SPLIT_COMPLEMENTARY", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_SPLIT_COMPLEMENTARY" - }, - { - "name": "GENERATE_PALETTE_SURPRISE", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_SURPRISE" - }, - { - "name": "GENERATE_PALETTE_TRIADIC", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_TRIADIC" - }, - { - "name": "GENERATE_PALETTE_UNKNOWN", - "deprecated": false, - "deprecationMessage": "", - "value": "GENERATE_PALETTE_UNKNOWN" - }, - { - "name": "TEST", - "deprecated": false, - "deprecationMessage": "", - "value": "TEST_NAME" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Tracking event names for Matomo

\n", - "file": "src/app/shared/enums/tracking-event.ts" - } - ], - "src/app/shared/enums/tracking-goal.ts": [ - { - "name": "TrackingGoal", - "childs": [ - { - "name": "EXPORT_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": 1 - }, - { - "name": "VISIT_GITHUB", - "deprecated": false, - "deprecationMessage": "", - "value": 2 - }, - { - "name": "INSTALL_PWA", - "deprecated": false, - "deprecationMessage": "", - "value": 4 - }, - { - "name": "GENERATE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": 5 - }, - { - "name": "SAVE_PALETTE", - "deprecated": false, - "deprecationMessage": "", - "value": 6 - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "

Tracking goals for Matomo

\n", - "file": "src/app/shared/enums/tracking-goal.ts" - } - ], - "src/app/editor/editor.component.ts": [ - { - "name": "UpdateType", - "childs": [ - { - "name": "HEX", - "deprecated": false, - "deprecationMessage": "", - "value": "hex" - }, - { - "name": "HUE", - "deprecated": false, - "deprecationMessage": "", - "value": "hue" - }, - { - "name": "SATURATION", - "deprecated": false, - "deprecationMessage": "", - "value": "saturation" - }, - { - "name": "LIGHTNESS", - "deprecated": false, - "deprecationMessage": "", - "value": "lightness" - } - ], - "ctype": "miscellaneous", - "subtype": "enum", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "file": "src/app/editor/editor.component.ts" - } - ] - }, - "groupedTypeAliases": { - "src/app/shared/types/export-option.ts": [ - { - "name": "ExportOption", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "\"copy\" | \"file\"", - "file": "src/app/shared/types/export-option.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 192 - } - ], - "src/app/shared/types/color-format.ts": [ - { - "name": "HSLObject", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "literal type", - "file": "src/app/shared/types/color-format.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 187 - }, - { - "name": "RGBObject", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "literal type", - "file": "src/app/shared/types/color-format.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 187 - } - ], - "src/app/shared/utils/storybook.ts": [ - { - "name": "InputStory", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "", - "file": "src/app/shared/utils/storybook.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 193 - } - ], - "src/app/layout/types/language.ts": [ - { - "name": "Language", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "\"en\" | \"de\"", - "file": "src/app/layout/types/language.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "

The language type.

\n", - "kind": 192 - } - ], - "src/app/shared/types/theme.ts": [ - { - "name": "Theme", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "\"light\" | \"dark\"", - "file": "src/app/shared/types/theme.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "

A theme type.

\n", - "kind": 192 - } - ], - "src/app/shared/data-access/analytics.service.ts": [ - { - "name": "TrackingEvent", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "literal type", - "file": "src/app/shared/data-access/analytics.service.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 187 - } - ] - } - }, - "routes": [], - "coverage": { - "count": 15, - "status": "low", - "files": [ - { - "filePath": "src/app/app.component.ts", - "type": "component", - "linktype": "component", - "name": "AppComponent", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/app.config.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "appConfig", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/editor/data-access/color-editor.service.ts", - "type": "class", - "linktype": "classe", - "name": "ColorEditorServiceMock", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/editor/data-access/color-editor.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "ColorEditorService", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/editor/editor.component.ts", - "type": "component", - "linktype": "component", - "name": "EditorComponent", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/editor/ui/editor-range/editor-range.component.ts", - "type": "component", - "linktype": "component", - "name": "EditorRangeComponent", - "coveragePercent": 85, - "coverageCount": "6/7", - "status": "very-good" - }, - { - "filePath": "src/app/editor/utils/color-wheel.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "hueToWheel", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/editor/utils/color-wheel.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "wheelToHue", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/export/data-access/export-modal.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "ExportModalService", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/export/export-modal.component.ts", - "type": "component", - "linktype": "component", - "name": "ExportModalComponent", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/export/ui/export-download/export-download.component.ts", - "type": "component", - "linktype": "component", - "name": "ExportDownloadComponent", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/export/ui/export-format/export-format.component.ts", - "type": "component", - "linktype": "component", - "name": "ExportFormatComponent", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/export/ui/export-success/export-success.component.ts", - "type": "component", - "linktype": "component", - "name": "ExportSuccessComponent", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/export/ui/request-format/request-format.component.ts", - "type": "component", - "linktype": "component", - "name": "RequestFormatComponent", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/home/data-access/home.service.ts", - "type": "class", - "linktype": "classe", - "name": "HomeServiceMock", - "coveragePercent": 0, - "coverageCount": "0/5", - "status": "low" - }, - { - "filePath": "src/app/home/data-access/home.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "HomeService", - "coveragePercent": 0, - "coverageCount": "0/6", - "status": "low" - }, - { - "filePath": "src/app/home/home.component.ts", - "type": "component", - "linktype": "component", - "name": "HomeComponent", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/home/ui/home-generator/home-generator.component.ts", - "type": "component", - "linktype": "component", - "name": "HomeGeneratorComponent", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/home/ui/home-manual/home-manual.component.ts", - "type": "component", - "linktype": "component", - "name": "HomeManualComponent", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/home/ui/home-support/home-support.component.ts", - "type": "component", - "linktype": "component", - "name": "HomeSupportComponent", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/imprint/imprint.component.ts", - "type": "component", - "linktype": "component", - "name": "ImprintComponent", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/layout/constants/languages.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "LANGUAGE_OPTIONS", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/layout/constants/themes.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "THEME_OPTIONS", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/layout/layout.component.ts", - "type": "component", - "linktype": "component", - "name": "LayoutComponent", - "coveragePercent": 0, - "coverageCount": "0/6", - "status": "low" - }, - { - "filePath": "src/app/layout/types/language.ts", - "type": "interface", - "linktype": "interface", - "name": "LanguageOption", - "coveragePercent": 100, - "coverageCount": "4/4", - "status": "very-good" - }, - { - "filePath": "src/app/layout/types/navigation-entry.ts", - "type": "interface", - "linktype": "interface", - "name": "NavigationEntry", - "coveragePercent": 100, - "coverageCount": "5/5", - "status": "very-good" - }, - { - "filePath": "src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.ts", - "type": "component", - "linktype": "component", - "name": "LayoutAnalyticsConsentComponent", - "coveragePercent": 0, - "coverageCount": "0/5", - "status": "low" - }, - { - "filePath": "src/app/layout/ui/layout-footer/layout-footer.component.ts", - "type": "component", - "linktype": "component", - "name": "LayoutFooterComponent", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/layout/ui/layout-navigation/layout-navigation.component.ts", - "type": "component", - "linktype": "component", - "name": "LayoutNavigationComponent", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/layout/ui/layout-options/layout-options.component.ts", - "type": "component", - "linktype": "component", - "name": "LayoutOptionsComponent", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/loading/loading.component.ts", - "type": "component", - "linktype": "component", - "name": "LoadingComponent", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/preview/preview.component.ts", - "type": "component", - "linktype": "component", - "name": "PreviewComponent", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/constants/export-format.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "EXPORT_FORMATS", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/constants/palette-scheme.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "PALETTE_SCHEMES", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/constants/tailwind-colors.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "sky", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/analytics.service.ts", - "type": "class", - "linktype": "classe", - "name": "AnalyticsServiceMock", - "coveragePercent": 0, - "coverageCount": "0/8", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/analytics.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "AnalyticsService", - "coveragePercent": 37, - "coverageCount": "3/8", - "status": "medium" - }, - { - "filePath": "src/app/shared/data-access/color-name.service.ts", - "type": "class", - "linktype": "classe", - "name": "ColorNameServiceMock", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/color-name.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "ColorNameService", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/color-name.service.ts", - "type": "interface", - "linktype": "interface", - "name": "ColorMapEntry", - "coveragePercent": 0, - "coverageCount": "0/5", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/color.service.ts", - "type": "class", - "linktype": "classe", - "name": "ColorServiceMock", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/color.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "ColorService", - "coveragePercent": 33, - "coverageCount": "1/3", - "status": "medium" - }, - { - "filePath": "src/app/shared/data-access/dialog.service.ts", - "type": "class", - "linktype": "classe", - "name": "DialogServiceMock", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/dialog.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "DialogService", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/export.service.ts", - "type": "class", - "linktype": "classe", - "name": "ExportServiceMock", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/export.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "ExportService", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/language.service.ts", - "type": "class", - "linktype": "classe", - "name": "LanguageServiceMock", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/language.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "LanguageService", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/mobile.service.ts", - "type": "class", - "linktype": "classe", - "name": "MobileServiceMock", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/mobile.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "MobileService", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/offline.service.ts", - "type": "class", - "linktype": "classe", - "name": "OfflineServiceMock", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/offline.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "OfflineService", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/palette.service.ts", - "type": "class", - "linktype": "classe", - "name": "PaletteServiceMock", - "coveragePercent": 0, - "coverageCount": "0/5", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/palette.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "PaletteService", - "coveragePercent": 0, - "coverageCount": "0/5", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/pwa.service.ts", - "type": "class", - "linktype": "classe", - "name": "PwaServiceMock", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/pwa.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "PwaService", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/theme.service.ts", - "type": "class", - "linktype": "classe", - "name": "ThemeServiceMock", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/theme.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "ThemeService", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/toast.service.ts", - "type": "class", - "linktype": "classe", - "name": "ToastServiceMock", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/toast.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "ToastService", - "coveragePercent": 50, - "coverageCount": "2/4", - "status": "medium" - }, - { - "filePath": "src/app/shared/data-access/version.service.ts", - "type": "class", - "linktype": "classe", - "name": "VersionServiceMock", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/data-access/version.service.ts", - "type": "injectable", - "linktype": "injectable", - "name": "VersionService", - "coveragePercent": 0, - "coverageCount": "0/4", - "status": "low" - }, - { - "filePath": "src/app/shared/formatter/css.formatter.ts", - "type": "class", - "linktype": "classe", - "name": "CssFormatter", - "coveragePercent": 0, - "coverageCount": "0/7", - "status": "low" - }, - { - "filePath": "src/app/shared/formatter/less.formatter.ts", - "type": "class", - "linktype": "classe", - "name": "LessFormatter", - "coveragePercent": 0, - "coverageCount": "0/7", - "status": "low" - }, - { - "filePath": "src/app/shared/formatter/scss.formatter.ts", - "type": "class", - "linktype": "classe", - "name": "ScssFormatter", - "coveragePercent": 0, - "coverageCount": "0/7", - "status": "low" - }, - { - "filePath": "src/app/shared/formatter/tailwind.formatter.ts", - "type": "class", - "linktype": "classe", - "name": "TailwindFormatter", - "coveragePercent": 0, - "coverageCount": "0/7", - "status": "low" - }, - { - "filePath": "src/app/shared/interfaces/formatter.interface.ts", - "type": "interface", - "linktype": "interface", - "name": "Formatter", - "coveragePercent": 0, - "coverageCount": "0/7", - "status": "low" - }, - { - "filePath": "src/app/shared/interfaces/toast.interface.ts", - "type": "interface", - "linktype": "interface", - "name": "Toast", - "coveragePercent": 100, - "coverageCount": "4/4", - "status": "very-good" - }, - { - "filePath": "src/app/shared/interfaces/toast.interface.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "ToastTimeouts", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/model/color.model.ts", - "type": "class", - "linktype": "classe", - "name": "Color", - "coveragePercent": 0, - "coverageCount": "0/8", - "status": "low" - }, - { - "filePath": "src/app/shared/model/palette.model.ts", - "type": "class", - "linktype": "classe", - "name": "Palette", - "coveragePercent": 0, - "coverageCount": "0/9", - "status": "low" - }, - { - "filePath": "src/app/shared/model/shade.model.ts", - "type": "class", - "linktype": "classe", - "name": "Shade", - "coveragePercent": 0, - "coverageCount": "0/9", - "status": "low" - }, - { - "filePath": "src/app/shared/model/value.model.ts", - "type": "class", - "linktype": "classe", - "name": "Value", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/shared/types/theme.ts", - "type": "interface", - "linktype": "interface", - "name": "ThemeOption", - "coveragePercent": 75, - "coverageCount": "3/4", - "status": "good" - }, - { - "filePath": "src/app/shared/ui/accordion/accordion.component.ts", - "type": "component", - "linktype": "component", - "name": "AccordionComponent", - "coveragePercent": 85, - "coverageCount": "6/7", - "status": "very-good" - }, - { - "filePath": "src/app/shared/ui/color-input/color-input.component.ts", - "type": "component", - "linktype": "component", - "name": "ColorInputComponent", - "coveragePercent": 66, - "coverageCount": "4/6", - "status": "good" - }, - { - "filePath": "src/app/shared/ui/dropdown-menu/dropdown-menu.component.ts", - "type": "component", - "linktype": "component", - "name": "DropdownMenuComponent", - "coveragePercent": 84, - "coverageCount": "11/13", - "status": "very-good" - }, - { - "filePath": "src/app/shared/ui/no-palette/no-palette.component.ts", - "type": "component", - "linktype": "component", - "name": "NoPaletteComponent", - "coveragePercent": 50, - "coverageCount": "1/2", - "status": "medium" - }, - { - "filePath": "src/app/shared/ui/toast/toast.component.ts", - "type": "component", - "linktype": "component", - "name": "ToastComponent", - "coveragePercent": 75, - "coverageCount": "3/4", - "status": "good" - }, - { - "filePath": "src/app/shared/utils/dialog-mock.ts", - "type": "class", - "linktype": "classe", - "name": "DialogMock", - "coveragePercent": 0, - "coverageCount": "0/3", - "status": "low" - }, - { - "filePath": "src/app/shared/utils/is-running-test.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "IS_RUNNING_TEST", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/shared/utils/matomo-title-interceptor.ts", - "type": "class", - "linktype": "classe", - "name": "MatomoTitleInterceptor", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - }, - { - "filePath": "src/app/shared/utils/matomo-tracker-mock.ts", - "type": "class", - "linktype": "classe", - "name": "MatomoTrackerMock", - "coveragePercent": 0, - "coverageCount": "0/6", - "status": "low" - }, - { - "filePath": "src/app/shared/utils/perceived-brightness.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "perceivedBrightnessFromHex", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/shared/utils/perceived-brightness.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "perceivedBrightnessFromRGB", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/shared/utils/sleep.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "sleep", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/shared/utils/storybook.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "createStory", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/utils/sw-update-mock.ts", - "type": "class", - "linktype": "classe", - "name": "SwUpdateMock", - "coveragePercent": 0, - "coverageCount": "0/6", - "status": "low" - }, - { - "filePath": "src/app/shared/utils/sw-update-mock.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "newVersion", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/utils/sw-update-mock.ts", - "type": "variable", - "linktype": "miscellaneous", - "linksubtype": "variable", - "name": "oldVersion", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, - { - "filePath": "src/app/shared/utils/text-color.ts", - "type": "function", - "linktype": "miscellaneous", - "linksubtype": "function", - "name": "textColor", - "coveragePercent": 100, - "coverageCount": "1/1", - "status": "very-good" - }, - { - "filePath": "src/app/view/ui/view-palette/view-palette.component.ts", - "type": "component", - "linktype": "component", - "name": "ViewPaletteComponent", - "coveragePercent": 0, - "coverageCount": "0/6", - "status": "low" - }, - { - "filePath": "src/app/view/view.component.ts", - "type": "component", - "linktype": "component", - "name": "ViewComponent", - "coveragePercent": 0, - "coverageCount": "0/9", - "status": "low" - }, - { - "filePath": "src/stories/button.component.ts", - "type": "component", - "linktype": "component", - "name": "ButtonComponent", - "coveragePercent": 83, - "coverageCount": "5/6", - "status": "very-good" - }, - { - "filePath": "src/stories/header.component.ts", - "type": "component", - "linktype": "component", - "name": "HeaderComponent", - "coveragePercent": 0, - "coverageCount": "0/5", - "status": "low" - }, - { - "filePath": "src/stories/page.component.ts", - "type": "component", - "linktype": "component", - "name": "PageComponent", - "coveragePercent": 0, - "coverageCount": "0/5", - "status": "low" - }, - { - "filePath": "src/stories/user.ts", - "type": "interface", - "linktype": "interface", - "name": "User", - "coveragePercent": 0, - "coverageCount": "0/2", - "status": "low" - } - ] - } -} \ No newline at end of file diff --git a/src/app/export/export-modal.component.stories.ts b/src/app/export/export-modal.component.stories.ts new file mode 100644 index 0000000..b905284 --- /dev/null +++ b/src/app/export/export-modal.component.stories.ts @@ -0,0 +1,33 @@ +import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog'; +import { Meta, applicationConfig } from '@storybook/angular'; +import { Tailwind } from '../shared/constants/tailwind-colors'; +import { AnalyticsService, AnalyticsServiceMock } from '../shared/data-access/analytics.service'; +import { ExportService, ExportServiceMock } from '../shared/data-access/export.service'; +import { ToastService, ToastServiceMock } from '../shared/data-access/toast.service'; +import { createStory } from '../shared/utils/storybook'; +import { ExportModalComponent } from './export-modal.component'; + +const meta: Meta = { + title: 'Export/Export', + component: ExportModalComponent, + tags: ['autodocs'], + decorators: [ + applicationConfig({ + providers: [ + { provide: DIALOG_DATA, useValue: { palette: Tailwind } }, + { + provide: DialogRef, + useValue: { + close: (): void => {} + } + }, + { provide: ToastService, useClass: ToastServiceMock }, + { provide: ExportService, useClass: ExportServiceMock }, + { provide: AnalyticsService, useClass: AnalyticsServiceMock } + ] + }) + ] +}; +export default meta; + +export const Export = createStory({}); diff --git a/src/app/export/ui/export-download/export-download.component.stories.ts b/src/app/export/ui/export-download/export-download.component.stories.ts new file mode 100644 index 0000000..8f49246 --- /dev/null +++ b/src/app/export/ui/export-download/export-download.component.stories.ts @@ -0,0 +1,30 @@ +import { Meta } from '@storybook/angular'; +import { ExportFormat } from '../../../shared/constants/export-format'; +import { createStory } from '../../../shared/utils/storybook'; +import { ExportDownloadComponent } from './export-download.component'; + +const meta: Meta = { + title: 'Export/Download', + component: ExportDownloadComponent, + tags: ['autodocs'], + argTypes: { + exportFormat: { + control: 'select', + type: { + name: 'enum', + value: Object.values(ExportFormat).filter((format) => format !== ExportFormat.OTHER), + required: true + } + }, + choseDownloadFormat: { + table: { + disable: true + } + } + } +}; +export default meta; + +export const Download = createStory({ + args: { exportFormat: ExportFormat.LESS } +}); diff --git a/src/app/export/ui/export-format/export-format.component.stories.ts b/src/app/export/ui/export-format/export-format.component.stories.ts new file mode 100644 index 0000000..93f683a --- /dev/null +++ b/src/app/export/ui/export-format/export-format.component.stories.ts @@ -0,0 +1,19 @@ +import { Meta } from '@storybook/angular'; +import { createStory } from '../../../shared/utils/storybook'; +import { ExportFormatComponent } from './export-format.component'; + +const meta: Meta = { + title: 'Export/Format', + component: ExportFormatComponent, + tags: ['autodocs'], + argTypes: { + choseExportFormat: { + table: { + disable: true + } + } + } +}; +export default meta; + +export const Format = createStory({}); diff --git a/src/app/export/ui/export-success/export-success.component.stories.ts b/src/app/export/ui/export-success/export-success.component.stories.ts new file mode 100644 index 0000000..c760d22 --- /dev/null +++ b/src/app/export/ui/export-success/export-success.component.stories.ts @@ -0,0 +1,40 @@ +import { Meta } from '@storybook/angular'; +import { ExportFormat } from '../../../shared/constants/export-format'; +import { createStory } from '../../../shared/utils/storybook'; +import { ExportSuccessComponent } from './export-success.component'; + +const meta: Meta = { + title: 'Export/Success', + component: ExportSuccessComponent, + tags: ['autodocs'], + argTypes: { + exportOption: { + type: { + name: 'enum', + value: ['copy', 'file'], + required: true + } + }, + exportFormat: { + control: 'select', + type: { + name: 'enum', + value: Object.values(ExportFormat).filter((format) => format !== ExportFormat.OTHER), + required: true + } + }, + openDocumentation: { + table: { + disable: true + } + } + } +}; +export default meta; + +export const Copy = createStory({ + args: { exportOption: 'copy', exportFormat: ExportFormat.CSS } +}); +export const File = createStory({ + args: { exportOption: 'file', exportFormat: ExportFormat.TAILWIND } +}); diff --git a/src/app/export/ui/request-format/request-format.component.stories.ts b/src/app/export/ui/request-format/request-format.component.stories.ts new file mode 100644 index 0000000..f75bb9e --- /dev/null +++ b/src/app/export/ui/request-format/request-format.component.stories.ts @@ -0,0 +1,12 @@ +import { Meta } from '@storybook/angular'; +import { createStory } from '../../../shared/utils/storybook'; +import { RequestFormatComponent } from './request-format.component'; + +const meta: Meta = { + title: 'Export/Request Format', + component: RequestFormatComponent, + tags: ['autodocs'] +}; +export default meta; + +export const RequestFormat = createStory({}); diff --git a/src/app/home/ui/home-generator/home-generator.component.stories.ts b/src/app/home/ui/home-generator/home-generator.component.stories.ts new file mode 100644 index 0000000..ce92eee --- /dev/null +++ b/src/app/home/ui/home-generator/home-generator.component.stories.ts @@ -0,0 +1,34 @@ +import { Meta } from '@storybook/angular'; +import { PaletteScheme } from '../../../shared/constants/palette-scheme'; +import { createStory } from '../../../shared/utils/storybook'; +import { HomeGeneratorComponent } from './home-generator.component'; + +const meta: Meta = { + title: 'Home/Generator', + component: HomeGeneratorComponent, + tags: ['autodocs'], + argTypes: { + scheme: { + control: { + type: 'select' + }, + type: { + name: 'enum', + value: Object.values(PaletteScheme) + } + }, + generate: { + table: { + disable: true + } + } + } +}; +export default meta; + +export const Generator = createStory({ + args: { + hex: '#3B82F6', + scheme: PaletteScheme.RAINBOW + } +}); diff --git a/src/app/home/ui/home-manual/home-manual.component.stories.ts b/src/app/home/ui/home-manual/home-manual.component.stories.ts new file mode 100644 index 0000000..5a1b0a3 --- /dev/null +++ b/src/app/home/ui/home-manual/home-manual.component.stories.ts @@ -0,0 +1,12 @@ +import { Meta } from '@storybook/angular'; +import { createStory } from '../../../shared/utils/storybook'; +import { HomeManualComponent } from './home-manual.component'; + +const meta: Meta = { + title: 'Home/Manual', + component: HomeManualComponent, + tags: ['autodocs'] +}; +export default meta; + +export const Manual = createStory({}); diff --git a/src/app/home/ui/home-support/home-support.component.stories.ts b/src/app/home/ui/home-support/home-support.component.stories.ts new file mode 100644 index 0000000..c7e53f0 --- /dev/null +++ b/src/app/home/ui/home-support/home-support.component.stories.ts @@ -0,0 +1,12 @@ +import { Meta } from '@storybook/angular'; +import { createStory } from '../../../shared/utils/storybook'; +import { HomeSupportComponent } from './home-support.component'; + +const meta: Meta = { + title: 'Home/Support', + component: HomeSupportComponent, + tags: ['autodocs'] +}; +export default meta; + +export const Support = createStory({}); diff --git a/src/app/layout/constants/navigation-entries.ts b/src/app/layout/constants/navigation-entries.ts new file mode 100644 index 0000000..39fa781 --- /dev/null +++ b/src/app/layout/constants/navigation-entries.ts @@ -0,0 +1,23 @@ +import { heroAdjustmentsHorizontalSolid, heroRectangleGroupSolid, heroSwatchSolid } from '@ng-icons/heroicons/solid'; +import { NavigationEntry } from '../types/navigation-entry'; + +export const NAVIGATION_ENTRIES: Array = [ + { + title: 'layout.navigation.generate.title', + path: '/', + icon: heroSwatchSolid, + description: 'layout.navigation.generate.description' + }, + { + title: 'layout.navigation.view.title', + path: '/view', + icon: heroAdjustmentsHorizontalSolid, + description: 'layout.navigation.view.description' + }, + { + title: 'layout.navigation.preview.title', + path: '/preview', + icon: heroRectangleGroupSolid, + description: 'layout.navigation.preview.description' + } +]; diff --git a/src/app/layout/layout.component.css b/src/app/layout/layout.component.css index 49a04a9..09bdaf3 100644 --- a/src/app/layout/layout.component.css +++ b/src/app/layout/layout.component.css @@ -2,3 +2,7 @@ padding-top: var(--header-height); padding-bottom: calc(var(--bottom-navigation-height) + var(--analytics-consent-height)); } + +#consent { + margin-bottom: var(--bottom-navigation-height); +} diff --git a/src/app/layout/layout.component.html b/src/app/layout/layout.component.html index edcd2c5..25b014b 100644 --- a/src/app/layout/layout.component.html +++ b/src/app/layout/layout.component.html @@ -42,7 +42,12 @@

} @if (showAnalyticsConsent()) { - + } @if (isMobile()) {
>('bottomNavigation'); private readonly _analytics = viewChild(LayoutAnalyticsConsentComponent); - protected readonly navigationEntries: Array = [ - { - title: 'layout.navigation.generate.title', - path: '/', - icon: heroSwatchSolid, - description: 'layout.navigation.generate.description' - }, - { - title: 'layout.navigation.view.title', - path: '/view', - icon: heroAdjustmentsHorizontalSolid, - description: 'layout.navigation.view.description' - }, - { - title: 'layout.navigation.preview.title', - path: '/preview', - icon: heroRectangleGroupSolid, - description: 'layout.navigation.preview.description' - } - ]; + protected readonly navigationEntries = NAVIGATION_ENTRIES; protected readonly isMobile = this._mobileService.isMobile; protected readonly language = this._languageService.language; protected readonly theme = this._themeService.theme; diff --git a/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.css b/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.css deleted file mode 100644 index baaa2b2..0000000 --- a/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.css +++ /dev/null @@ -1,4 +0,0 @@ -:host { - @apply fixed bottom-0 left-0 block; - margin-bottom: var(--bottom-navigation-height); -} diff --git a/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.stories.ts b/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.stories.ts new file mode 100644 index 0000000..8ada1c0 --- /dev/null +++ b/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.stories.ts @@ -0,0 +1,30 @@ +import { ActivatedRoute } from '@angular/router'; +import { Meta, applicationConfig } from '@storybook/angular'; +import { createStory } from '../../../shared/utils/storybook'; +import { LayoutAnalyticsConsentComponent } from './layout-analytics-consent.component'; + +const meta: Meta = { + title: 'Layout/Analytics Consent', + component: LayoutAnalyticsConsentComponent, + tags: ['autodocs'], + decorators: [ + applicationConfig({ + providers: [{ provide: ActivatedRoute, useValue: {} }] + }) + ], + argTypes: { + consent: { + table: { + disable: true + } + }, + height: { + table: { + disable: true + } + } + } +}; +export default meta; + +export const AnalyticsConsent = createStory({}); diff --git a/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.ts b/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.ts index d3bb82e..7aaa942 100644 --- a/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.ts +++ b/src/app/layout/ui/layout-analytics-consent/layout-analytics-consent.component.ts @@ -7,7 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; standalone: true, imports: [TranslateModule, RouterLink], templateUrl: './layout-analytics-consent.component.html', - styleUrl: './layout-analytics-consent.component.css' + styles: ':host { display: block; }' }) export class LayoutAnalyticsConsentComponent { protected readonly matomo = 'Matomo'; diff --git a/src/app/layout/ui/layout-footer/layout-footer.component.stories.ts b/src/app/layout/ui/layout-footer/layout-footer.component.stories.ts new file mode 100644 index 0000000..b476f54 --- /dev/null +++ b/src/app/layout/ui/layout-footer/layout-footer.component.stories.ts @@ -0,0 +1,27 @@ +import { ActivatedRoute } from '@angular/router'; +import { Meta, applicationConfig } from '@storybook/angular'; +import { VersionService, VersionServiceMock } from '../../../shared/data-access/version.service'; +import { createStory } from '../../../shared/utils/storybook'; +import { LayoutFooterComponent } from './layout-footer.component'; + +const meta: Meta = { + title: 'Layout/Footer', + component: LayoutFooterComponent, + tags: ['autodocs'], + decorators: [ + applicationConfig({ + providers: [ + { provide: ActivatedRoute, useValue: {} }, + { provide: VersionService, useClass: VersionServiceMock } + ] + }) + ], + argTypes: {} +}; +export default meta; + +export const Footer = createStory({ + args: { + logoAsset: 'assets/rainbow-palette-dark.svg' + } +}); diff --git a/src/app/layout/ui/layout-navigation/layout-navigation.component.stories.ts b/src/app/layout/ui/layout-navigation/layout-navigation.component.stories.ts new file mode 100644 index 0000000..4b150d5 --- /dev/null +++ b/src/app/layout/ui/layout-navigation/layout-navigation.component.stories.ts @@ -0,0 +1,61 @@ +/* eslint-disable storybook/story-exports */ +import { Component } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { Meta, applicationConfig } from '@storybook/angular'; +import { createStory } from '../../../shared/utils/storybook'; +import { NAVIGATION_ENTRIES } from '../../constants/navigation-entries'; +import { LayoutNavigationComponent } from './layout-navigation.component'; + +@Component({ + selector: 'rp-test', + template: '' +}) +class TestComponent {} + +const meta: Meta = { + title: 'Layout/Navigation', + component: LayoutNavigationComponent, + tags: ['autodocs'], + decorators: [ + applicationConfig({ + providers: [provideRouter([{ path: '**/*', component: TestComponent }])] + }) + ], + argTypes: { + navigationEntries: { + type: { + name: 'array', + value: { + name: 'object', + value: { + title: { + name: 'string', + required: true + }, + path: { + name: 'string', + required: true + }, + icon: { + name: 'string', + required: true + }, + description: { + name: 'string', + required: true + } + } + }, + required: true + } + } + } +}; +export default meta; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Navigation = createStory({ + args: { + navigationEntries: NAVIGATION_ENTRIES + } +}); diff --git a/src/app/layout/ui/layout-options/layout-options.component.stories.ts b/src/app/layout/ui/layout-options/layout-options.component.stories.ts new file mode 100644 index 0000000..dc44459 --- /dev/null +++ b/src/app/layout/ui/layout-options/layout-options.component.stories.ts @@ -0,0 +1,37 @@ +import { Meta } from '@storybook/angular'; +import { createStory } from '../../../shared/utils/storybook'; +import { LayoutOptionsComponent } from './layout-options.component'; + +const meta: Meta = { + title: 'Layout/Options', + component: LayoutOptionsComponent, + tags: ['autodocs'], + argTypes: { + language: { + defaultValue: 'en', + type: { + name: 'enum', + value: ['en', 'de'], + required: true + }, + control: 'select' + }, + theme: { + defaultValue: 'light', + type: { + name: 'enum', + value: ['light', 'dark'], + required: true + }, + control: 'select' + } + } +}; +export default meta; + +export const Options = createStory({ + args: { + language: 'en', + theme: 'light' + } +}); diff --git a/src/app/shared/constants/tailwind-colors.ts b/src/app/shared/constants/tailwind-colors.ts index 7bd9c72..f0b5629 100644 --- a/src/app/shared/constants/tailwind-colors.ts +++ b/src/app/shared/constants/tailwind-colors.ts @@ -1,5 +1,215 @@ -import { Color, Shade, Value } from '../model'; +import { Color, Palette, Shade, Value } from '../model'; +export const slate = new Color( + [ + new Shade(50, new Value('#f8fafc'), true), + new Shade(100, new Value('#f1f5f9'), true), + new Shade(200, new Value('#e2e8f0'), true), + new Shade(300, new Value('#cbd5e1'), true), + new Shade(400, new Value('#94a3b8'), true), + new Shade(500, new Value('#64748b'), true), + new Shade(600, new Value('#475569'), true), + new Shade(700, new Value('#334155'), true), + new Shade(800, new Value('#1e293b'), true), + new Shade(900, new Value('#0f172a'), true) + ], + 'Slate' +); +export const gray = new Color( + [ + new Shade(50, new Value('#f9fafb'), true), + new Shade(100, new Value('#f3f4f6'), true), + new Shade(200, new Value('#e5e7eb'), true), + new Shade(300, new Value('#d1d5db'), true), + new Shade(400, new Value('#9ca3af'), true), + new Shade(500, new Value('#6b7280'), true), + new Shade(600, new Value('#4b5563'), true), + new Shade(700, new Value('#374151'), true), + new Shade(800, new Value('#1f2937'), true), + new Shade(900, new Value('#111827'), true) + ], + 'Gray' +); +export const zinc = new Color( + [ + new Shade(50, new Value('#fafafa'), true), + new Shade(100, new Value('#f4f4f5'), true), + new Shade(200, new Value('#e4e4e7'), true), + new Shade(300, new Value('#d4d4d8'), true), + new Shade(400, new Value('#a1a1aa'), true), + new Shade(500, new Value('#71717a'), true), + new Shade(600, new Value('#52525b'), true), + new Shade(700, new Value('#3f3f46'), true), + new Shade(800, new Value('#27272a'), true), + new Shade(900, new Value('#18181b'), true) + ], + 'Zinc' +); +export const neutral = new Color( + [ + new Shade(50, new Value('#fafafa'), true), + new Shade(100, new Value('#f5f5f5'), true), + new Shade(200, new Value('#e5e5e5'), true), + new Shade(300, new Value('#d4d4d4'), true), + new Shade(400, new Value('#a3a3a3'), true), + new Shade(500, new Value('#737373'), true), + new Shade(600, new Value('#525252'), true), + new Shade(700, new Value('#404040'), true), + new Shade(800, new Value('#262626'), true), + new Shade(900, new Value('#171717'), true) + ], + 'Neutral' +); +export const stone = new Color( + [ + new Shade(50, new Value('#fafaf9'), true), + new Shade(100, new Value('#f5f5f4'), true), + new Shade(200, new Value('#e7e5e4'), true), + new Shade(300, new Value('#d6d3d1'), true), + new Shade(400, new Value('#a8a29e'), true), + new Shade(500, new Value('#78716c'), true), + new Shade(600, new Value('#57534e'), true), + new Shade(700, new Value('#44403c'), true), + new Shade(800, new Value('#292524'), true), + new Shade(900, new Value('#1c1917'), true) + ], + 'Stone' +); +export const red = new Color( + [ + new Shade(50, new Value('#fef2f2'), true), + new Shade(100, new Value('#fee2e2'), true), + new Shade(200, new Value('#fecaca'), true), + new Shade(300, new Value('#fca5a5'), true), + new Shade(400, new Value('#f87171'), true), + new Shade(500, new Value('#ef4444'), true), + new Shade(600, new Value('#dc2626'), true), + new Shade(700, new Value('#b91c1c'), true), + new Shade(800, new Value('#991b1b'), true), + new Shade(900, new Value('#7f1d1d'), true) + ], + 'Red' +); +export const orange = new Color( + [ + new Shade(50, new Value('#fff7ed'), true), + new Shade(100, new Value('#ffedd5'), true), + new Shade(200, new Value('#fed7aa'), true), + new Shade(300, new Value('#fdba74'), true), + new Shade(400, new Value('#fb923c'), true), + new Shade(500, new Value('#f97316'), true), + new Shade(600, new Value('#ea580c'), true), + new Shade(700, new Value('#c2410c'), true), + new Shade(800, new Value('#9a3412'), true), + new Shade(900, new Value('#7c2d12'), true) + ], + 'Orange' +); +export const amber = new Color( + [ + new Shade(50, new Value('#fffbeb'), true), + new Shade(100, new Value('#fef3c7'), true), + new Shade(200, new Value('#fde68a'), true), + new Shade(300, new Value('#fcd34d'), true), + new Shade(400, new Value('#fbbf24'), true), + new Shade(500, new Value('#f59e0b'), true), + new Shade(600, new Value('#d97706'), true), + new Shade(700, new Value('#b45309'), true), + new Shade(800, new Value('#92400e'), true), + new Shade(900, new Value('#78350f'), true) + ], + 'Amber' +); +export const yellow = new Color( + [ + new Shade(50, new Value('#fefce8'), true), + new Shade(100, new Value('#fef9c3'), true), + new Shade(200, new Value('#fef08a'), true), + new Shade(300, new Value('#fde047'), true), + new Shade(400, new Value('#facc15'), true), + new Shade(500, new Value('#eab308'), true), + new Shade(600, new Value('#ca8a04'), true), + new Shade(700, new Value('#a16207'), true), + new Shade(800, new Value('#854d0e'), true), + new Shade(900, new Value('#713f12'), true) + ], + 'Yellow' +); +export const lime = new Color( + [ + new Shade(50, new Value('#f7fee7'), true), + new Shade(100, new Value('#ecfccb'), true), + new Shade(200, new Value('#d9f99d'), true), + new Shade(300, new Value('#bef264'), true), + new Shade(400, new Value('#a3e635'), true), + new Shade(500, new Value('#84cc16'), true), + new Shade(600, new Value('#65a30d'), true), + new Shade(700, new Value('#4d7c0f'), true), + new Shade(800, new Value('#3f6212'), true), + new Shade(900, new Value('#365314'), true) + ], + 'Lime' +); +export const green = new Color( + [ + new Shade(50, new Value('#f0fdf4'), true), + new Shade(100, new Value('#dcfce7'), true), + new Shade(200, new Value('#bbf7d0'), true), + new Shade(300, new Value('#86efac'), true), + new Shade(400, new Value('#4ade80'), true), + new Shade(500, new Value('#22c55e'), true), + new Shade(600, new Value('#16a34a'), true), + new Shade(700, new Value('#15803d'), true), + new Shade(800, new Value('#166534'), true), + new Shade(900, new Value('#14532d'), true) + ], + 'Green' +); +export const emerald = new Color( + [ + new Shade(50, new Value('#ecfdf5'), true), + new Shade(100, new Value('#d1fae5'), true), + new Shade(200, new Value('#a7f3d0'), true), + new Shade(300, new Value('#6ee7b7'), true), + new Shade(400, new Value('#34d399'), true), + new Shade(500, new Value('#10b981'), true), + new Shade(600, new Value('#059669'), true), + new Shade(700, new Value('#047857'), true), + new Shade(800, new Value('#065f46'), true), + new Shade(900, new Value('#064e3b'), true) + ], + 'Emerald' +); +export const teal = new Color( + [ + new Shade(50, new Value('#f0fdfa'), true), + new Shade(100, new Value('#ccfbf1'), true), + new Shade(200, new Value('#99f6e4'), true), + new Shade(300, new Value('#5eead4'), true), + new Shade(400, new Value('#2dd4bf'), true), + new Shade(500, new Value('#14b8a6'), true), + new Shade(600, new Value('#0d9488'), true), + new Shade(700, new Value('#0f766e'), true), + new Shade(800, new Value('#115e59'), true), + new Shade(900, new Value('#134e4a'), true) + ], + 'Teal' +); +export const cyan = new Color( + [ + new Shade(50, new Value('#ecfeff'), true), + new Shade(100, new Value('#cffafe'), true), + new Shade(200, new Value('#a5f3fc'), true), + new Shade(300, new Value('#67e8f9'), true), + new Shade(400, new Value('#22d3ee'), true), + new Shade(500, new Value('#06b6d4'), true), + new Shade(600, new Value('#0891b2'), true), + new Shade(700, new Value('#0e7490'), true), + new Shade(800, new Value('#155e75'), true), + new Shade(900, new Value('#164e63'), true) + ], + 'Cyan' +); export const sky = new Color( [ new Shade(50, new Value('#f0f9ff'), true), @@ -15,3 +225,130 @@ export const sky = new Color( ], 'Sky' ); +export const blue = new Color( + [ + new Shade(50, new Value('#eff6ff'), true), + new Shade(100, new Value('#dbeafe'), true), + new Shade(200, new Value('#bfdbfe'), true), + new Shade(300, new Value('#93c5fd'), true), + new Shade(400, new Value('#60a5fa'), true), + new Shade(500, new Value('#3b82f6'), true), + new Shade(600, new Value('#2563eb'), true), + new Shade(700, new Value('#1d4ed8'), true), + new Shade(800, new Value('#1e40af'), true), + new Shade(900, new Value('#1e3a8a'), true) + ], + 'Blue' +); +export const indigo = new Color( + [ + new Shade(50, new Value('#eef2ff'), true), + new Shade(100, new Value('#e0e7ff'), true), + new Shade(200, new Value('#c7d2fe'), true), + new Shade(300, new Value('#a5b4fc'), true), + new Shade(400, new Value('#818cf8'), true), + new Shade(500, new Value('#6366f1'), true), + new Shade(600, new Value('#4f46e5'), true), + new Shade(700, new Value('#4338ca'), true), + new Shade(800, new Value('#3730a3'), true), + new Shade(900, new Value('#312e81'), true) + ], + 'Indigo' +); +export const violet = new Color( + [ + new Shade(50, new Value('#f5f3ff'), true), + new Shade(100, new Value('#ede9fe'), true), + new Shade(200, new Value('#ddd6fe'), true), + new Shade(300, new Value('#c4b5fd'), true), + new Shade(400, new Value('#a78bfa'), true), + new Shade(500, new Value('#8b5cf6'), true), + new Shade(600, new Value('#7c3aed'), true), + new Shade(700, new Value('#6d28d9'), true), + new Shade(800, new Value('#5b21b6'), true), + new Shade(900, new Value('#4c1d95'), true) + ], + 'Violet' +); +export const purple = new Color( + [ + new Shade(50, new Value('#faf5ff'), true), + new Shade(100, new Value('#f3e8ff'), true), + new Shade(200, new Value('#e9d5ff'), true), + new Shade(300, new Value('#d8b4fe'), true), + new Shade(400, new Value('#c084fc'), true), + new Shade(500, new Value('#a855f7'), true), + new Shade(600, new Value('#9333ea'), true), + new Shade(700, new Value('#7e22ce'), true), + new Shade(800, new Value('#6b21a8'), true), + new Shade(900, new Value('#581c87'), true) + ], + 'Purple' +); +export const fuchsia = new Color( + [ + new Shade(50, new Value('#fdf4ff'), true), + new Shade(100, new Value('#fae8ff'), true), + new Shade(200, new Value('#f5d0fe'), true), + new Shade(300, new Value('#f0abfc'), true), + new Shade(400, new Value('#e879f9'), true), + new Shade(500, new Value('#d946ef'), true), + new Shade(600, new Value('#c026d3'), true), + new Shade(700, new Value('#a21caf'), true), + new Shade(800, new Value('#86198f'), true), + new Shade(900, new Value('#701a75'), true) + ], + 'Fuchsia' +); +export const pink = new Color( + [ + new Shade(50, new Value('#fdf2f9'), true), + new Shade(100, new Value('#fce7f3'), true), + new Shade(200, new Value('#fbcfe8'), true), + new Shade(300, new Value('#f9a8d4'), true), + new Shade(400, new Value('#f472b6'), true), + new Shade(500, new Value('#ec4899'), true), + new Shade(600, new Value('#db2777'), true), + new Shade(700, new Value('#be185d'), true), + new Shade(800, new Value('#9d174d'), true), + new Shade(900, new Value('#831843'), true) + ], + 'Pink' +); +export const rose = new Color( + [ + new Shade(50, new Value('#fff1f2'), true), + new Shade(100, new Value('#ffe4e6'), true), + new Shade(200, new Value('#fecdd3'), true), + new Shade(300, new Value('#fda4af'), true), + new Shade(400, new Value('#fb7185'), true), + new Shade(500, new Value('#f43f5e'), true), + new Shade(600, new Value('#e11d48'), true), + new Shade(700, new Value('#be123c'), true), + new Shade(800, new Value('#9f1239'), true), + new Shade(900, new Value('#881337'), true) + ], + 'Rose' +); + +export const TailwindRainbow = new Palette('Tailwind Rainbow', [ + red, + orange, + amber, + yellow, + lime, + green, + emerald, + teal, + cyan, + sky, + blue, + indigo, + violet, + purple, + fuchsia, + pink, + rose +]); +export const TailwindGrays = new Palette('Tailwind Grays', [slate, gray, zinc, neutral, stone]); +export const Tailwind = new Palette('Tailwind', [...TailwindRainbow.colors, ...TailwindGrays.colors]); diff --git a/src/app/shared/ui/toast/toast.component.stories.ts b/src/app/shared/ui/toast/toast.component.stories.ts index 75b0f81..7bf3691 100644 --- a/src/app/shared/ui/toast/toast.component.stories.ts +++ b/src/app/shared/ui/toast/toast.component.stories.ts @@ -1,16 +1,10 @@ -import { action } from '@storybook/addon-actions'; -import { Meta, argsToTemplate } from '@storybook/angular'; +import { Meta } from '@storybook/angular'; import { createStory } from '../../utils/storybook'; import { ToastComponent } from './toast.component'; -export const actionsData = { - close: action('close') -}; - const meta: Meta = { title: 'Shared/Toast', component: ToastComponent, - excludeStories: /.*Data$/, tags: ['autodocs'], argTypes: { toast: { @@ -33,15 +27,7 @@ const meta: Meta = { } } } - }, - // @ts-expect-error - Args are missing protected internal properties - render: (args: ToastComponent) => ({ - props: { - ...args, - close: actionsData.close - }, - template: `` - }) + } }; export default meta; diff --git a/src/app/view/ui/view-palette/view-palette.component.stories.ts b/src/app/view/ui/view-palette/view-palette.component.stories.ts new file mode 100644 index 0000000..9063d6e --- /dev/null +++ b/src/app/view/ui/view-palette/view-palette.component.stories.ts @@ -0,0 +1,56 @@ +import { Meta } from '@storybook/angular'; +import { TailwindGrays, TailwindRainbow } from '../../../shared/constants/tailwind-colors'; +import { createStory } from '../../../shared/utils/storybook'; +import { ViewPaletteComponent } from './view-palette.component'; + +const meta: Meta = { + title: 'View/Palette', + component: ViewPaletteComponent, + tags: ['autodocs'], + argTypes: { + palette: { + type: { + name: 'object', + value: { + name: { + name: 'string', + required: true + } + }, + required: true + } + }, + renameColor: { + table: { + disable: true + } + }, + editColor: { + table: { + disable: true + } + }, + removeColor: { + table: { + disable: true + } + }, + copyShade: { + table: { + disable: true + } + } + } +}; +export default meta; + +export const Rainbow = createStory({ + args: { + palette: TailwindRainbow + } +}); +export const Gray = createStory({ + args: { + palette: TailwindGrays + } +});