fix: filter library videos in lesson with videos in the current lesson
This commit is contained in:
+1
-1
@@ -45,7 +45,7 @@
|
|||||||
<Dialog.Title>{$t('course.navItem.lessons.materials.tabs.video.add_video.title')}</Dialog.Title>
|
<Dialog.Title>{$t('course.navItem.lessons.materials.tabs.video.add_video.title')}</Dialog.Title>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
|
|
||||||
<UnderlineTabs.Root bind:value={currentTab} class="">
|
<UnderlineTabs.Root bind:value={currentTab} class="min-w-0">
|
||||||
<UnderlineTabs.List>
|
<UnderlineTabs.List>
|
||||||
{#each tabs as item (item.value)}
|
{#each tabs as item (item.value)}
|
||||||
<UnderlineTabs.Trigger
|
<UnderlineTabs.Trigger
|
||||||
|
|||||||
-20
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Avatar from '@cio/ui/base/avatar';
|
|
||||||
import { Image } from '$features/ui';
|
import { Image } from '$features/ui';
|
||||||
import VideoIcon from '@lucide/svelte/icons/video';
|
import VideoIcon from '@lucide/svelte/icons/video';
|
||||||
import { ExternalLinkIcon, HoverableItem } from '@cio/ui/custom/moving-icons';
|
import { ExternalLinkIcon, HoverableItem } from '@cio/ui/custom/moving-icons';
|
||||||
@@ -36,16 +35,6 @@
|
|||||||
|
|
||||||
const isYoutubeWithLink = $derived(video.type === 'youtube' && !!video.link);
|
const isYoutubeWithLink = $derived(video.type === 'youtube' && !!video.link);
|
||||||
|
|
||||||
const courseLogoSrc = $derived.by(() => {
|
|
||||||
const raw = courseApi.course?.logo;
|
|
||||||
|
|
||||||
if (typeof raw === 'string' && raw.trim().length > 0) {
|
|
||||||
return raw.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const channelLine = $derived.by(() => {
|
const channelLine = $derived.by(() => {
|
||||||
const courseTitle = courseApi.course?.title?.trim();
|
const courseTitle = courseApi.course?.title?.trim();
|
||||||
|
|
||||||
@@ -104,15 +93,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3 flex min-w-0 gap-3 px-3 py-3">
|
<div class="mt-3 flex min-w-0 gap-3 px-3 py-3">
|
||||||
<Avatar.Root class="mt-0.5 size-9 shrink-0">
|
|
||||||
{#if courseLogoSrc}
|
|
||||||
<Avatar.Image src={courseLogoSrc} alt={channelLine} />
|
|
||||||
{/if}
|
|
||||||
<Avatar.Fallback>
|
|
||||||
<VideoIcon class="ui:text-muted-foreground size-4" />
|
|
||||||
</Avatar.Fallback>
|
|
||||||
</Avatar.Root>
|
|
||||||
|
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<div class="flex items-start gap-1">
|
<div class="flex items-start gap-1">
|
||||||
<div class="ui:text-foreground min-w-0 flex-1 text-base leading-snug font-semibold" {title}>
|
<div class="ui:text-foreground min-w-0 flex-1 text-base leading-snug font-semibold" {title}>
|
||||||
|
|||||||
+33
-10
@@ -3,6 +3,7 @@
|
|||||||
import { Spinner } from '@cio/ui/base/spinner';
|
import { Spinner } from '@cio/ui/base/spinner';
|
||||||
import { Empty } from '@cio/ui/custom/empty';
|
import { Empty } from '@cio/ui/custom/empty';
|
||||||
import { InputField } from '@cio/ui/custom/input-field';
|
import { InputField } from '@cio/ui/custom/input-field';
|
||||||
|
import VideoIcon from '@lucide/svelte/icons/video';
|
||||||
import { lessonApi } from '$features/course/api';
|
import { lessonApi } from '$features/course/api';
|
||||||
import { mediaApi } from '$features/media/api';
|
import { mediaApi } from '$features/media/api';
|
||||||
import type { OrganizationAsset } from '$features/media/utils';
|
import type { OrganizationAsset } from '$features/media/utils';
|
||||||
@@ -21,10 +22,21 @@
|
|||||||
let search = $state('');
|
let search = $state('');
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let addingAssetId = $state<string | null>(null);
|
let addingAssetId = $state<string | null>(null);
|
||||||
// Local copy so adding a video swipes it out of this list without mutating the
|
// Local copy so the fetch (which also backs the media manager page via the
|
||||||
// shared mediaApi.assets store (which also backs the media manager page).
|
// shared mediaApi.assets store) doesn't leak removals between surfaces.
|
||||||
let assets = $state<OrganizationAsset[]>([]);
|
let assets = $state<OrganizationAsset[]>([]);
|
||||||
|
|
||||||
|
// Asset ids already attached to this lesson, so the library hides them.
|
||||||
|
const addedAssetIds = $derived(
|
||||||
|
new Set(
|
||||||
|
(lessonApi.lesson?.videos ?? [])
|
||||||
|
.map((video) => (video as { assetId?: string }).assetId)
|
||||||
|
.filter((id): id is string => Boolean(id))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const visibleAssets = $derived(assets.filter((asset) => !addedAssetIds.has(asset.id)));
|
||||||
|
|
||||||
async function loadLibrary() {
|
async function loadLibrary() {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
await mediaApi.listAssets({
|
await mediaApi.listAssets({
|
||||||
@@ -53,7 +65,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
lessonApi.updateLessonState('videos', [lessonVideo], { append: true });
|
lessonApi.updateLessonState('videos', [lessonVideo], { append: true });
|
||||||
assets = assets.filter((item) => item.id !== asset.id);
|
|
||||||
snackbar.success('snackbar.media_manager.added_to_lesson');
|
snackbar.success('snackbar.media_manager.added_to_lesson');
|
||||||
} finally {
|
} finally {
|
||||||
addingAssetId = null;
|
addingAssetId = null;
|
||||||
@@ -65,7 +76,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="space-y-3">
|
<div class="min-w-0 space-y-3">
|
||||||
<div class="flex items-end gap-3">
|
<div class="flex items-end gap-3">
|
||||||
<InputField
|
<InputField
|
||||||
label={$t('course.navItem.lessons.materials.tabs.video.add_video.search_library')}
|
label={$t('course.navItem.lessons.materials.tabs.video.add_video.search_library')}
|
||||||
@@ -84,24 +95,36 @@
|
|||||||
icon={Spinner}
|
icon={Spinner}
|
||||||
class="ui:max-h-40 py-6"
|
class="ui:max-h-40 py-6"
|
||||||
/>
|
/>
|
||||||
{:else if assets.length === 0}
|
{:else if visibleAssets.length === 0}
|
||||||
<p class="ui:text-muted-foreground text-sm">
|
<p class="ui:text-muted-foreground text-sm">
|
||||||
{$t('course.navItem.lessons.materials.tabs.video.add_video.no_library_videos')}
|
{$t('course.navItem.lessons.materials.tabs.video.add_video.no_library_videos')}
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="max-h-[360px] space-y-2 overflow-x-hidden">
|
<div class="max-h-[360px] space-y-2 overflow-x-hidden">
|
||||||
{#each assets as asset (asset.id)}
|
{#each visibleAssets as asset (asset.id)}
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between rounded-md border p-3"
|
class="flex items-center justify-between gap-3 rounded-md border p-3"
|
||||||
animate:flip={{ duration: 250 }}
|
animate:flip={{ duration: 250 }}
|
||||||
out:fly={{ x: -400, duration: 300 }}
|
out:fly={{ x: -400, duration: 300 }}
|
||||||
>
|
>
|
||||||
<div class="max-w-md">
|
<div class="flex min-w-0 flex-1 items-center gap-3">
|
||||||
<p class="truncate text-sm font-medium wrap-break-word">{asset.title || asset.storageKey || asset.id}</p>
|
<div
|
||||||
<p class="ui:text-muted-foreground text-xs">{asset.provider}</p>
|
class="ui:bg-muted/40 flex h-12 w-20 shrink-0 items-center justify-center overflow-hidden rounded border"
|
||||||
|
>
|
||||||
|
{#if asset.thumbnailUrl}
|
||||||
|
<img src={asset.thumbnailUrl} alt={asset.title ?? ''} class="h-full w-full object-cover" />
|
||||||
|
{:else}
|
||||||
|
<VideoIcon class="ui:text-muted-foreground size-5" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p class="truncate text-sm font-medium">{asset.title || asset.storageKey || asset.id}</p>
|
||||||
|
<p class="ui:text-muted-foreground text-xs">{asset.provider}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
class="shrink-0"
|
||||||
onclick={() => addFromLibrary(asset)}
|
onclick={() => addFromLibrary(asset)}
|
||||||
loading={addingAssetId === asset.id}
|
loading={addingAssetId === asset.id}
|
||||||
disabled={addingAssetId !== null}
|
disabled={addingAssetId !== null}
|
||||||
|
|||||||
Reference in New Issue
Block a user