From 3cef4a917cc71ffaed84dcde4c8bcaba22da8d0f Mon Sep 17 00:00:00 2001 From: Florent BENOIT Date: Wed, 6 Nov 2024 10:26:40 +0100 Subject: [PATCH] feat: add catalog UI to browse the catalog (#72) * feat: add catalog UI to browse the catalog related to https://github.com/containers/podman-desktop/issues/8972 Signed-off-by: Florent Benoit Change-Id: I9e121490ccf65275ec1273f716d9d7a8e26d791a --- src/app.d.ts | 18 + src/lib/Appearance.spec.ts | 114 +++ src/lib/Appearance.svelte | 27 + src/lib/Markdown.svelte | 103 +++ src/lib/api/extensions-info.ts | 48 ++ src/lib/extensions.svelte.spec.ts | 59 ++ src/lib/extensions.svelte.ts | 116 +++ src/lib/ui/ExtensionByCategoryCard.spec.ts | 101 +++ src/lib/ui/ExtensionByCategoryCard.svelte | 39 + src/lib/ui/ExtensionIcon.spec.ts | 70 ++ src/lib/ui/ExtensionIcon.svelte | 21 + src/lib/ui/ExtensionsByCategory.spec.ts | 50 ++ src/lib/ui/ExtensionsByCategory.svelte | 22 + src/lib/ui/ExtensionsDetails.spec.ts | 66 ++ src/lib/ui/ExtensionsDetails.svelte | 30 + src/lib/ui/ExtensionsDetailsBottom.spec.ts | 70 ++ src/lib/ui/ExtensionsDetailsBottom.svelte | 58 ++ src/lib/ui/ExtensionsDetailsTop.spec.ts | 73 ++ src/lib/ui/ExtensionsDetailsTop.svelte | 26 + src/lib/ui/ExtensionsList.spec.ts | 51 ++ src/lib/ui/ExtensionsList.svelte | 13 + src/podman-desktop.css | 956 ++++++++++----------- src/routes/+page.svelte | 47 +- src/routes/+page.ts | 18 + src/routes/page.svelte.spec.ts | 43 +- 25 files changed, 1750 insertions(+), 489 deletions(-) create mode 100644 src/lib/Appearance.spec.ts create mode 100644 src/lib/Appearance.svelte create mode 100644 src/lib/Markdown.svelte create mode 100644 src/lib/api/extensions-info.ts create mode 100644 src/lib/extensions.svelte.spec.ts create mode 100644 src/lib/extensions.svelte.ts create mode 100644 src/lib/ui/ExtensionByCategoryCard.spec.ts create mode 100644 src/lib/ui/ExtensionByCategoryCard.svelte create mode 100644 src/lib/ui/ExtensionIcon.spec.ts create mode 100644 src/lib/ui/ExtensionIcon.svelte create mode 100644 src/lib/ui/ExtensionsByCategory.spec.ts create mode 100644 src/lib/ui/ExtensionsByCategory.svelte create mode 100644 src/lib/ui/ExtensionsDetails.spec.ts create mode 100644 src/lib/ui/ExtensionsDetails.svelte create mode 100644 src/lib/ui/ExtensionsDetailsBottom.spec.ts create mode 100644 src/lib/ui/ExtensionsDetailsBottom.svelte create mode 100644 src/lib/ui/ExtensionsDetailsTop.spec.ts create mode 100644 src/lib/ui/ExtensionsDetailsTop.svelte create mode 100644 src/lib/ui/ExtensionsList.spec.ts create mode 100644 src/lib/ui/ExtensionsList.svelte diff --git a/src/app.d.ts b/src/app.d.ts index 62af1c3..ddb61da 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,3 +1,21 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + /// // See https://kit.svelte.dev/docs/types#app diff --git a/src/lib/Appearance.spec.ts b/src/lib/Appearance.spec.ts new file mode 100644 index 0000000..d34de65 --- /dev/null +++ b/src/lib/Appearance.spec.ts @@ -0,0 +1,114 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import '@testing-library/jest-dom/vitest'; + +import { render } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import Appearance from './Appearance.svelte'; + +const addEventListenerMock = vi.fn(); + +beforeEach(() => { + vi.resetAllMocks(); + window.matchMedia = vi.fn().mockReturnValue({ + matches: false, + addEventListener: addEventListenerMock, + removeEventListener: vi.fn(), + }); +}); + +function getRootElement(container: HTMLElement): HTMLElement { + // get root html element + let rootElement: HTMLElement | null = container; + let loop = 0; + while (rootElement?.parentElement && loop < 10) { + rootElement = container.parentElement; + loop++; + } + return rootElement as HTMLElement; +} + +function getRootElementClassesValue(container: HTMLElement): string | undefined { + return getRootElement(container).classList.value; +} + +test('check initial light theme', async () => { + const { baseElement } = render(Appearance, {}); + // expect to have no (dark) class as OS is using light + await vi.waitFor(() => expect(getRootElementClassesValue(baseElement)).toBe('light')); +}); + +test('check initial dark theme', async () => { + window.matchMedia = vi.fn().mockReturnValue({ + matches: true, + addEventListener: addEventListenerMock, + removeEventListener: vi.fn(), + }); + const { baseElement } = render(Appearance, {}); + // expect to have no (dark) class as OS is using light + await vi.waitFor(() => expect(getRootElementClassesValue(baseElement)).toBe('dark')); +}); + +test('Expect event being changed when changing the default appearance on the operating system', async () => { + // initial is dark + window.matchMedia = vi.fn().mockReturnValue({ + matches: true, + addEventListener: addEventListenerMock, + removeEventListener: vi.fn(), + }); + + let userCallback: () => void = () => {}; + addEventListenerMock.mockImplementation((event: string, callback: () => void) => { + if (event === 'change') { + userCallback = callback; + } + }); + + const { baseElement } = render(Appearance, {}); + + // check it's dark + expect(getRootElementClassesValue(baseElement)).toBe('dark'); + + // now change to light + window.matchMedia = vi.fn().mockReturnValue({ + matches: false, + addEventListener: addEventListenerMock, + removeEventListener: vi.fn(), + }); + + // call the callback on matchMedia + userCallback(); + + // check if it's now light + await vi.waitFor(() => expect(getRootElementClassesValue(baseElement)).toBe('light')); + + // now change to dark + window.matchMedia = vi.fn().mockReturnValue({ + matches: true, + addEventListener: addEventListenerMock, + removeEventListener: vi.fn(), + }); + + // call again the callback on matchMedia + userCallback(); + + // check if it's now dark + await vi.waitFor(() => expect(getRootElementClassesValue(baseElement)).toBe('dark')); +}); diff --git a/src/lib/Appearance.svelte b/src/lib/Appearance.svelte new file mode 100644 index 0000000..570447c --- /dev/null +++ b/src/lib/Appearance.svelte @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/src/lib/Markdown.svelte b/src/lib/Markdown.svelte new file mode 100644 index 0000000..30333de --- /dev/null +++ b/src/lib/Markdown.svelte @@ -0,0 +1,103 @@ + + +
+ + {@html html} +
+ + + diff --git a/src/lib/api/extensions-info.ts b/src/lib/api/extensions-info.ts new file mode 100644 index 0000000..e43a18f --- /dev/null +++ b/src/lib/api/extensions-info.ts @@ -0,0 +1,48 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ +export interface CatalogExtensionVersionFileInfo { + assetType: 'icon' | 'LICENSE' | 'README'; + data: string; +} + +export interface CatalogExtensionVersionInfo { + version: string; + podmanDesktopVersion?: string; + ociUri: string; + preview: boolean; + lastUpdated: Date; + files: CatalogExtensionVersionFileInfo[]; +} + +export interface CatalogExtensionInfo { + id: string; + publisherName: string; + publisherDisplayName: string; + extensionName: string; + categories: string[]; + shortDescription: string; + displayName: string; + keywords: string[]; + unlisted: boolean; + versions: CatalogExtensionVersionInfo[]; +} + +export interface ExtensionByCategoryInfo { + category: string; + extensions: CatalogExtensionInfo[]; +} diff --git a/src/lib/extensions.svelte.spec.ts b/src/lib/extensions.svelte.spec.ts new file mode 100644 index 0000000..e9bb688 --- /dev/null +++ b/src/lib/extensions.svelte.spec.ts @@ -0,0 +1,59 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import { beforeEach, describe, expect, test, vi } from 'vitest'; + +import catalogOfExtensions from '../../static/api/extensions.json'; +import type { CatalogExtensionInfo } from './api/extensions-info'; +import { catalogExtensions, getCurrentExtension, initCatalog, setCurrentExtension } from './extensions.svelte'; + +const fetchMock = vi.fn(); + +describe('check catalog', () => { + beforeEach(() => { + vi.resetAllMocks(); + catalogExtensions.length = 0; + window.fetch = fetchMock; + }); + + test('check fetch', async () => { + fetchMock.mockResolvedValue({ + ok: true, + json: async () => ({ extensions: catalogOfExtensions.extensions }), + }); + await initCatalog(); + + expect(vi.mocked(fetch)).toHaveBeenCalledWith('https://registry.podman-desktop.io/api/extensions.json'); + + // check we have extensions in the catalog + expect(catalogExtensions.length).toBeGreaterThan(0); + }); +}); + +test('check current extension', () => { + const currentExtension = getCurrentExtension(); + expect(currentExtension.value).toBeUndefined(); + setCurrentExtension({ id: 'dummy' } as unknown as CatalogExtensionInfo); + + // check again the value + expect(currentExtension.value).toEqual({ id: 'dummy' }); + + // unset + setCurrentExtension(undefined); + expect(currentExtension.value).toBeUndefined(); +}); diff --git a/src/lib/extensions.svelte.ts b/src/lib/extensions.svelte.ts new file mode 100644 index 0000000..7922b00 --- /dev/null +++ b/src/lib/extensions.svelte.ts @@ -0,0 +1,116 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import type { + CatalogExtensionInfo, + CatalogExtensionVersionFileInfo, + CatalogExtensionVersionInfo, +} from './api/extensions-info'; + +const REGISTRY_URL = 'https://registry.podman-desktop.io/api/extensions.json'; + +// a list of all extensions in the catalog +export const catalogExtensions: CatalogExtensionInfo[] = $state([]); + +// the current extension that is being viewed for details +// undefined means no extension is being viewed +let currentExtension: CatalogExtensionInfo | undefined = $state(undefined); + +export function getCurrentExtension(): { value: CatalogExtensionInfo | undefined } { + return { + get value(): CatalogExtensionInfo | undefined { + return currentExtension; + }, + }; +} + +export function setCurrentExtension(extension: CatalogExtensionInfo | undefined): void { + currentExtension = extension; +} + +export async function initCatalog(): Promise { + // grab JSON from the registry + const response = await fetch(REGISTRY_URL); + + if (!response.ok) { + throw new Error(`Failed to fetch extensions from ${REGISTRY_URL}`); + } + // parse the JSON + let data: unknown; + try { + data = await response.json(); + } catch (error: unknown) { + throw new Error(`Failed to parse JSON from ${REGISTRY_URL}: ${String(error)}`); + } + const extensions: CatalogExtensionInfo[] = []; + if (typeof data === 'object' && data && 'extensions' in data) { + const rawExtensions = data.extensions; + + if (Array.isArray(rawExtensions)) { + for (const rawExtension of rawExtensions) { + if (typeof rawExtension === 'object' && rawExtension) { + const versions: CatalogExtensionVersionInfo[] = []; + if (Array.isArray(rawExtension.versions)) { + for (const rawVersion of rawExtension.versions) { + if (typeof rawVersion === 'object' && rawVersion) { + const files: CatalogExtensionVersionFileInfo[] = []; + if (Array.isArray(rawVersion.files)) { + for (const rawFile of rawVersion.files) { + if (typeof rawFile === 'object' && rawFile) { + files.push({ + assetType: String(rawFile.assetType) as 'icon' | 'LICENSE' | 'README', + data: String(rawFile.data), + }); + } + } + } + const version: CatalogExtensionVersionInfo = { + version: String(rawVersion.version), + podmanDesktopVersion: String(rawVersion.podmanDesktopVersion), + ociUri: String(rawVersion.ociUri), + preview: Boolean(rawVersion.preview), + lastUpdated: new Date(String(rawVersion.lastUpdated)), + files, + }; + versions.push(version); + } + } + } + + const extension: CatalogExtensionInfo = { + id: `${rawExtension.publisher?.publisherName}.${rawExtension.extensionName}`, + publisherName: String(rawExtension.publisher?.publisherName), + publisherDisplayName: String(rawExtension.publisher?.displayName), + keywords: Array.isArray(rawExtension.keywords) ? rawExtension.keywords.map(String) : [], + unlisted: Boolean(rawExtension.unlisted), + shortDescription: String(rawExtension.shortDescription), + displayName: String(rawExtension.displayName), + extensionName: String(rawExtension.extensionName), + categories: Array.isArray(rawExtension.categories) ? rawExtension.categories.map(String) : [], + versions, + }; + extensions.push(extension); + } + } + } + } + + // update the catalog + catalogExtensions.length = 0; + catalogExtensions.push(...extensions); +} diff --git a/src/lib/ui/ExtensionByCategoryCard.spec.ts b/src/lib/ui/ExtensionByCategoryCard.spec.ts new file mode 100644 index 0000000..fe6bd00 --- /dev/null +++ b/src/lib/ui/ExtensionByCategoryCard.spec.ts @@ -0,0 +1,101 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import '@testing-library/jest-dom/vitest'; + +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import type { CatalogExtensionInfo } from '$lib/api/extensions-info'; +import { getCurrentExtension } from '$lib/extensions.svelte'; + +import ExtensionByCategoryCard from './ExtensionByCategoryCard.svelte'; + +beforeEach(() => { + vi.resetAllMocks(); +}); + +test('check content', async () => { + const version = { + version: '1.0.0', + files: [ + { + assetType: 'icon', + data: 'http://fake-podman-desktop/my.icon.png', + }, + ], + }; + const extension: CatalogExtensionInfo = { + id: 'dummy1', + displayName: 'My Display Name', + shortDescription: 'My Description', + versions: [version], + } as unknown as CatalogExtensionInfo; + + render(ExtensionByCategoryCard, { extension }); + + // check icon is there + const icon = screen.getByRole('img', { hidden: true }); + expect(icon).toBeInTheDocument(); + + // check display name is there + const displayName = screen.getByText('My Display Name'); + expect(displayName).toBeInTheDocument(); + + // check description is there + const description = screen.getByText('My Description'); + expect(description).toBeInTheDocument(); + + // check latest version is there + const latestVersion = screen.getByText('v1.0.0'); + expect(latestVersion).toBeInTheDocument(); + + // check click on the card + // no current version + expect(getCurrentExtension().value).toBeUndefined(); + const card = screen.getByRole('button'); + expect(card).toBeInTheDocument(); + await fireEvent.click(card); + + //check the click + expect(getCurrentExtension().value).toEqual(extension); +}); + +test('check version with v prefix', async () => { + const version = { + version: 'v2.0.0', + files: [ + { + assetType: 'icon', + data: 'http://fake-podman-desktop/my.icon.png', + }, + ], + }; + const extension: CatalogExtensionInfo = { + id: 'dummy1', + displayName: 'My Display Name', + shortDescription: 'My Description', + versions: [version], + } as unknown as CatalogExtensionInfo; + + render(ExtensionByCategoryCard, { extension }); + + // check we don't add another v prefix + const latestVersion = screen.getByText('v2.0.0'); + expect(latestVersion).toBeInTheDocument(); +}); diff --git a/src/lib/ui/ExtensionByCategoryCard.svelte b/src/lib/ui/ExtensionByCategoryCard.svelte new file mode 100644 index 0000000..c73e7b2 --- /dev/null +++ b/src/lib/ui/ExtensionByCategoryCard.svelte @@ -0,0 +1,39 @@ + + + diff --git a/src/lib/ui/ExtensionIcon.spec.ts b/src/lib/ui/ExtensionIcon.spec.ts new file mode 100644 index 0000000..98ce60f --- /dev/null +++ b/src/lib/ui/ExtensionIcon.spec.ts @@ -0,0 +1,70 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import type { CatalogExtensionInfo } from '$lib/api/extensions-info'; + +import ExtensionIcon from './ExtensionIcon.svelte'; + +beforeEach(() => { + vi.resetAllMocks(); +}); + +test('check icon', async () => { + const version = { + files: [ + { + assetType: 'icon', + data: 'http://fake-podman-desktop/my.icon.png', + }, + ], + }; + const extension: CatalogExtensionInfo = { id: 'dummy1', versions: [version] } as unknown as CatalogExtensionInfo; + + render(ExtensionIcon, { extension }); + + const icon = screen.getByRole('img', { hidden: true }); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveAttribute('src', 'http://fake-podman-desktop/my.icon.png'); + expect(icon).toHaveClass('w-8'); + expect(icon).toHaveClass('h-8'); +}); + +test('check large size', async () => { + const version = { + files: [ + { + assetType: 'icon', + data: 'http://fake-podman-desktop/my.icon.png', + }, + ], + }; + const extension: CatalogExtensionInfo = { id: 'dummy1', versions: [version] } as unknown as CatalogExtensionInfo; + + render(ExtensionIcon, { extension, size: 'xl' }); + + const icon = screen.getByRole('img', { hidden: true }); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveAttribute('src', 'http://fake-podman-desktop/my.icon.png'); + expect(icon).toHaveClass('w-20'); + expect(icon).toHaveClass('h-20'); +}); diff --git a/src/lib/ui/ExtensionIcon.svelte b/src/lib/ui/ExtensionIcon.svelte new file mode 100644 index 0000000..cb6a16d --- /dev/null +++ b/src/lib/ui/ExtensionIcon.svelte @@ -0,0 +1,21 @@ + + +{#if iconUrl} + {extension.extensionName} +{/if} diff --git a/src/lib/ui/ExtensionsByCategory.spec.ts b/src/lib/ui/ExtensionsByCategory.spec.ts new file mode 100644 index 0000000..72659b3 --- /dev/null +++ b/src/lib/ui/ExtensionsByCategory.spec.ts @@ -0,0 +1,50 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import type { CatalogExtensionInfo, ExtensionByCategoryInfo } from '$lib/api/extensions-info'; + +import ExtensionsByCategory from './ExtensionsByCategory.svelte'; + +beforeEach(() => { + vi.resetAllMocks(); + window.open = vi.fn(); +}); + +test('check content', async () => { + const extensionByCategoryInfo: ExtensionByCategoryInfo = { + category: 'category1', + extensions: [{ id: 'dummy1', displayName: 'My Display Name' } as unknown as CatalogExtensionInfo], + }; + render(ExtensionsByCategory, { extensionByCategoryInfo }); + // check region + const region = screen.getByRole('region', { name: 'Category name' }); + expect(region).toBeInTheDocument(); + + // check block of extensions + const regionExtensions = screen.getByRole('region', { name: 'Extensions of category category1' }); + expect(regionExtensions).toBeInTheDocument(); + + // check extension dummy1 is there + const extensionDummy1 = screen.getByText('My Display Name'); + expect(extensionDummy1).toBeInTheDocument(); +}); diff --git a/src/lib/ui/ExtensionsByCategory.svelte b/src/lib/ui/ExtensionsByCategory.svelte new file mode 100644 index 0000000..3efce60 --- /dev/null +++ b/src/lib/ui/ExtensionsByCategory.svelte @@ -0,0 +1,22 @@ + + +
+ +
{extensionByCategoryInfo.category}
+ + +
+ {#each extensionByCategoryInfo.extensions as extension} + + {/each} +
+
diff --git a/src/lib/ui/ExtensionsDetails.spec.ts b/src/lib/ui/ExtensionsDetails.spec.ts new file mode 100644 index 0000000..a1b9ace --- /dev/null +++ b/src/lib/ui/ExtensionsDetails.spec.ts @@ -0,0 +1,66 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import type { CatalogExtensionInfo } from '$lib/api/extensions-info'; + +import ExtensionsDetails from './ExtensionsDetails.svelte'; + +const fetchMock = vi.fn(); + +beforeEach(() => { + vi.resetAllMocks(); + window.fetch = fetchMock; +}); + +test('check content', async () => { + const version = { + version: '1.0.0', + files: [ + { + assetType: 'README', + data: 'http://fake-podman-desktop/my.readme', + }, + ], + }; + const extension: CatalogExtensionInfo = { + id: 'dummy1', + displayName: 'My Display Name', + shortDescription: 'My Description', + versions: [version], + } as unknown as CatalogExtensionInfo; + + // mock readme + fetchMock.mockResolvedValue({ + ok: true, + text: async () => '## hello world', + }); + + render(ExtensionsDetails, { extension }); + + // check display name is there + const displayName = screen.getByText('My Display Name'); + expect(displayName).toBeInTheDocument(); + + // check readme is there + await vi.waitFor(() => expect(screen.getByText('hello world')).toBeInTheDocument()); +}); diff --git a/src/lib/ui/ExtensionsDetails.svelte b/src/lib/ui/ExtensionsDetails.svelte new file mode 100644 index 0000000..0de194b --- /dev/null +++ b/src/lib/ui/ExtensionsDetails.svelte @@ -0,0 +1,30 @@ + + +
+
+ +
+
+ {#if extension} + + + + + + {/if} +
+
diff --git a/src/lib/ui/ExtensionsDetailsBottom.spec.ts b/src/lib/ui/ExtensionsDetailsBottom.spec.ts new file mode 100644 index 0000000..2f53896 --- /dev/null +++ b/src/lib/ui/ExtensionsDetailsBottom.spec.ts @@ -0,0 +1,70 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import type { CatalogExtensionInfo } from '$lib/api/extensions-info'; + +import ExtensionsDetailsBottom from './ExtensionsDetailsBottom.svelte'; + +const fetchMock = vi.fn(); + +beforeEach(() => { + vi.resetAllMocks(); + window.fetch = fetchMock; +}); + +test('check content', async () => { + const version = { + version: '1.0.0', + files: [ + { + assetType: 'README', + data: 'http://fake-podman-desktop/my.readme', + }, + ], + }; + const extension: CatalogExtensionInfo = { + id: 'dummy1', + displayName: 'My Display Name', + shortDescription: 'My Description', + versions: [version], + } as unknown as CatalogExtensionInfo; + + // mock readme + fetchMock.mockResolvedValue({ + ok: true, + text: async () => '## hello world', + }); + + render(ExtensionsDetailsBottom, { extension }); + + // check readme is there + await vi.waitFor(() => expect(screen.getByText('hello world')).toBeInTheDocument()); + + // should be formatted as h2 + const h2 = screen.getByRole('heading', { level: 2 }); + expect(h2).toBeInTheDocument(); + expect(h2).toHaveTextContent('hello world'); + + // check the click + expect(fetchMock).toHaveBeenCalledWith('http://fake-podman-desktop/my.readme'); +}); diff --git a/src/lib/ui/ExtensionsDetailsBottom.svelte b/src/lib/ui/ExtensionsDetailsBottom.svelte new file mode 100644 index 0000000..73cc50c --- /dev/null +++ b/src/lib/ui/ExtensionsDetailsBottom.svelte @@ -0,0 +1,58 @@ + + +
+
+ {#if readmeContent} + + {:else} +
Loading...
+ {/if} +
+ + +
+ + + + + + + + {#each versions as version} + + + + + {/each} + +
VersionReleased
{version.version}{moment(version.lastUpdated).format('YYYY-MM-DD')}
+
+
diff --git a/src/lib/ui/ExtensionsDetailsTop.spec.ts b/src/lib/ui/ExtensionsDetailsTop.spec.ts new file mode 100644 index 0000000..5c7bc50 --- /dev/null +++ b/src/lib/ui/ExtensionsDetailsTop.spec.ts @@ -0,0 +1,73 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import '@testing-library/jest-dom/vitest'; + +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import type { CatalogExtensionInfo } from '$lib/api/extensions-info'; + +import ExtensionsDetailsTop from './ExtensionsDetailsTop.svelte'; + +beforeEach(() => { + vi.resetAllMocks(); + window.open = vi.fn(); +}); + +test('check content', async () => { + const version = { + version: '1.0.0', + files: [ + { + assetType: 'icon', + data: 'http://fake-podman-desktop/my.icon.png', + }, + ], + }; + const extension: CatalogExtensionInfo = { + id: 'dummy1', + displayName: 'My Display Name', + shortDescription: 'My Description', + versions: [version], + } as unknown as CatalogExtensionInfo; + + render(ExtensionsDetailsTop, { extension }); + + // check icon is there + const icon = screen.getByRole('img', { hidden: true }); + expect(icon).toBeInTheDocument(); + + // check display name is there + const displayName = screen.getByText('My Display Name'); + expect(displayName).toBeInTheDocument(); + + // check description is there + const description = screen.getByText('My Description'); + expect(description).toBeInTheDocument(); + + // check install button is there + const installButton = screen.getByRole('button', { name: 'Install' }); + expect(installButton).toBeInTheDocument(); + + // check click on the button + await fireEvent.click(installButton); + + // check the click + expect(window.open).toHaveBeenCalledWith('podman-desktop:extension/dummy1'); +}); diff --git a/src/lib/ui/ExtensionsDetailsTop.svelte b/src/lib/ui/ExtensionsDetailsTop.svelte new file mode 100644 index 0000000..b2ecaa9 --- /dev/null +++ b/src/lib/ui/ExtensionsDetailsTop.svelte @@ -0,0 +1,26 @@ + + +
+
+ +
+
{extension.displayName}
+
{extension.shortDescription}
+
+
+
+ +
+
diff --git a/src/lib/ui/ExtensionsList.spec.ts b/src/lib/ui/ExtensionsList.spec.ts new file mode 100644 index 0000000..f29563b --- /dev/null +++ b/src/lib/ui/ExtensionsList.spec.ts @@ -0,0 +1,51 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import '@testing-library/jest-dom/vitest'; + +import { render, screen } from '@testing-library/svelte'; +import { beforeEach, expect, test, vi } from 'vitest'; + +import type { CatalogExtensionInfo, ExtensionByCategoryInfo } from '$lib/api/extensions-info'; + +import ExtensionsList from './ExtensionsList.svelte'; + +beforeEach(() => { + vi.resetAllMocks(); +}); + +test('check categories', async () => { + const extensionsByCategories: ExtensionByCategoryInfo[] = [ + { + category: 'category1', + extensions: [{ id: 'dummy1' } as unknown as CatalogExtensionInfo], + }, + { + category: 'category2', + extensions: [{ id: 'dummy2' } as unknown as CatalogExtensionInfo], + }, + ]; + + render(ExtensionsList, { extensionsByCategories }); + + const category1 = screen.getByText('category1'); + expect(category1).toBeInTheDocument(); + + const category2 = screen.getByText('category2'); + expect(category2).toBeInTheDocument(); +}); diff --git a/src/lib/ui/ExtensionsList.svelte b/src/lib/ui/ExtensionsList.svelte new file mode 100644 index 0000000..4a40cf8 --- /dev/null +++ b/src/lib/ui/ExtensionsList.svelte @@ -0,0 +1,13 @@ + + +
+ {#each extensionsByCategories as extensionByCategoryInfo} + + {/each} +
diff --git a/src/podman-desktop.css b/src/podman-desktop.css index b16fd11..a92d2a8 100644 --- a/src/podman-desktop.css +++ b/src/podman-desktop.css @@ -1,483 +1,479 @@ -@scope (.light) { - :scope { - --pd-default-text: #0f0f11; - --pd-notification-dot: #6d48bf; - --pd-global-nav-bg: #f6f6f6; - --pd-global-nav-bg-border: #e4e4e4; - --pd-global-nav-icon: #5c5c5c; - --pd-global-nav-icon-hover: #4d2d87; - --pd-global-nav-icon-hover-bg: #bfa7f6; - --pd-global-nav-icon-inset-bg: #e4e4e4; - --pd-global-nav-icon-selected: #4d2d87; - --pd-global-nav-icon-selected-bg: #e4e4e4; - --pd-global-nav-icon-selected-highlight: #6d48bf; - --pd-secondary-nav-bg: #f6f6f6; - --pd-secondary-nav-header-text: #0f0f11; - --pd-secondary-nav-text: #222222; - --pd-secondary-nav-text-hover: #4d2d87; - --pd-secondary-nav-text-hover-bg: #bfa7f6; - --pd-secondary-nav-text-selected: #000; - --pd-secondary-nav-selected-bg: #e4e4e4; - --pd-secondary-nav-selected-highlight: #6d48bf; - --pd-secondary-nav-expander: #222222; - --pd-titlebar-bg: #f9fafb; - --pd-titlebar-text: #37255d; - --pd-titlebar-icon: #37255d; - --pd-titlebar-windows-hover-exit-bg: #c42b1c; - --pd-titlebar-windows-hover-bg: #dfdfdf; - --pd-content-breadcrumb: #37255d; - --pd-content-breadcrumb-2: #6d48bf; - --pd-content-header: #0f0f11; - --pd-content-text: #818181; - --pd-content-sub-header: #37255d; - --pd-content-header-icon: #6234b1; - --pd-content-card-header-text: #37255d; - --pd-content-card-bg: #fefefe; - --pd-content-card-hover-bg: #e4e4e4; - --pd-content-card-selected-bg: #efe9fe; - --pd-content-card-text: #37255d; - --pd-content-card-title: #0f0f11; - --pd-content-card-light-title: #37255d; - --pd-content-card-inset-bg: #d3d3f2; - --pd-content-card-hover-inset-bg: #b9b8e9; - --pd-content-bg: #f6f6f6; - --pd-content-card-icon: #37255d; - --pd-content-divider: #aaabac; - --pd-content-card-carousel-card-bg: #efefef; - --pd-content-card-carousel-card-hover-bg: #f6f6f6; - --pd-content-card-carousel-card-header-text: #0f0f11; - --pd-content-card-carousel-card-text: #36363d; - --pd-content-card-carousel-nav: #e4e4e4; - --pd-content-card-carousel-hover-nav: #c8c8c8; - --pd-content-card-carousel-disabled-nav: #efefef; - --pd-content-card-border: #efefef; - --pd-content-card-border-selected: #6d48bf; - --pd-invert-content-bg: #fefefe; - --pd-invert-content-header-text: #0f0f11; - --pd-invert-content-header2-text: #0f0f11; - --pd-invert-content-card-bg: #f6f6f6; - --pd-invert-content-card-header-text: #0f0f11; - --pd-invert-content-card-text: #222222; - --pd-invert-content-button-active: #6d48bf; - --pd-invert-content-button-inactive: #767676; - --pd-invert-content-info-icon: #6d48bf; - --pd-card-bg: #e4e4e4; - --pd-card-header-text: #0f0f11; - --pd-card-text: #222222; - --pd-input-field-bg: transparent; - --pd-input-field-focused-bg: #f6f6f6; - --pd-input-field-disabled-bg: transparent; - --pd-input-field-hover-bg: transparent; - --pd-input-field-focused-text: #0f0f11; - --pd-input-field-error-text: #f86847; - --pd-input-field-disabled-text: #aaabac; - --pd-input-field-hover-text: #5c5c5c; - --pd-input-field-placeholder-text: #5c5c5c; - --pd-input-field-stroke: #aaabac; - --pd-input-field-hover-stroke: #8b5cf6; - --pd-input-field-stroke-error: #f86847; - --pd-input-field-stroke-readonly: #707073; - --pd-input-field-icon: #5c5c5c; - --pd-input-field-focused-icon: #6d48bf; - --pd-input-field-disabled-icon: #aaabac; - --pd-input-field-hover-icon: #6d48bf; - --pd-input-checkbox-disabled: #5c5c5c; - --pd-input-checkbox-indeterminate: #37255d; - --pd-input-checkbox-focused-indeterminate: #6234b1; - --pd-input-checkbox-checked: #37255d; - --pd-input-checkbox-focused-checked: #6234b1; - --pd-input-checkbox-unchecked: #37255d; - --pd-input-checkbox-focused-unchecked: #6234b1; - --pd-input-toggle-off-bg: #818181; - --pd-input-toggle-off-focused-bg: #9a9a9a; - --pd-input-toggle-on-bg: #6d48bf; - --pd-input-toggle-on-focused-bg: #8b5cf6; - --pd-input-toggle-switch: #fff; - --pd-input-toggle-focused-switch: #fff; - --pd-input-toggle-on-text: #222222; - --pd-input-toggle-off-text: #222222; - --pd-input-toggle-disabled-text: #5c5c5c; - --pd-input-toggle-off-disabled-bg: #818181; - --pd-input-toggle-on-disabled-bg: #818181; - --pd-input-toggle-disabled-switch: #efefef; - --pd-table-header-text: #5c5c5c; - --pd-table-header-unsorted: #464649; - --pd-table-body-text: #707073; - --pd-table-body-text-highlight: #222222; - --pd-table-body-text-sub-secondary: #6234b1; - --pd-table-body-text-sub-highlight: #5c5c5c; - --pd-details-body-text: #36363d; - --pd-details-empty-icon: #5c5c5c; - --pd-details-empty-header: #36363d; - --pd-details-empty-sub-header: #36363d; - --pd-details-empty-cmdline-bg: #efefef; - --pd-details-empty-cmdline-text: #222222; - --pd-details-bg: #f9fafb; - --pd-details-card-bg: #e4e4e4; - --pd-details-card-header: #464649; - --pd-details-card-text: #0f0f11; - --pd-tab-text: #5c5c5c; - --pd-tab-text-highlight: #464649; - --pd-tab-highlight: #6d48bf; - --pd-tab-hover: #8b5cf6; - --pd-modal-fade: #fff; - --pd-modal-text: #464649; - --pd-modal-text-hover: #4d2d87; - --pd-modal-bg: #f9fafb; - --pd-modal-border: #c8c8c8; - --pd-modal-header-bg: #f6f6f6; - --pd-modal-header-text: #8b5cf6; - --pd-modal-header-divider: #bfa7f6; - --pd-link: #6234b1; - --pd-link-hover-bg: #0002; - --pd-button-primary-bg: #6d48bf; - --pd-button-primary-hover-bg: #8b5cf6; - --pd-button-secondary: #6d48bf; - --pd-button-secondary-hover: #8b5cf6; - --pd-button-text: #fff; - --pd-button-disabled: #b4b4b4; - --pd-button-disabled-text: #818181; - --pd-button-danger-border: #c13414; - --pd-button-danger-bg: transparent; - --pd-button-danger-text: #c13414; - --pd-button-danger-hover-text: #fff; - --pd-button-danger-hover-bg: #e5421d; - --pd-button-danger-disabled-border: #818181; - --pd-button-danger-disabled-text: #818181; - --pd-button-danger-disabled-bg: transparent; - --pd-button-tab-border: transparent; - --pd-button-tab-border-selected: #6d48bf; - --pd-button-tab-hover-border: #b4b4b4; - --pd-button-tab-text: #5c5c5c; - --pd-button-tab-text-selected: #000; - --pd-button-link-text: #6234b1; - --pd-button-link-hover-bg: #0002; - --pd-button-help-link-text: #0f0f11; - --pd-action-button-text: #36363d; - --pd-action-button-bg: #d1d1d1; - --pd-action-button-hover-bg: #f9fafb; - --pd-action-button-hover-text: #8b5cf6; - --pd-action-button-primary-text: #6d48bf; - --pd-action-button-primary-hover-text: #8b5cf6; - --pd-action-button-disabled-text: #818181; - --pd-action-button-details-text: #0f0f11; - --pd-action-button-details-bg: #f9fafb; - --pd-action-button-details-hover-text: #8b5cf6; - --pd-action-button-details-disabled-text: #818181; - --pd-action-button-details-disabled-bg: #f9fafb; - --pd-action-button-spinner: #8b5cf6; - --pd-tooltip-bg: #f9fafb; - --pd-tooltip-text: #000; - --pd-tooltip-border: #c8c8c8; - --pd-dropdown-bg: #f6f6f6; - --pd-select-bg: #e4e4e4; - --pd-dropdown-ring: #c8c8c8; - --pd-dropdown-hover-ring: #bfa7f6; - --pd-dropdown-divider: #f6f6f6; - --pd-dropdown-item-text: #27272a; - --pd-dropdown-item-hover-bg: #e4e4e4; - --pd-dropdown-item-hover-text: #8b5cf6; - --pd-dropdown-disabled-item-text: #707073; - --pd-dropdown-disabled-item-bg: #efefef; - --pd-modal-dropdown-highlight: #bfa7f6; - --pd-modal-dropdown-text: #0f0f11; - --pd-input-select-hover-text: #5c5c5c; - --pd-label-bg: #e2d6fe; - --pd-label-text: #464649; - --pd-status-running: #2b7037; - --pd-status-terminated: #c13414; - --pd-status-waiting: #d97706; - --pd-status-starting: #2b7037; - --pd-status-stopped: #5c5c5c; - --pd-status-exited: #5c5c5c; - --pd-status-not-running: #818181; - --pd-status-paused: #d97706; - --pd-status-degraded: #b45309; - --pd-status-created: #8ec792; - --pd-status-dead: #c13414; - --pd-status-unknown: #d1d1d1; - --pd-status-connected: #2b7037; - --pd-status-disconnected: #9a9a9a; - --pd-status-updated: #2f88c8; - --pd-status-ready: #f6f6f6; - --pd-status-contrast: #fff; - --pd-statusbar-bg: #37255d; - --pd-statusbar-hover-bg: #4d2d87; - --pd-statusbar-text: #fff; - --pd-onboarding-active-dot-bg: #6234b1; - --pd-onboarding-active-dot-border: #6234b1; - --pd-onboarding-inactive-dot-bg: transparent; - --pd-onboarding-inactive-dot-border: #aaabac; - --pd-state-success: #2b7037; - --pd-state-warning: #d97706; - --pd-state-error: #e5421d; - --pd-state-info: #6d48bf; - --pd-files-hidden: #f86847; - --pd-files-directory: #2f88c8; - --pd-files-symlink: #90c3e9; - --pd-files-executable: #3c8d47; - --pd-terminal-foreground: #000; - --pd-terminal-background: #fff; - --pd-terminal-cursor: #000; - --pd-terminal-selectionBackground: #000; - --pd-terminal-selectionForeground: #fff; - --pd-terminal-ansiBlack: #000; - --pd-terminal-ansiRed: #f86847; - --pd-terminal-ansiGreen: #3c8d47; - --pd-terminal-ansiYellow: #f59e0b; - --pd-terminal-ansiBlue: #2f88c8; - --pd-terminal-ansiMagenta: #8b5cf6; - --pd-terminal-ansiCyan: #2f88c8; - --pd-terminal-ansiWhite: #fff; - --pd-terminal-ansiBrightBlack: #c8c8c8; - --pd-terminal-ansiBrightRed: #e5421d; - --pd-terminal-ansiBrightGreen: #2b7037; - --pd-terminal-ansiBrightYellow: #d97706; - --pd-terminal-ansiBrightBlue: #206ca9; - --pd-terminal-ansiBrightMagenta: #6d48bf; - --pd-terminal-ansiBrightCyan: #206ca9; - --pd-terminal-ansiBrightWhite: #fff; - } +.light { + --pd-default-text: #0f0f11; + --pd-notification-dot: #6d48bf; + --pd-global-nav-bg: #f6f6f6; + --pd-global-nav-bg-border: #e4e4e4; + --pd-global-nav-icon: #5c5c5c; + --pd-global-nav-icon-hover: #4d2d87; + --pd-global-nav-icon-hover-bg: #bfa7f6; + --pd-global-nav-icon-inset-bg: #e4e4e4; + --pd-global-nav-icon-selected: #4d2d87; + --pd-global-nav-icon-selected-bg: #e4e4e4; + --pd-global-nav-icon-selected-highlight: #6d48bf; + --pd-secondary-nav-bg: #f6f6f6; + --pd-secondary-nav-header-text: #0f0f11; + --pd-secondary-nav-text: #222222; + --pd-secondary-nav-text-hover: #4d2d87; + --pd-secondary-nav-text-hover-bg: #bfa7f6; + --pd-secondary-nav-text-selected: #000; + --pd-secondary-nav-selected-bg: #e4e4e4; + --pd-secondary-nav-selected-highlight: #6d48bf; + --pd-secondary-nav-expander: #222222; + --pd-titlebar-bg: #f9fafb; + --pd-titlebar-text: #37255d; + --pd-titlebar-icon: #37255d; + --pd-titlebar-windows-hover-exit-bg: #c42b1c; + --pd-titlebar-windows-hover-bg: #dfdfdf; + --pd-content-breadcrumb: #37255d; + --pd-content-breadcrumb-2: #6d48bf; + --pd-content-header: #0f0f11; + --pd-content-text: #818181; + --pd-content-sub-header: #37255d; + --pd-content-header-icon: #6234b1; + --pd-content-card-header-text: #37255d; + --pd-content-card-bg: #fefefe; + --pd-content-card-hover-bg: #e4e4e4; + --pd-content-card-selected-bg: #efe9fe; + --pd-content-card-text: #37255d; + --pd-content-card-title: #0f0f11; + --pd-content-card-light-title: #37255d; + --pd-content-card-inset-bg: #d3d3f2; + --pd-content-card-hover-inset-bg: #b9b8e9; + --pd-content-bg: #f6f6f6; + --pd-content-card-icon: #37255d; + --pd-content-divider: #aaabac; + --pd-content-card-carousel-card-bg: #efefef; + --pd-content-card-carousel-card-hover-bg: #f6f6f6; + --pd-content-card-carousel-card-header-text: #0f0f11; + --pd-content-card-carousel-card-text: #36363d; + --pd-content-card-carousel-nav: #e4e4e4; + --pd-content-card-carousel-hover-nav: #c8c8c8; + --pd-content-card-carousel-disabled-nav: #efefef; + --pd-content-card-border: #efefef; + --pd-content-card-border-selected: #6d48bf; + --pd-invert-content-bg: #fefefe; + --pd-invert-content-header-text: #0f0f11; + --pd-invert-content-header2-text: #0f0f11; + --pd-invert-content-card-bg: #f6f6f6; + --pd-invert-content-card-header-text: #0f0f11; + --pd-invert-content-card-text: #222222; + --pd-invert-content-button-active: #6d48bf; + --pd-invert-content-button-inactive: #767676; + --pd-invert-content-info-icon: #6d48bf; + --pd-card-bg: #e4e4e4; + --pd-card-header-text: #0f0f11; + --pd-card-text: #222222; + --pd-input-field-bg: transparent; + --pd-input-field-focused-bg: #f6f6f6; + --pd-input-field-disabled-bg: transparent; + --pd-input-field-hover-bg: transparent; + --pd-input-field-focused-text: #0f0f11; + --pd-input-field-error-text: #f86847; + --pd-input-field-disabled-text: #aaabac; + --pd-input-field-hover-text: #5c5c5c; + --pd-input-field-placeholder-text: #5c5c5c; + --pd-input-field-stroke: #aaabac; + --pd-input-field-hover-stroke: #8b5cf6; + --pd-input-field-stroke-error: #f86847; + --pd-input-field-stroke-readonly: #707073; + --pd-input-field-icon: #5c5c5c; + --pd-input-field-focused-icon: #6d48bf; + --pd-input-field-disabled-icon: #aaabac; + --pd-input-field-hover-icon: #6d48bf; + --pd-input-checkbox-disabled: #5c5c5c; + --pd-input-checkbox-indeterminate: #37255d; + --pd-input-checkbox-focused-indeterminate: #6234b1; + --pd-input-checkbox-checked: #37255d; + --pd-input-checkbox-focused-checked: #6234b1; + --pd-input-checkbox-unchecked: #37255d; + --pd-input-checkbox-focused-unchecked: #6234b1; + --pd-input-toggle-off-bg: #818181; + --pd-input-toggle-off-focused-bg: #9a9a9a; + --pd-input-toggle-on-bg: #6d48bf; + --pd-input-toggle-on-focused-bg: #8b5cf6; + --pd-input-toggle-switch: #fff; + --pd-input-toggle-focused-switch: #fff; + --pd-input-toggle-on-text: #222222; + --pd-input-toggle-off-text: #222222; + --pd-input-toggle-disabled-text: #5c5c5c; + --pd-input-toggle-off-disabled-bg: #818181; + --pd-input-toggle-on-disabled-bg: #818181; + --pd-input-toggle-disabled-switch: #efefef; + --pd-table-header-text: #5c5c5c; + --pd-table-header-unsorted: #464649; + --pd-table-body-text: #707073; + --pd-table-body-text-highlight: #222222; + --pd-table-body-text-sub-secondary: #6234b1; + --pd-table-body-text-sub-highlight: #5c5c5c; + --pd-details-body-text: #36363d; + --pd-details-empty-icon: #5c5c5c; + --pd-details-empty-header: #36363d; + --pd-details-empty-sub-header: #36363d; + --pd-details-empty-cmdline-bg: #efefef; + --pd-details-empty-cmdline-text: #222222; + --pd-details-bg: #f9fafb; + --pd-details-card-bg: #e4e4e4; + --pd-details-card-header: #464649; + --pd-details-card-text: #0f0f11; + --pd-tab-text: #5c5c5c; + --pd-tab-text-highlight: #464649; + --pd-tab-highlight: #6d48bf; + --pd-tab-hover: #8b5cf6; + --pd-modal-fade: #fff; + --pd-modal-text: #464649; + --pd-modal-text-hover: #4d2d87; + --pd-modal-bg: #f9fafb; + --pd-modal-border: #c8c8c8; + --pd-modal-header-bg: #f6f6f6; + --pd-modal-header-text: #8b5cf6; + --pd-modal-header-divider: #bfa7f6; + --pd-link: #6234b1; + --pd-link-hover-bg: #0002; + --pd-button-primary-bg: #6d48bf; + --pd-button-primary-hover-bg: #8b5cf6; + --pd-button-secondary: #6d48bf; + --pd-button-secondary-hover: #8b5cf6; + --pd-button-text: #fff; + --pd-button-disabled: #b4b4b4; + --pd-button-disabled-text: #818181; + --pd-button-danger-border: #c13414; + --pd-button-danger-bg: transparent; + --pd-button-danger-text: #c13414; + --pd-button-danger-hover-text: #fff; + --pd-button-danger-hover-bg: #e5421d; + --pd-button-danger-disabled-border: #818181; + --pd-button-danger-disabled-text: #818181; + --pd-button-danger-disabled-bg: transparent; + --pd-button-tab-border: transparent; + --pd-button-tab-border-selected: #6d48bf; + --pd-button-tab-hover-border: #b4b4b4; + --pd-button-tab-text: #5c5c5c; + --pd-button-tab-text-selected: #000; + --pd-button-link-text: #6234b1; + --pd-button-link-hover-bg: #0002; + --pd-button-help-link-text: #0f0f11; + --pd-action-button-text: #36363d; + --pd-action-button-bg: #d1d1d1; + --pd-action-button-hover-bg: #f9fafb; + --pd-action-button-hover-text: #8b5cf6; + --pd-action-button-primary-text: #6d48bf; + --pd-action-button-primary-hover-text: #8b5cf6; + --pd-action-button-disabled-text: #818181; + --pd-action-button-details-text: #0f0f11; + --pd-action-button-details-bg: #f9fafb; + --pd-action-button-details-hover-text: #8b5cf6; + --pd-action-button-details-disabled-text: #818181; + --pd-action-button-details-disabled-bg: #f9fafb; + --pd-action-button-spinner: #8b5cf6; + --pd-tooltip-bg: #f9fafb; + --pd-tooltip-text: #000; + --pd-tooltip-border: #c8c8c8; + --pd-dropdown-bg: #f6f6f6; + --pd-select-bg: #e4e4e4; + --pd-dropdown-ring: #c8c8c8; + --pd-dropdown-hover-ring: #bfa7f6; + --pd-dropdown-divider: #f6f6f6; + --pd-dropdown-item-text: #27272a; + --pd-dropdown-item-hover-bg: #e4e4e4; + --pd-dropdown-item-hover-text: #8b5cf6; + --pd-dropdown-disabled-item-text: #707073; + --pd-dropdown-disabled-item-bg: #efefef; + --pd-modal-dropdown-highlight: #bfa7f6; + --pd-modal-dropdown-text: #0f0f11; + --pd-input-select-hover-text: #5c5c5c; + --pd-label-bg: #e2d6fe; + --pd-label-text: #464649; + --pd-status-running: #2b7037; + --pd-status-terminated: #c13414; + --pd-status-waiting: #d97706; + --pd-status-starting: #2b7037; + --pd-status-stopped: #5c5c5c; + --pd-status-exited: #5c5c5c; + --pd-status-not-running: #818181; + --pd-status-paused: #d97706; + --pd-status-degraded: #b45309; + --pd-status-created: #8ec792; + --pd-status-dead: #c13414; + --pd-status-unknown: #d1d1d1; + --pd-status-connected: #2b7037; + --pd-status-disconnected: #9a9a9a; + --pd-status-updated: #2f88c8; + --pd-status-ready: #f6f6f6; + --pd-status-contrast: #fff; + --pd-statusbar-bg: #37255d; + --pd-statusbar-hover-bg: #4d2d87; + --pd-statusbar-text: #fff; + --pd-onboarding-active-dot-bg: #6234b1; + --pd-onboarding-active-dot-border: #6234b1; + --pd-onboarding-inactive-dot-bg: transparent; + --pd-onboarding-inactive-dot-border: #aaabac; + --pd-state-success: #2b7037; + --pd-state-warning: #d97706; + --pd-state-error: #e5421d; + --pd-state-info: #6d48bf; + --pd-files-hidden: #f86847; + --pd-files-directory: #2f88c8; + --pd-files-symlink: #90c3e9; + --pd-files-executable: #3c8d47; + --pd-terminal-foreground: #000; + --pd-terminal-background: #fff; + --pd-terminal-cursor: #000; + --pd-terminal-selectionBackground: #000; + --pd-terminal-selectionForeground: #fff; + --pd-terminal-ansiBlack: #000; + --pd-terminal-ansiRed: #f86847; + --pd-terminal-ansiGreen: #3c8d47; + --pd-terminal-ansiYellow: #f59e0b; + --pd-terminal-ansiBlue: #2f88c8; + --pd-terminal-ansiMagenta: #8b5cf6; + --pd-terminal-ansiCyan: #2f88c8; + --pd-terminal-ansiWhite: #fff; + --pd-terminal-ansiBrightBlack: #c8c8c8; + --pd-terminal-ansiBrightRed: #e5421d; + --pd-terminal-ansiBrightGreen: #2b7037; + --pd-terminal-ansiBrightYellow: #d97706; + --pd-terminal-ansiBrightBlue: #206ca9; + --pd-terminal-ansiBrightMagenta: #6d48bf; + --pd-terminal-ansiBrightCyan: #206ca9; + --pd-terminal-ansiBrightWhite: #fff; } -@scope (.dark) { - :scope { - --pd-default-text: #fff; - --pd-notification-dot: #8b5cf6; - --pd-global-nav-bg: #27272a; - --pd-global-nav-bg-border: #36363d; - --pd-global-nav-icon: #b4b4b4; - --pd-global-nav-icon-hover: #fff; - --pd-global-nav-icon-hover-bg: #6234b1; - --pd-global-nav-icon-inset-bg: #18181b; - --pd-global-nav-icon-selected: #fff; - --pd-global-nav-icon-selected-bg: #36363d; - --pd-global-nav-icon-selected-highlight: #8b5cf6; - --pd-secondary-nav-bg: #222222; - --pd-secondary-nav-header-text: #fff; - --pd-secondary-nav-text: #e4e4e4; - --pd-secondary-nav-text-hover: #fff; - --pd-secondary-nav-text-hover-bg: #6234b1; - --pd-secondary-nav-text-selected: #fff; - --pd-secondary-nav-selected-bg: #36363d; - --pd-secondary-nav-selected-highlight: #8b5cf6; - --pd-secondary-nav-expander: #fff; - --pd-titlebar-bg: #0f0f11; - --pd-titlebar-text: #fff; - --pd-titlebar-icon: #fff; - --pd-titlebar-windows-hover-exit-bg: #c42b1c; - --pd-titlebar-windows-hover-bg: #2d2d2d; - --pd-content-breadcrumb: #b4b4b4; - --pd-content-breadcrumb-2: #ad8bfa; - --pd-content-header: #fff; - --pd-content-text: #aaabac; - --pd-content-sub-header: #818181; - --pd-content-header-icon: #b4b4b4; - --pd-content-card-header-text: #f6f6f6; - --pd-content-card-bg: #18181b; - --pd-content-card-hover-bg: #36363d; - --pd-content-card-selected-bg: #4a4b4f; - --pd-content-card-text: #d1d1d1; - --pd-content-card-title: #d1d1d1; - --pd-content-card-light-title: #9a9a9a; - --pd-content-card-inset-bg: #0f0f11; - --pd-content-card-hover-inset-bg: #222222; - --pd-content-bg: #222222; - --pd-content-card-icon: #d1d1d1; - --pd-content-divider: #4a4b4f; - --pd-content-card-carousel-card-bg: #27272a; - --pd-content-card-carousel-card-hover-bg: #36363d; - --pd-content-card-carousel-card-header-text: #f6f6f6; - --pd-content-card-carousel-card-text: #d1d1d1; - --pd-content-card-carousel-nav: #9a9a9a; - --pd-content-card-carousel-hover-nav: #b4b4b4; - --pd-content-card-carousel-disabled-nav: #222222; - --pd-content-card-border: #222222; - --pd-content-card-border-selected: #6d57ab; - --pd-invert-content-bg: #18181b; - --pd-invert-content-header-text: #fff; - --pd-invert-content-header2-text: #fff; - --pd-invert-content-card-bg: #27272a; - --pd-invert-content-card-header-text: #fff; - --pd-invert-content-card-text: #e4e4e4; - --pd-invert-content-button-active: #8b5cf6; - --pd-invert-content-button-inactive: #767676; - --pd-invert-content-info-icon: #8b5cf6; - --pd-card-bg: #18181b; - --pd-card-header-text: #fff; - --pd-card-text: #e4e4e4; - --pd-input-field-bg: transparent; - --pd-input-field-focused-bg: #0f0f11; - --pd-input-field-disabled-bg: transparent; - --pd-input-field-hover-bg: transparent; - --pd-input-field-focused-text: #fff; - --pd-input-field-error-text: #f86847; - --pd-input-field-disabled-text: #707073; - --pd-input-field-hover-text: #aaabac; - --pd-input-field-placeholder-text: #aaabac; - --pd-input-field-stroke: #4a4b4f; - --pd-input-field-hover-stroke: #ad8bfa; - --pd-input-field-stroke-error: #f86847; - --pd-input-field-stroke-readonly: #707073; - --pd-input-field-icon: #aaabac; - --pd-input-field-focused-icon: #c8c8c8; - --pd-input-field-disabled-icon: #707073; - --pd-input-field-hover-icon: #aaabac; - --pd-input-checkbox-disabled: #aaabac; - --pd-input-checkbox-indeterminate: #8b5cf6; - --pd-input-checkbox-focused-indeterminate: #ad8bfa; - --pd-input-checkbox-checked: #8b5cf6; - --pd-input-checkbox-focused-checked: #ad8bfa; - --pd-input-checkbox-unchecked: #d1d1d1; - --pd-input-checkbox-focused-unchecked: #ad8bfa; - --pd-input-toggle-off-bg: #818181; - --pd-input-toggle-off-focused-bg: #9a9a9a; - --pd-input-toggle-on-bg: #8b5cf6; - --pd-input-toggle-on-focused-bg: #ad8bfa; - --pd-input-toggle-switch: #fff; - --pd-input-toggle-focused-switch: #fff; - --pd-input-toggle-on-text: #e4e4e4; - --pd-input-toggle-off-text: #e4e4e4; - --pd-input-toggle-disabled-text: #aaabac; - --pd-input-toggle-off-disabled-bg: #0f0f11; - --pd-input-toggle-on-disabled-bg: #0f0f11; - --pd-input-toggle-disabled-switch: #efefef; - --pd-table-header-text: #b4b4b4; - --pd-table-header-unsorted: #5c5c5c; - --pd-table-body-text: #aaabac; - --pd-table-body-text-highlight: #e4e4e4; - --pd-table-body-text-sub-secondary: #ad8bfa; - --pd-table-body-text-sub-highlight: #d1d1d1; - --pd-details-body-text: #efefef; - --pd-details-empty-icon: #b4b4b4; - --pd-details-empty-header: #efefef; - --pd-details-empty-sub-header: #b4b4b4; - --pd-details-empty-cmdline-bg: #0f0f11; - --pd-details-empty-cmdline-text: #d1d1d1; - --pd-details-bg: #0f0f11; - --pd-details-card-bg: #27272a; - --pd-details-card-header: #aaabac; - --pd-details-card-text: #fff; - --pd-tab-text: #b4b4b4; - --pd-tab-text-highlight: #fff; - --pd-tab-highlight: #8b5cf6; - --pd-tab-hover: #ad8bfa; - --pd-modal-fade: #000; - --pd-modal-text: #c8c8c8; - --pd-modal-text-hover: #e4e4e4; - --pd-modal-bg: #18181b; - --pd-modal-border: #36363d; - --pd-modal-header-bg: #000; - --pd-modal-header-text: #d1d1d1; - --pd-modal-header-divider: #6234b1; - --pd-link: #ad8bfa; - --pd-link-hover-bg: #fff2; - --pd-button-primary-bg: #6d48bf; - --pd-button-primary-hover-bg: #8b5cf6; - --pd-button-secondary: #efefef; - --pd-button-secondary-hover: #8b5cf6; - --pd-button-text: #fff; - --pd-button-disabled: #464649; - --pd-button-disabled-text: #767676; - --pd-button-danger-border: #f86847; - --pd-button-danger-bg: transparent; - --pd-button-danger-text: #f86847; - --pd-button-danger-hover-text: #fff; - --pd-button-danger-hover-bg: #e5421d; - --pd-button-danger-disabled-border: #767676; - --pd-button-danger-disabled-text: #767676; - --pd-button-danger-disabled-bg: transparent; - --pd-button-tab-border: transparent; - --pd-button-tab-border-selected: #8b5cf6; - --pd-button-tab-hover-border: #707073; - --pd-button-tab-text: #b4b4b4; - --pd-button-tab-text-selected: #fff; - --pd-button-link-text: #ad8bfa; - --pd-button-link-hover-bg: #fff2; - --pd-button-help-link-text: #f6f6f6; - --pd-action-button-text: #d1d1d1; - --pd-action-button-bg: #0f0f11; - --pd-action-button-hover-bg: #27272a; - --pd-action-button-hover-text: #6d48bf; - --pd-action-button-primary-text: #6d48bf; - --pd-action-button-primary-hover-text: #8b5cf6; - --pd-action-button-disabled-text: #818181; - --pd-action-button-details-text: #d1d1d1; - --pd-action-button-details-bg: #18181b; - --pd-action-button-details-hover-text: #6d48bf; - --pd-action-button-details-disabled-text: #818181; - --pd-action-button-details-disabled-bg: #18181b; - --pd-action-button-spinner: #8b5cf6; - --pd-tooltip-bg: #18181b; - --pd-tooltip-text: #fff; - --pd-tooltip-border: #36363d; - --pd-dropdown-bg: #27272a; - --pd-select-bg: #18181b; - --pd-dropdown-ring: #37255d; - --pd-dropdown-hover-ring: #6234b1; - --pd-dropdown-divider: #27272a; - --pd-dropdown-item-text: #d1d1d1; - --pd-dropdown-item-hover-bg: #000; - --pd-dropdown-item-hover-text: #8b5cf6; - --pd-dropdown-disabled-item-text: #818181; - --pd-dropdown-disabled-item-bg: #18181b; - --pd-modal-dropdown-highlight: #6d48bf; - --pd-modal-dropdown-text: #fff; - --pd-input-select-hover-text: #818181; - --pd-label-bg: #36363d; - --pd-label-text: #c8c8c8; - --pd-status-running: #3c8d47; - --pd-status-terminated: #f86847; - --pd-status-waiting: #d97706; - --pd-status-starting: #3c8d47; - --pd-status-stopped: #818181; - --pd-status-exited: #818181; - --pd-status-not-running: #aaabac; - --pd-status-paused: #d97706; - --pd-status-degraded: #b45309; - --pd-status-created: #8ec792; - --pd-status-dead: #f86847; - --pd-status-unknown: #f6f6f6; - --pd-status-connected: #2b7037; - --pd-status-disconnected: #c8c8c8; - --pd-status-updated: #2f88c8; - --pd-status-ready: #818181; - --pd-status-contrast: #fff; - --pd-statusbar-bg: #37255d; - --pd-statusbar-hover-bg: #4d2d87; - --pd-statusbar-text: #fff; - --pd-onboarding-active-dot-bg: #6234b1; - --pd-onboarding-active-dot-border: #6234b1; - --pd-onboarding-inactive-dot-bg: transparent; - --pd-onboarding-inactive-dot-border: #aaabac; - --pd-state-success: #3c8d47; - --pd-state-warning: #f59e0b; - --pd-state-error: #f86847; - --pd-state-info: #8b5cf6; - --pd-files-hidden: #f86847; - --pd-files-directory: #2f88c8; - --pd-files-symlink: #90c3e9; - --pd-files-executable: #3c8d47; - --pd-terminal-foreground: #fff; - --pd-terminal-background: #000; - --pd-terminal-cursor: #fff; - --pd-terminal-selectionBackground: #fff; - --pd-terminal-selectionForeground: #000; - --pd-terminal-ansiBlack: #000; - --pd-terminal-ansiRed: #f86847; - --pd-terminal-ansiGreen: #3c8d47; - --pd-terminal-ansiYellow: #f59e0b; - --pd-terminal-ansiBlue: #2f88c8; - --pd-terminal-ansiMagenta: #8b5cf6; - --pd-terminal-ansiCyan: #2f88c8; - --pd-terminal-ansiWhite: #fff; - --pd-terminal-ansiBrightBlack: #c8c8c8; - --pd-terminal-ansiBrightRed: #e5421d; - --pd-terminal-ansiBrightGreen: #2b7037; - --pd-terminal-ansiBrightYellow: #d97706; - --pd-terminal-ansiBrightBlue: #206ca9; - --pd-terminal-ansiBrightMagenta: #6d48bf; - --pd-terminal-ansiBrightCyan: #206ca9; - --pd-terminal-ansiBrightWhite: #fff; - } +.dark { + --pd-default-text: #fff; + --pd-notification-dot: #8b5cf6; + --pd-global-nav-bg: #27272a; + --pd-global-nav-bg-border: #36363d; + --pd-global-nav-icon: #b4b4b4; + --pd-global-nav-icon-hover: #fff; + --pd-global-nav-icon-hover-bg: #6234b1; + --pd-global-nav-icon-inset-bg: #18181b; + --pd-global-nav-icon-selected: #fff; + --pd-global-nav-icon-selected-bg: #36363d; + --pd-global-nav-icon-selected-highlight: #8b5cf6; + --pd-secondary-nav-bg: #222222; + --pd-secondary-nav-header-text: #fff; + --pd-secondary-nav-text: #e4e4e4; + --pd-secondary-nav-text-hover: #fff; + --pd-secondary-nav-text-hover-bg: #6234b1; + --pd-secondary-nav-text-selected: #fff; + --pd-secondary-nav-selected-bg: #36363d; + --pd-secondary-nav-selected-highlight: #8b5cf6; + --pd-secondary-nav-expander: #fff; + --pd-titlebar-bg: #0f0f11; + --pd-titlebar-text: #fff; + --pd-titlebar-icon: #fff; + --pd-titlebar-windows-hover-exit-bg: #c42b1c; + --pd-titlebar-windows-hover-bg: #2d2d2d; + --pd-content-breadcrumb: #b4b4b4; + --pd-content-breadcrumb-2: #ad8bfa; + --pd-content-header: #fff; + --pd-content-text: #aaabac; + --pd-content-sub-header: #818181; + --pd-content-header-icon: #b4b4b4; + --pd-content-card-header-text: #f6f6f6; + --pd-content-card-bg: #18181b; + --pd-content-card-hover-bg: #36363d; + --pd-content-card-selected-bg: #4a4b4f; + --pd-content-card-text: #d1d1d1; + --pd-content-card-title: #d1d1d1; + --pd-content-card-light-title: #9a9a9a; + --pd-content-card-inset-bg: #0f0f11; + --pd-content-card-hover-inset-bg: #222222; + --pd-content-bg: #222222; + --pd-content-card-icon: #d1d1d1; + --pd-content-divider: #4a4b4f; + --pd-content-card-carousel-card-bg: #27272a; + --pd-content-card-carousel-card-hover-bg: #36363d; + --pd-content-card-carousel-card-header-text: #f6f6f6; + --pd-content-card-carousel-card-text: #d1d1d1; + --pd-content-card-carousel-nav: #9a9a9a; + --pd-content-card-carousel-hover-nav: #b4b4b4; + --pd-content-card-carousel-disabled-nav: #222222; + --pd-content-card-border: #222222; + --pd-content-card-border-selected: #6d57ab; + --pd-invert-content-bg: #18181b; + --pd-invert-content-header-text: #fff; + --pd-invert-content-header2-text: #fff; + --pd-invert-content-card-bg: #27272a; + --pd-invert-content-card-header-text: #fff; + --pd-invert-content-card-text: #e4e4e4; + --pd-invert-content-button-active: #8b5cf6; + --pd-invert-content-button-inactive: #767676; + --pd-invert-content-info-icon: #8b5cf6; + --pd-card-bg: #18181b; + --pd-card-header-text: #fff; + --pd-card-text: #e4e4e4; + --pd-input-field-bg: transparent; + --pd-input-field-focused-bg: #0f0f11; + --pd-input-field-disabled-bg: transparent; + --pd-input-field-hover-bg: transparent; + --pd-input-field-focused-text: #fff; + --pd-input-field-error-text: #f86847; + --pd-input-field-disabled-text: #707073; + --pd-input-field-hover-text: #aaabac; + --pd-input-field-placeholder-text: #aaabac; + --pd-input-field-stroke: #4a4b4f; + --pd-input-field-hover-stroke: #ad8bfa; + --pd-input-field-stroke-error: #f86847; + --pd-input-field-stroke-readonly: #707073; + --pd-input-field-icon: #aaabac; + --pd-input-field-focused-icon: #c8c8c8; + --pd-input-field-disabled-icon: #707073; + --pd-input-field-hover-icon: #aaabac; + --pd-input-checkbox-disabled: #aaabac; + --pd-input-checkbox-indeterminate: #8b5cf6; + --pd-input-checkbox-focused-indeterminate: #ad8bfa; + --pd-input-checkbox-checked: #8b5cf6; + --pd-input-checkbox-focused-checked: #ad8bfa; + --pd-input-checkbox-unchecked: #d1d1d1; + --pd-input-checkbox-focused-unchecked: #ad8bfa; + --pd-input-toggle-off-bg: #818181; + --pd-input-toggle-off-focused-bg: #9a9a9a; + --pd-input-toggle-on-bg: #8b5cf6; + --pd-input-toggle-on-focused-bg: #ad8bfa; + --pd-input-toggle-switch: #fff; + --pd-input-toggle-focused-switch: #fff; + --pd-input-toggle-on-text: #e4e4e4; + --pd-input-toggle-off-text: #e4e4e4; + --pd-input-toggle-disabled-text: #aaabac; + --pd-input-toggle-off-disabled-bg: #0f0f11; + --pd-input-toggle-on-disabled-bg: #0f0f11; + --pd-input-toggle-disabled-switch: #efefef; + --pd-table-header-text: #b4b4b4; + --pd-table-header-unsorted: #5c5c5c; + --pd-table-body-text: #aaabac; + --pd-table-body-text-highlight: #e4e4e4; + --pd-table-body-text-sub-secondary: #ad8bfa; + --pd-table-body-text-sub-highlight: #d1d1d1; + --pd-details-body-text: #efefef; + --pd-details-empty-icon: #b4b4b4; + --pd-details-empty-header: #efefef; + --pd-details-empty-sub-header: #b4b4b4; + --pd-details-empty-cmdline-bg: #0f0f11; + --pd-details-empty-cmdline-text: #d1d1d1; + --pd-details-bg: #0f0f11; + --pd-details-card-bg: #27272a; + --pd-details-card-header: #aaabac; + --pd-details-card-text: #fff; + --pd-tab-text: #b4b4b4; + --pd-tab-text-highlight: #fff; + --pd-tab-highlight: #8b5cf6; + --pd-tab-hover: #ad8bfa; + --pd-modal-fade: #000; + --pd-modal-text: #c8c8c8; + --pd-modal-text-hover: #e4e4e4; + --pd-modal-bg: #18181b; + --pd-modal-border: #36363d; + --pd-modal-header-bg: #000; + --pd-modal-header-text: #d1d1d1; + --pd-modal-header-divider: #6234b1; + --pd-link: #ad8bfa; + --pd-link-hover-bg: #fff2; + --pd-button-primary-bg: #6d48bf; + --pd-button-primary-hover-bg: #8b5cf6; + --pd-button-secondary: #efefef; + --pd-button-secondary-hover: #8b5cf6; + --pd-button-text: #fff; + --pd-button-disabled: #464649; + --pd-button-disabled-text: #767676; + --pd-button-danger-border: #f86847; + --pd-button-danger-bg: transparent; + --pd-button-danger-text: #f86847; + --pd-button-danger-hover-text: #fff; + --pd-button-danger-hover-bg: #e5421d; + --pd-button-danger-disabled-border: #767676; + --pd-button-danger-disabled-text: #767676; + --pd-button-danger-disabled-bg: transparent; + --pd-button-tab-border: transparent; + --pd-button-tab-border-selected: #8b5cf6; + --pd-button-tab-hover-border: #707073; + --pd-button-tab-text: #b4b4b4; + --pd-button-tab-text-selected: #fff; + --pd-button-link-text: #ad8bfa; + --pd-button-link-hover-bg: #fff2; + --pd-button-help-link-text: #f6f6f6; + --pd-action-button-text: #d1d1d1; + --pd-action-button-bg: #0f0f11; + --pd-action-button-hover-bg: #27272a; + --pd-action-button-hover-text: #6d48bf; + --pd-action-button-primary-text: #6d48bf; + --pd-action-button-primary-hover-text: #8b5cf6; + --pd-action-button-disabled-text: #818181; + --pd-action-button-details-text: #d1d1d1; + --pd-action-button-details-bg: #18181b; + --pd-action-button-details-hover-text: #6d48bf; + --pd-action-button-details-disabled-text: #818181; + --pd-action-button-details-disabled-bg: #18181b; + --pd-action-button-spinner: #8b5cf6; + --pd-tooltip-bg: #18181b; + --pd-tooltip-text: #fff; + --pd-tooltip-border: #36363d; + --pd-dropdown-bg: #27272a; + --pd-select-bg: #18181b; + --pd-dropdown-ring: #37255d; + --pd-dropdown-hover-ring: #6234b1; + --pd-dropdown-divider: #27272a; + --pd-dropdown-item-text: #d1d1d1; + --pd-dropdown-item-hover-bg: #000; + --pd-dropdown-item-hover-text: #8b5cf6; + --pd-dropdown-disabled-item-text: #818181; + --pd-dropdown-disabled-item-bg: #18181b; + --pd-modal-dropdown-highlight: #6d48bf; + --pd-modal-dropdown-text: #fff; + --pd-input-select-hover-text: #818181; + --pd-label-bg: #36363d; + --pd-label-text: #c8c8c8; + --pd-status-running: #3c8d47; + --pd-status-terminated: #f86847; + --pd-status-waiting: #d97706; + --pd-status-starting: #3c8d47; + --pd-status-stopped: #818181; + --pd-status-exited: #818181; + --pd-status-not-running: #aaabac; + --pd-status-paused: #d97706; + --pd-status-degraded: #b45309; + --pd-status-created: #8ec792; + --pd-status-dead: #f86847; + --pd-status-unknown: #f6f6f6; + --pd-status-connected: #2b7037; + --pd-status-disconnected: #c8c8c8; + --pd-status-updated: #2f88c8; + --pd-status-ready: #818181; + --pd-status-contrast: #fff; + --pd-statusbar-bg: #37255d; + --pd-statusbar-hover-bg: #4d2d87; + --pd-statusbar-text: #fff; + --pd-onboarding-active-dot-bg: #6234b1; + --pd-onboarding-active-dot-border: #6234b1; + --pd-onboarding-inactive-dot-bg: transparent; + --pd-onboarding-inactive-dot-border: #aaabac; + --pd-state-success: #3c8d47; + --pd-state-warning: #f59e0b; + --pd-state-error: #f86847; + --pd-state-info: #8b5cf6; + --pd-files-hidden: #f86847; + --pd-files-directory: #2f88c8; + --pd-files-symlink: #90c3e9; + --pd-files-executable: #3c8d47; + --pd-terminal-foreground: #fff; + --pd-terminal-background: #000; + --pd-terminal-cursor: #fff; + --pd-terminal-selectionBackground: #fff; + --pd-terminal-selectionForeground: #000; + --pd-terminal-ansiBlack: #000; + --pd-terminal-ansiRed: #f86847; + --pd-terminal-ansiGreen: #3c8d47; + --pd-terminal-ansiYellow: #f59e0b; + --pd-terminal-ansiBlue: #2f88c8; + --pd-terminal-ansiMagenta: #8b5cf6; + --pd-terminal-ansiCyan: #2f88c8; + --pd-terminal-ansiWhite: #fff; + --pd-terminal-ansiBrightBlack: #c8c8c8; + --pd-terminal-ansiBrightRed: #e5421d; + --pd-terminal-ansiBrightGreen: #2b7037; + --pd-terminal-ansiBrightYellow: #d97706; + --pd-terminal-ansiBrightBlue: #206ca9; + --pd-terminal-ansiBrightMagenta: #6d48bf; + --pd-terminal-ansiBrightCyan: #206ca9; + --pd-terminal-ansiBrightWhite: #fff; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4c08dbc..9b792c6 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,8 +1,45 @@ +import { onMount } from 'svelte'; + +import Appearance from '$lib/Appearance.svelte'; +import ExtensionsDetails from '$lib/ui/ExtensionsDetails.svelte'; +import ExtensionsList from '$lib/ui/ExtensionsList.svelte'; + +import type { ExtensionByCategoryInfo } from '../lib/api/extensions-info'; +import { catalogExtensions, getCurrentExtension, initCatalog } from '../lib/extensions.svelte'; - +const uniqueCategories: string[] = $state([]); +const extensionsByCategories: ExtensionByCategoryInfo[] = $state([]); - \ No newline at end of file +onMount(async () => { + await initCatalog(); + + // collect all the categories of all the extensions + const categories = catalogExtensions.flatMap(ext => ext.categories); + // need to remove any duplicates from this array of string + // so we can use a Set to do this + uniqueCategories.push(...new Set(categories)); + + // for each category, compute the extensions + for (const category of uniqueCategories) { + // how many extensions are in this category + const extensions = catalogExtensions.filter(ext => ext.categories.includes(category)); + // sort extensions by name + extensions.sort((a, b) => a.extensionName.localeCompare(b.extensionName)); + const extensionsByCategory: ExtensionByCategoryInfo = { category, extensions }; + extensionsByCategories.push(extensionsByCategory); + } + // sort them by the number of extensions in each category + extensionsByCategories.sort((a, b) => b.extensions.length - a.extensions.length); +}); + + + +
+ {#if getCurrentExtension().value} + + {:else} + + {/if} +
+ \ No newline at end of file diff --git a/src/routes/+page.ts b/src/routes/+page.ts index 89da957..fda4267 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -1,2 +1,20 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + export const ssr = false; export const prerender = true; diff --git a/src/routes/page.svelte.spec.ts b/src/routes/page.svelte.spec.ts index ceef5af..81ccf73 100644 --- a/src/routes/page.svelte.spec.ts +++ b/src/routes/page.svelte.spec.ts @@ -1,18 +1,53 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + import '@testing-library/jest-dom/vitest'; import { render, screen } from '@testing-library/svelte'; import { beforeEach, expect, test, vi } from 'vitest'; +import { catalogExtensions } from '$lib/extensions.svelte'; + +import catalogOfExtensions from '../../static/api/extensions.json'; import Page from './+page.svelte'; +const fetchMock = vi.fn(); + beforeEach(() => { vi.resetAllMocks(); + catalogExtensions.length = 0; + window.fetch = fetchMock; + window.matchMedia = vi.fn().mockReturnValue({ + matches: false, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + }); }); -test('check title', async () => { +test('check fetch', async () => { + fetchMock.mockResolvedValue({ + ok: true, + json: async () => ({ extensions: catalogOfExtensions.extensions }), + }); render(Page); - // get the button element - const button = screen.getByRole('heading', { name: 'coming soon' }); - expect(button).toBeInTheDocument(); + expect(vi.mocked(fetch)).toHaveBeenCalledWith('https://registry.podman-desktop.io/api/extensions.json'); + + // at least 4 categories should be displayed + await vi.waitFor(() => expect(screen.getAllByRole('region', { name: 'Category name' }).length).toBeGreaterThan(4)); });