Skip to content
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

Add video thumbnails to the reciple display pages #377

Merged
merged 4 commits into from
Dec 27, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
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
@@ -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">
@@ -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">⭐{{
5 changes: 2 additions & 3 deletions src/components/RecipeList.vue
Original file line number Diff line number Diff line change
@@ -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;
}

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
@@ -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));
}

14 changes: 12 additions & 2 deletions src/services/dataService.ts
Original file line number Diff line number Diff line change
@@ -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>;
@@ -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;
}
2 changes: 1 addition & 1 deletion tests/category-recipes.spec.ts
Original file line number Diff line number Diff line change
@@ -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 }) => {
18 changes: 18 additions & 0 deletions tests/edit.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
Loading