From 2f4b331f86c286ac38982574c7b7321fdc53fb0b Mon Sep 17 00:00:00 2001 From: Lucas Pains Date: Thu, 26 Dec 2024 17:41:40 -0700 Subject: [PATCH] Add video thumbnails to the reciple display pages (#377) --- ...c-web-apps-delightful-flower-0c3edd710.yml | 17 ----------------- GitVersion.yml | 9 --------- src/components/RecipeCard.vue | 5 +++-- src/components/RecipeList.vue | 5 ++--- src/helpers/videoHelpers.ts | 19 +++++++++++++++---- src/pages/recipe/[id]/print.vue | 6 +++--- src/services/dataService.ts | 14 ++++++++++++-- tests/category-recipes.spec.ts | 2 +- tests/edit.spec.ts | 18 ++++++++++++++++++ 9 files changed, 54 insertions(+), 41 deletions(-) delete mode 100644 GitVersion.yml diff --git a/.github/workflows/azure-static-web-apps-delightful-flower-0c3edd710.yml b/.github/workflows/azure-static-web-apps-delightful-flower-0c3edd710.yml index 569a50a4..2afef63a 100644 --- a/.github/workflows/azure-static-web-apps-delightful-flower-0c3edd710.yml +++ b/.github/workflows/azure-static-web-apps-delightful-flower-0c3edd710.yml @@ -46,23 +46,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.7 - with: - versionSpec: '5.x' - - - name: Determine Version - id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.7 - with: - useConfigFile: true - - - name: Set Version number in .env - uses: datamonsters/replace-action@v2 - with: - files: '.env' - replacements: 'DEV=${{ steps.gitversion.outputs.majorMinorPatch }}' - - name: Build And Deploy id: builddeploy uses: Azure/static-web-apps-deploy@v1 diff --git a/GitVersion.yml b/GitVersion.yml deleted file mode 100644 index aa131aeb..00000000 --- a/GitVersion.yml +++ /dev/null @@ -1,9 +0,0 @@ -mode: Mainline -branches: {} -ignore: - sha: [] -major-version-bump-message: '^(breaking|major):' -minor-version-bump-message: '^(feature|minor):' -patch-version-bump-message: '^(fix|patch):' -no-bump-message: '^(none|skip):' -merge-message-formats: {} \ No newline at end of file diff --git a/src/components/RecipeCard.vue b/src/components/RecipeCard.vue index 926248a8..d495e494 100644 --- a/src/components/RecipeCard.vue +++ b/src/components/RecipeCard.vue @@ -24,7 +24,8 @@ const emit = defineEmits<{ cursor-pointer ">
- Recipe + Recipe
@@ -38,7 +39,7 @@ const emit = defineEmits<{
{{ props.title - }} + }}
⭐{{ diff --git a/src/components/RecipeList.vue b/src/components/RecipeList.vue index 1863e01c..98408e7f 100644 --- a/src/components/RecipeList.vue +++ b/src/components/RecipeList.vue @@ -3,7 +3,7 @@ import { ref, onMounted, watch, nextTick, onBeforeUnmount } from "vue"; import { useRouter } from "vue-router"; import { useTranslation } from "i18next-vue"; import { useState } from "../services/store"; -import { getRecipesByCategory, getRecipeMedia, initialize, saveSetting, getSetting, getRecipes } from "../services/dataService"; +import { getRecipesByCategory, getRecipeMediaUrl, initialize, saveSetting, getSetting, getRecipes } from "../services/dataService"; import { RecipeViewModel } from "../pages/recipe/recipeViewModel"; import debounce from "lodash.debounce"; import { TransitionRoot, Menu, MenuButton, MenuItems, MenuItem } from "@headlessui/vue"; @@ -162,8 +162,7 @@ async function loadCategory() { } for (const recipe of allRecipes) { - const item = await getRecipeMedia(recipe.id || 0); - recipe.image = (item && item.url) || undefined; + recipe.image = await getRecipeMediaUrl(recipe.id || 0); recipe.imageAvailable = recipe.image ? true : false; } diff --git a/src/helpers/videoHelpers.ts b/src/helpers/videoHelpers.ts index 44fba9af..0543999c 100644 --- a/src/helpers/videoHelpers.ts +++ b/src/helpers/videoHelpers.ts @@ -1,3 +1,4 @@ + export function isVideoUrlSupported(url: string) { return !url || url.startsWith("https://www.youtube.com/") @@ -5,7 +6,19 @@ export function isVideoUrlSupported(url: string) { || url.startsWith("https://youtu.be/"); } +export function getThumbnail(url: string) { + const id = getVideoId(url); + + return `https://img.youtube.com/vi/${id}/0.jpg`; +} + export function prepareUrlForEmbed(url: string) { + const id = getVideoId(url); + + return `https://www.youtube.com/embed/${id}`; +} + +function getVideoId(url: string) { const regexp = /watch\?v=([\w-]*)|embed\/([\w-]*)|youtu\.be\/([\w-]*)/; // get the capture group from regexp @@ -13,11 +26,9 @@ export function prepareUrlForEmbed(url: string) { if (!match) { return ""; } - + // there are 3 possible matches in the regex // the code below checks for all of them // and returns the first one that is not null - const id = match[1] || match[2] || match[3]; - - return `https://www.youtube.com/embed/${id}`; + return match[1] || match[2] || match[3]; } \ No newline at end of file diff --git a/src/pages/recipe/[id]/print.vue b/src/pages/recipe/[id]/print.vue index df0a7ae2..a7361ec4 100644 --- a/src/pages/recipe/[id]/print.vue +++ b/src/pages/recipe/[id]/print.vue @@ -3,7 +3,7 @@ import { ref, onMounted, computed } from "vue"; import { useRoute, onBeforeRouteLeave } from "vue-router"; import { getRecipe, - getRecipeMedia, + getRecipeMediaUrl, getSetting } from "../../../services/dataService"; import { useTranslation } from "i18next-vue"; @@ -57,14 +57,14 @@ onMounted(async () => { ]; const recipe = (await getRecipe(id.value)) as RecipeViewModel; - const image = await getRecipeMedia(id.value); + const image = await getRecipeMediaUrl(id.value); const defaultTimeSetting = await getSetting("StepsInterval", "5"); if (recipe) { state.title = recipe.title; item.value = recipe; - item.value.image = image?.url; + item.value.image = image; displayTime.value = getRecipeDisplayTime(recipe, parseInt(defaultTimeSetting)); } diff --git a/src/services/dataService.ts b/src/services/dataService.ts index 8184927e..c1e3d7cb 100644 --- a/src/services/dataService.ts +++ b/src/services/dataService.ts @@ -3,6 +3,7 @@ import { BackupModel, RecipeBackupModel } from "../pages/recipe/backupModel" import { Recipe, RecipeImage, RecipeMedia, RecipeNutrition } from "./recipe"; import { Setting } from "./setting"; import { Category } from "./category"; +import { getThumbnail } from "../helpers/videoHelpers"; class RecipeDatabase extends Dexie { public recipes!: Table; @@ -103,10 +104,19 @@ export async function getRecipeMediaList(id: number): Promise { } export async function getRecipeMedia(id: number): Promise { - const result = await db.recipeMedia + return await db.recipeMedia .where("recipeId").equals(id) - .and(item => item.type == "img") .first(); +} + +export async function getRecipeMediaUrl(id: number): Promise { + const media = await getRecipeMedia(id); + + let result = media?.url; + if (media?.type == "vid") + { + result = getThumbnail(media.url); + } return result; } diff --git a/tests/category-recipes.spec.ts b/tests/category-recipes.spec.ts index 6549bb3e..70523b9c 100644 --- a/tests/category-recipes.spec.ts +++ b/tests/category-recipes.spec.ts @@ -18,7 +18,7 @@ test.beforeEach(async ({ page }) => { async function goToCategory(page: Page, category: string) { await page.goto('/'); await page.getByText(category).click(); - await page.waitForTimeout(200); + await page.waitForTimeout(500); } test('title is Category 1', async ({ page }) => { diff --git a/tests/edit.spec.ts b/tests/edit.spec.ts index 89cc2a39..662cb09b 100644 --- a/tests/edit.spec.ts +++ b/tests/edit.spec.ts @@ -99,6 +99,24 @@ test('add video', async ({ page, browserName, isMobile }) => { .toHaveAttribute("src", "https://www.youtube.com/embed/0YY7K7Xa5rE"); }); +test('video media shows thumbnail', async ({ page, browserName, isMobile }) => { + await page.goto('/'); + await page.getByText('Sourdough Bread').first().click(); + await page.getByTestId('edit-button').click(); + await page.getByTestId('remove-image-button').click(); + await page.getByTestId('add-video-button').click(); + await page.getByTestId('add-video-url').fill("https://www.youtube.com/watch?v=0YY7K7Xa5rE"); + await page.getByRole("button").getByText("OK").click(); + + await expect(page.locator("iframe")) + .toHaveAttribute("src", "https://www.youtube.com/embed/0YY7K7Xa5rE"); + + await page.getByTestId("topbar-single-button").click(); + + await page.goto('/'); + await expect(page.getByTestId('recipe-image')).toHaveAttribute("src", "https://img.youtube.com/vi/0YY7K7Xa5rE/0.jpg") +}); + test('remove media', async ({ page }) => { await page.goto('/'); await page.getByText('Sourdough Bread').first().click();