From 3e23d5f8cc94108288f59f05eaaa4e69a9f1d705 Mon Sep 17 00:00:00 2001 From: Andre Torquato Date: Wed, 5 Nov 2025 00:12:14 -0300 Subject: [PATCH 1/3] chore: add skeleton mock loading state --- src/App.vue | 56 ++++++++++++++++++++++++++++---- src/components/VideoCarousel.vue | 42 ++++++++++++++++-------- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/App.vue b/src/App.vue index 6280cb1..e8e8a72 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,5 @@ @@ -55,9 +72,34 @@ function handleRedirectPlayer() {
- - + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
diff --git a/src/components/VideoCarousel.vue b/src/components/VideoCarousel.vue index 170ea9c..4196b44 100644 --- a/src/components/VideoCarousel.vue +++ b/src/components/VideoCarousel.vue @@ -5,7 +5,7 @@ {{ title }} -
+
-
+
-
- -
-
-
+ + +
+
@@ -48,14 +62,15 @@ interface Video { } interface VideoCarouselProps { - title: string; - videos: Video[]; + title: string; + videos: Video[]; + loading?: boolean; } const props = defineProps(); const emit = defineEmits<{ (e: 'select', id: string): void }>(); -const { title, videos } = props; +const { title, videos, loading = false } = props as any; const [emblaRef, emblaApi] = emblaCarouselVue({ loop: false, @@ -66,6 +81,7 @@ const canPrev = ref(false); const canNext = ref(false); const slidesPerView = ref(5); const slidesToScroll = ref(3); +const skeletonCount = ref(10); const emblaRootEl = ref(null); const emblaWrapperRef = ref(null); From bcda0b56f703cde1f78d629dd02b689555da6099 Mon Sep 17 00:00:00 2001 From: Andre Torquato Date: Wed, 5 Nov 2025 01:06:06 -0300 Subject: [PATCH 2/3] chore: use manifest remote to load video data --- src/App.vue | 2 +- src/catalogData.ts | 4 +++- src/components/VideoCard.vue | 30 ++++++++++++++++++++++++++---- src/components/VideoCarousel.vue | 1 + src/utils/manifestLoader.ts | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 src/utils/manifestLoader.ts diff --git a/src/App.vue b/src/App.vue index e8e8a72..02b5d47 100644 --- a/src/App.vue +++ b/src/App.vue @@ -48,7 +48,7 @@ onMounted(async () => { }); function handleRedirectPlayer() { - const manifestUrl = "https://d2ov2y0lwp0e6t.cloudfront.net/videos/video1/manifest.remote.json"; + const manifestUrl = "https://d2ov2y0lwp0e6t.cloudfront.net/videos/video2/manifest.remote.json"; if ((window as any)?.shellNavigate) { (window as any).shellNavigate(`/player?manifestUrl=${encodeURIComponent(manifestUrl)}`); } diff --git a/src/catalogData.ts b/src/catalogData.ts index 82f1318..5862439 100644 --- a/src/catalogData.ts +++ b/src/catalogData.ts @@ -13,6 +13,7 @@ export type CatalogVideo = { technologies?: string[]; learnings?: string[]; relatedIds?: string[]; + manifestUrl?: string; // URL do manifest.remote.json para buscar thumbnail e duration real }; export type CatalogCarousel = { @@ -75,7 +76,8 @@ export const catalogData: CatalogData = { 'Compartilhamento de componentes', 'Gestão de dependências' ], - relatedIds: ['2', '3', '5'] + relatedIds: ['2', '3', '5'], + manifestUrl: 'https://d2ov2y0lwp0e6t.cloudfront.net/videos/video2/manifest.remote.json' }, { id: '2', diff --git a/src/components/VideoCard.vue b/src/components/VideoCard.vue index f52620f..34f4037 100644 --- a/src/components/VideoCard.vue +++ b/src/components/VideoCard.vue @@ -3,13 +3,13 @@ @mouseleave="() => isHovered = false" @click="() => emit('select', id)">
-
- {{ duration }} + {{ displayDuration }}
import { Play, Plus, ThumbsUp } from 'lucide-vue-next'; -import { ref } from 'vue'; +import { ref, onMounted, computed } from 'vue'; +import { loadManifestData, formatDuration } from '../utils/manifestLoader'; interface VideoCardProps { id: string; @@ -55,13 +56,34 @@ interface VideoCardProps { thumbnail: string; duration: string; category: string; + manifestUrl?: string; } const isHovered = ref(false); +const manifestThumbnail = ref(null); +const manifestDuration = ref(null); const props = defineProps(); const emit = defineEmits<{ (e: 'select', id: string): void }>(); -const { id, title, thumbnail, duration, category } = props as any; +const { id, title, thumbnail, duration, category, manifestUrl } = props as any; + +const displayThumbnail = computed(() => manifestThumbnail.value || thumbnail); + +const displayDuration = computed(() => manifestDuration.value || duration); + +onMounted(async () => { + if (manifestUrl) { + const manifestData = await loadManifestData(manifestUrl); + if (manifestData) { + if (manifestData.thumbnailUrl) { + manifestThumbnail.value = manifestData.thumbnailUrl; + } + if (manifestData.duration) { + manifestDuration.value = formatDuration(manifestData.duration); + } + } + } +}); \ No newline at end of file diff --git a/src/components/VideoCarousel.vue b/src/components/VideoCarousel.vue index 4196b44..458ee47 100644 --- a/src/components/VideoCarousel.vue +++ b/src/components/VideoCarousel.vue @@ -59,6 +59,7 @@ interface Video { thumbnail: string; duration: string; category: string; + manifestUrl?: string; } interface VideoCarouselProps { diff --git a/src/utils/manifestLoader.ts b/src/utils/manifestLoader.ts new file mode 100644 index 0000000..5be6221 --- /dev/null +++ b/src/utils/manifestLoader.ts @@ -0,0 +1,32 @@ +export interface ManifestData { + thumbnailUrl?: string; + duration?: number; +} + +export async function loadManifestData(manifestUrl: string): Promise { + try { + const response = await fetch(manifestUrl); + if (!response.ok) { + return null; + } + const manifest = await response.json(); + return { + thumbnailUrl: manifest.thumbnailUrl, + duration: manifest.duration + }; + } catch (error) { + console.error('Erro ao carregar manifest:', error); + return null; + } +} + +export function formatDuration(seconds: number): string { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (hours > 0) { + return `${hours}h ${minutes}min`; + } + return `${minutes} min`; +} + From f04b7c19407b2ccfba0afc3a292cfc92bda7df1c Mon Sep 17 00:00:00 2001 From: Andre Torquato Date: Wed, 5 Nov 2025 23:11:14 -0300 Subject: [PATCH 3/3] feat: add navigation to player by video id and share data between mfes --- src/App.vue | 7 +- src/components/VideoCard.vue | 7 +- src/components/VideoDetailsDialog.vue | 9 +- src/shared/hash.ts | 47 ++++++++++ src/shared/index.ts | 37 ++++++++ src/shared/navigation.ts | 57 ++++++++++++ src/shared/registry.ts | 120 ++++++++++++++++++++++++++ src/shared/types.ts | 48 +++++++++++ vite.config.js | 1 + 9 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 src/shared/hash.ts create mode 100644 src/shared/index.ts create mode 100644 src/shared/navigation.ts create mode 100644 src/shared/registry.ts create mode 100644 src/shared/types.ts diff --git a/src/App.vue b/src/App.vue index 02b5d47..c538694 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,6 +6,7 @@ import Hero from './components/Hero.vue'; import VideoCarousel from './components/VideoCarousel.vue'; import VideoDetailsDialog from './components/VideoDetailsDialog.vue'; import catalogData from './catalogData'; +import { navigateToPlayerByVideoId } from './shared/navigation'; const modalDetailOpen = ref(false); @@ -48,10 +49,8 @@ onMounted(async () => { }); function handleRedirectPlayer() { - const manifestUrl = "https://d2ov2y0lwp0e6t.cloudfront.net/videos/video2/manifest.remote.json"; - if ((window as any)?.shellNavigate) { - (window as any).shellNavigate(`/player?manifestUrl=${encodeURIComponent(manifestUrl)}`); - } + // Usa o vídeo do hero (id: '1') + navigateToPlayerByVideoId('1'); } diff --git a/src/components/VideoCard.vue b/src/components/VideoCard.vue index 34f4037..52248cd 100644 --- a/src/components/VideoCard.vue +++ b/src/components/VideoCard.vue @@ -27,7 +27,7 @@