forked from podman-desktop/podman-desktop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: getting started carousel on dashboard page (podman-desktop#5142)
* Carousel component to show cards and rotate them with left and right buttons * Learning center carousel for dashboard with links to the podman desktop guides Signed-off-by: Denis Golovin <[email protected]>
- Loading branch information
Showing
11 changed files
with
367 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
26 changes: 26 additions & 0 deletions
26
packages/main/src/plugin/learning-center/learning-center-api.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/********************************************************************** | ||
* Copyright (C) 2023 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 Guide { | ||
id: string; | ||
url: string; | ||
title: string; | ||
description: string; | ||
categories: string[]; | ||
icon: string; | ||
} |
24 changes: 24 additions & 0 deletions
24
packages/main/src/plugin/learning-center/learning-center.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/********************************************************************** | ||
* Copyright (C) 2023 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 { Guide } from './learning-center-api.js'; | ||
import { default as guidesJson } from './guides.json'; | ||
|
||
export function downloadGuideList(): Guide[] { | ||
return guidesJson.guides; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/********************************************************************** | ||
* Copyright (C) 2023 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 | ||
***********************************************************************/ | ||
|
||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
||
import '@testing-library/jest-dom/vitest'; | ||
import { test, expect, vi, beforeEach, afterEach } from 'vitest'; | ||
import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; | ||
import CarouselTest from './CarouselTest.svelte'; | ||
|
||
let callback: any; | ||
|
||
class ResizeObserver { | ||
constructor(callback1: (entries: ResizeObserverEntry[], observer: ResizeObserver) => void) { | ||
callback = callback1; | ||
} | ||
|
||
observe = vi.fn(); | ||
disconnect = vi.fn(); | ||
unobserve = vi.fn(); | ||
} | ||
|
||
beforeEach(() => { | ||
(window as any).ResizeObserver = ResizeObserver; | ||
}); | ||
|
||
afterEach(() => { | ||
vi.resetAllMocks(); | ||
}); | ||
|
||
test('carousel cards get visible when size permits', async () => { | ||
render(CarouselTest); | ||
const card1 = screen.getByText('card 1'); | ||
console.log(window.innerWidth); | ||
expect(card1).toBeVisible(); | ||
|
||
callback([{ contentRect: { width: 680 } }]); | ||
|
||
await waitFor(() => { | ||
const card2 = screen.getByText('card 2'); | ||
expect(card2).toBeVisible(); | ||
}); | ||
|
||
const cards = screen.queryAllByText('card 3'); | ||
expect(cards.length).toBe(0); | ||
|
||
callback([{ contentRect: { width: 1020 } }]); | ||
|
||
await waitFor(() => { | ||
const card3 = screen.getByText('card 3'); | ||
expect(card3).toBeVisible(); | ||
}); | ||
}); | ||
|
||
test('rotate left button displays previous card', async () => { | ||
render(CarouselTest); | ||
const card1 = screen.getByText('card 1'); | ||
expect(card1).toBeVisible(); | ||
|
||
const cards = screen.queryAllByText('card 3'); | ||
expect(cards.length).toBe(0); | ||
|
||
const left = screen.getByRole('button', { name: 'Rotate left' }); | ||
await fireEvent.click(left); | ||
|
||
const card3 = screen.getByText('card 3'); | ||
expect(card3).toBeVisible(); | ||
}); | ||
|
||
test('rotate right button displays next card', async () => { | ||
render(CarouselTest); | ||
const card1 = screen.getByText('card 1'); | ||
expect(card1).toBeVisible(); | ||
|
||
const cards = screen.queryAllByText('card 2'); | ||
expect(cards.length).toBe(0); | ||
|
||
const right = screen.getByRole('button', { name: 'Rotate right' }); | ||
await fireEvent.click(right); | ||
|
||
const card3 = screen.getByText('card 2'); | ||
expect(card3).toBeVisible(); | ||
}); | ||
|
||
test('carousel left and right buttons enabled when all items does not fit into screen and disabled otherwise', async () => { | ||
render(CarouselTest); | ||
const card1 = screen.getByText('card 1'); | ||
console.log(window.innerWidth); | ||
expect(card1).toBeVisible(); | ||
|
||
let cards = screen.queryAllByText('card 2'); | ||
expect(cards.length).toBe(0); | ||
|
||
cards = screen.queryAllByText('card 3'); | ||
expect(cards.length).toBe(0); | ||
|
||
const left = screen.getByRole('button', { name: 'Rotate left' }); | ||
const right = screen.getByRole('button', { name: 'Rotate right' }); | ||
|
||
expect(left).toBeEnabled(); | ||
expect(right).toBeEnabled(); | ||
|
||
callback([{ contentRect: { width: 1020 } }]); | ||
|
||
await waitFor(() => { | ||
const card1 = screen.getByText('card 1'); | ||
expect(card1).toBeVisible(); | ||
const card2 = screen.getByText('card 2'); | ||
expect(card2).toBeVisible(); | ||
const card3 = screen.getByText('card 3'); | ||
expect(card3).toBeVisible(); | ||
}); | ||
|
||
expect(left).toBeDisabled(); | ||
expect(right).toBeDisabled(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<script lang="ts"> | ||
import { faGreaterThan, faLessThan } from '@fortawesome/free-solid-svg-icons'; | ||
import Fa from 'svelte-fa'; | ||
import { onDestroy, onMount } from 'svelte'; | ||
let resizeObserver: ResizeObserver; | ||
export let cards: any[]; | ||
export let cardWidth = 340; | ||
let cardsFit = 1; | ||
let containerId = Math.random().toString(36).slice(-6); | ||
$: visibleCards = cards.slice(0, cardsFit); | ||
function calcCardsToFit(width: number) { | ||
const cf = Math.floor(width / cardWidth); | ||
return cf === 0 ? 1 : cf; | ||
} | ||
function update(entries: any) { | ||
const width = entries[0].contentRect.width; | ||
cardsFit = calcCardsToFit(width); | ||
} | ||
onMount(() => { | ||
const cardsContainer = document.getElementById(`carousel-cards-${containerId}`); | ||
const initialWidth = cardsContainer?.offsetWidth as number; | ||
cardsFit = calcCardsToFit(initialWidth); | ||
resizeObserver = new ResizeObserver(update); | ||
resizeObserver.observe(cardsContainer as Element); | ||
}); | ||
onDestroy(() => { | ||
resizeObserver.disconnect(); | ||
}); | ||
function rotateLeft() { | ||
cards = [cards[cards.length - 1], ...cards.slice(0, cards.length - 1)]; | ||
} | ||
function rotateRight() { | ||
cards = [...cards.slice(1, cards.length), cards[0]]; | ||
} | ||
</script> | ||
|
||
<div class="flex flex-row items-center"> | ||
<button | ||
id="left" | ||
on:click="{rotateLeft}" | ||
aria-label="Rotate left" | ||
class="h-8 w-8 mr-3 bg-gray-800 rounded-full disabled:bg-zinc-700" | ||
disabled="{visibleCards.length === cards.length}"> | ||
<Fa class="w-8 h-8" icon="{faLessThan}" color="black" /> | ||
</button> | ||
|
||
<div id="carousel-cards-{containerId}" class="flex flex-grow gap-3 overflow-hidden"> | ||
{#each visibleCards as card} | ||
<slot card="{card}" /> | ||
{/each} | ||
</div> | ||
|
||
<button | ||
id="right" | ||
on:click="{rotateRight}" | ||
aria-label="Rotate right" | ||
class="h-8 w-8 ml-3 bg-gray-800 rounded-full disabled:bg-zinc-700" | ||
disabled="{visibleCards.length === cards.length}"> | ||
<Fa class="h-8 w-8" icon="{faGreaterThan}" color="black" /> | ||
</button> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<script lang="ts"> | ||
import Carousel from './Carousel.svelte'; | ||
</script> | ||
|
||
<div> | ||
<p class="text-lg first-letter:uppercase font-bold pb-5">Learning center:</p> | ||
<div class="bg-charcoal-800 p-4 rounded-lg"> | ||
<Carousel cards="{['card 1', 'card 2', 'card 3']}" cardWidth="{340}" let:card> | ||
<p>{card}</p> | ||
</Carousel> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
packages/renderer/src/lib/learning-center/GuideCard.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<script lang="ts"> | ||
import type { Guide } from '../../../../main/src/plugin/learning-center/learning-center-api'; | ||
import Button from '../ui/Button.svelte'; | ||
export let guide: Guide; | ||
export let width = 300; | ||
export let height = 300; | ||
</script> | ||
|
||
<div | ||
class="flex flex-col flex-1 bg-charcoal-600 pb-4 rounded-lg hover:bg-zinc-700 min-w-[{width}px] min-h-[{height}px]"> | ||
<dif class="pt-4 flex flex-col"> | ||
<div class="px-4"> | ||
<img src="{`data:image/png;base64,${guide.icon}`}" class="h-[48px]" alt="{guide.id}" /> | ||
</div> | ||
<div class="px-4 pt-4 text-nowrap text-gray-400"> | ||
{guide.title} | ||
</div> | ||
<p class="line-clamp-4 px-4 pt-4 text-base text-gray-700">{guide.description}</p> | ||
</dif> | ||
<div class="flex justify-center items-end flex-1 pt-4"> | ||
<Button | ||
class="justify-self-center self-end text-lg" | ||
on:click="{() => window.openExternal(guide.url)}" | ||
title="Get started">Get started</Button> | ||
</div> | ||
</div> |
21 changes: 21 additions & 0 deletions
21
packages/renderer/src/lib/learning-center/LearningCenter.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<script lang="ts"> | ||
import { onMount } from 'svelte'; | ||
import Carousel from '../carousel/Carousel.svelte'; | ||
import GuideCard from './GuideCard.svelte'; | ||
import type { Guide } from '../../../../main/src/plugin/learning-center/learning-center-api'; | ||
let guides: Guide[] = []; | ||
onMount(async () => { | ||
guides = await window.listGuides(); | ||
}); | ||
</script> | ||
|
||
<div class="flex flex-1 flex-col"> | ||
<p class="text-lg first-letter:uppercase font-bold pb-5">Learning center:</p> | ||
<div class="flex flex-1 flex-col bg-charcoal-800 p-5 rounded-lg"> | ||
<Carousel cards="{guides}" let:card> | ||
<GuideCard guide="{card}" /> | ||
</Carousel> | ||
</div> | ||
</div> |