-
Notifications
You must be signed in to change notification settings - Fork 647
feat: add provider logo overlays to workflow template thumbnails #8365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
fb9bc50
9c5f482
0c5b97e
019262b
85a7d19
d5166a0
fc36229
a51a93c
31ea8e4
d77fa09
467a571
4b8edbf
b998a5c
edd357c
0310759
00468ca
95d254b
ee28040
90a203e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import { mount } from '@vue/test-utils' | ||
| import { describe, expect, it } from 'vitest' | ||
|
|
||
| import LogoOverlay from '@/components/templates/thumbnails/LogoOverlay.vue' | ||
| import type { LogoInfo } from '@/platform/workflow/templates/types/template' | ||
|
|
||
| describe('LogoOverlay', () => { | ||
| const mockGetLogoUrl = (provider: string) => `/logos/${provider}.png` | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| const mountOverlay = (logos: LogoInfo[], props = {}) => { | ||
| return mount(LogoOverlay, { | ||
| props: { | ||
| logos, | ||
| getLogoUrl: mockGetLogoUrl, | ||
| ...props | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| it('renders nothing when logos array is empty', () => { | ||
| const wrapper = mountOverlay([]) | ||
| expect(wrapper.findAll('img')).toHaveLength(0) | ||
| }) | ||
|
|
||
| it('renders a logo with correct src and alt', () => { | ||
| const wrapper = mountOverlay([{ provider: 'Google' }]) | ||
| const img = wrapper.find('img') | ||
| expect(img.attributes('src')).toBe('/logos/Google.png') | ||
| expect(img.attributes('alt')).toBe('Google') | ||
| }) | ||
|
|
||
| it('renders multiple logos', () => { | ||
| const wrapper = mountOverlay([ | ||
| { provider: 'Google' }, | ||
| { provider: 'OpenAI' }, | ||
| { provider: 'Stability' } | ||
| ]) | ||
| expect(wrapper.findAll('img')).toHaveLength(3) | ||
| }) | ||
|
|
||
| it('applies default position when not specified', () => { | ||
| const wrapper = mountOverlay([{ provider: 'Google' }]) | ||
| const container = wrapper.find('div') | ||
| expect(container.classes()).toContain('bottom-2') | ||
|
Check failure on line 44 in src/components/templates/thumbnails/LogoOverlay.test.ts
|
||
| expect(container.classes()).toContain('right-2') | ||
| }) | ||
|
|
||
| it('applies custom position from logo config', () => { | ||
| const wrapper = mountOverlay([ | ||
| { provider: 'Google', position: 'top-2 left-2' } | ||
| ]) | ||
| const container = wrapper.find('div') | ||
| expect(container.classes()).toContain('top-2') | ||
| expect(container.classes()).toContain('left-2') | ||
| }) | ||
|
|
||
| it('applies default medium size class', () => { | ||
| const wrapper = mountOverlay([{ provider: 'Google' }]) | ||
| const img = wrapper.find('img') | ||
| expect(img.classes()).toContain('h-8') | ||
|
Check failure on line 60 in src/components/templates/thumbnails/LogoOverlay.test.ts
|
||
| expect(img.classes()).toContain('w-8') | ||
| }) | ||
|
|
||
| it('applies small size class', () => { | ||
| const wrapper = mountOverlay([{ provider: 'Google', size: 'sm' }]) | ||
| const img = wrapper.find('img') | ||
| expect(img.classes()).toContain('h-6') | ||
|
Check failure on line 67 in src/components/templates/thumbnails/LogoOverlay.test.ts
|
||
| expect(img.classes()).toContain('w-6') | ||
| }) | ||
|
|
||
| it('applies large size class', () => { | ||
| const wrapper = mountOverlay([{ provider: 'Google', size: 'lg' }]) | ||
| const img = wrapper.find('img') | ||
| expect(img.classes()).toContain('h-12') | ||
|
Check failure on line 74 in src/components/templates/thumbnails/LogoOverlay.test.ts
|
||
| expect(img.classes()).toContain('w-12') | ||
| }) | ||
|
|
||
| it('applies default opacity', () => { | ||
| const wrapper = mountOverlay([{ provider: 'Google' }]) | ||
| const container = wrapper.find('div') | ||
| expect(container.attributes('style')).toContain('opacity: 0.9') | ||
| }) | ||
|
|
||
| it('applies custom opacity', () => { | ||
| const wrapper = mountOverlay([{ provider: 'Google', opacity: 0.5 }]) | ||
| const container = wrapper.find('div') | ||
| expect(container.attributes('style')).toContain('opacity: 0.5') | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| it('images are not draggable', () => { | ||
| const wrapper = mountOverlay([{ provider: 'Google' }]) | ||
| const img = wrapper.find('img') | ||
| expect(img.attributes('draggable')).toBe('false') | ||
| }) | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| <template> | ||
| <div | ||
| v-for="(logo, index) in validLogos" | ||
| :key="index" | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| :class=" | ||
| cn('pointer-events-none absolute z-10', logo.position ?? defaultPosition) | ||
| " | ||
| > | ||
| <div | ||
| v-show="!failedLogos.has(logo.provider)" | ||
| class="flex items-center gap-1.5 rounded-full bg-black/20 px-2 py-1" | ||
| :style="{ opacity: logo.opacity ?? 1 }" | ||
| > | ||
| <img | ||
| :src="logo.url" | ||
| :alt="logo.provider" | ||
| class="h-5 w-5 rounded-[50%]" | ||
| draggable="false" | ||
| @error="onImageError(logo.provider)" | ||
| /> | ||
| <span class="text-sm font-medium text-white"> | ||
| {{ logo.provider }} | ||
| </span> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honor size/opacity defaults and align default position.
🛠️ Proposed fix const {
logos,
getLogoUrl,
- defaultPosition = 'top-2 left-2'
+ defaultPosition = 'bottom-2 right-2'
} = defineProps<{
logos: LogoInfo[]
getLogoUrl: (provider: string) => string
defaultPosition?: string
}>()
+function getLogoSizeClass(size?: 'sm' | 'md' | 'lg') {
+ switch (size) {
+ case 'sm':
+ return 'h-6 w-6'
+ case 'lg':
+ return 'h-12 w-12'
+ default:
+ return 'h-8 w-8'
+ }
+}
+
const failedLogos = ref(new Set<string>())- :style="{ opacity: logo.opacity ?? 1 }"
+ :style="{ opacity: logo.opacity ?? 0.9 }"- class="h-5 w-5 rounded-[50%]"
+ :class="cn('rounded-full', getLogoSizeClass(logo.size))"Also applies to: 37-57 🤖 Prompt for AI Agents |
||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| import { computed, ref } from 'vue' | ||
|
|
||
| import type { LogoInfo } from '@/platform/workflow/templates/types/template' | ||
| import { cn } from '@/utils/tailwindUtil' | ||
|
|
||
| const { | ||
| logos, | ||
| getLogoUrl, | ||
| defaultPosition = 'top-2 left-2' | ||
| } = defineProps<{ | ||
| logos: LogoInfo[] | ||
| getLogoUrl: (provider: string) => string | ||
| defaultPosition?: string | ||
| }>() | ||
|
|
||
| const failedLogos = ref(new Set<string>()) | ||
|
|
||
| const onImageError = (provider: string) => { | ||
| failedLogos.value = new Set([...failedLogos.value, provider]) | ||
| } | ||
|
|
||
| const validLogos = computed(() => | ||
| logos | ||
| .map((logo) => ({ | ||
| ...logo, | ||
| url: getLogoUrl(logo.provider) | ||
| })) | ||
| .filter((logo) => logo.url) | ||
| ) | ||
| </script> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolve the ESLint
no-unresolvederror for@vue/test-utils.ESLint reports
import-x/no-unresolved; ensure the dependency is installed and the resolver is configured for the workspace.🧰 Tools
🪛 ESLint
[error] 1-1: Unable to resolve path to module '@vue/test-utils'.
(import-x/no-unresolved)
🤖 Prompt for AI Agents