Skip to content

Commit

Permalink
Add video thumbnails to the reciple display pages (#377)
Browse files Browse the repository at this point in the history
  • Loading branch information
jlucaspains authored Dec 27, 2024
1 parent 1e57666 commit 2f4b331
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 0 additions & 9 deletions GitVersion.yml

This file was deleted.

5 changes: 3 additions & 2 deletions src/components/RecipeCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const emit = defineEmits<{
cursor-pointer
">
<div style="height: calc(100% - 0.5rem)" class="-mx-5 -mt-5 overflow-hidden">
<img alt="Recipe" v-if="props.imageAvailable" :src="props.image" class="object-contain m-auto" />
<img alt="Recipe" v-if="props.imageAvailable" :src="props.image" class="object-contain m-auto"
data-testid="recipe-image" />
<div v-else class="bg-theme-primary h-full grid place-items-center">
<svg class="h-16 w-16 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
Expand All @@ -38,7 +39,7 @@ const emit = defineEmits<{
<div class="truncate grow pe-2">
<span data-testid="recipe-title" class="text-ellipsis text-black dark:text-white text-lg">{{
props.title
}}</span>
}}</span>
</div>
<div class="my-auto" v-if="props.rating > 0">
<span data-testid="recipe-score" class="text-black dark:text-white">⭐{{
Expand Down
5 changes: 2 additions & 3 deletions src/components/RecipeList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
Expand Down
19 changes: 15 additions & 4 deletions src/helpers/videoHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@

export function isVideoUrlSupported(url: string) {
return !url
|| url.startsWith("https://www.youtube.com/")
|| url.startsWith("https://m.youtube.com/")
|| 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
const match = regexp.exec(url);
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];
}
6 changes: 3 additions & 3 deletions src/pages/recipe/[id]/print.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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));
}
Expand Down
14 changes: 12 additions & 2 deletions src/services/dataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Recipe, number>;
Expand Down Expand Up @@ -103,10 +104,19 @@ export async function getRecipeMediaList(id: number): Promise<RecipeMedia[]> {
}

export async function getRecipeMedia(id: number): Promise<RecipeMedia | undefined> {
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<string | undefined> {
const media = await getRecipeMedia(id);

let result = media?.url;
if (media?.type == "vid")
{
result = getThumbnail(media.url);
}

return result;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/category-recipes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down
18 changes: 18 additions & 0 deletions tests/edit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 2f4b331

Please sign in to comment.