-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Joery
committed
Apr 3, 2024
1 parent
7af9d8d
commit a247378
Showing
8 changed files
with
186 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
178 changes: 142 additions & 36 deletions
178
packages/safelight/src/components/Editor/Library/Library.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,156 @@ | ||
<template> | ||
<SLCard> | ||
<DataView | ||
:value="media" | ||
scrollable | ||
scroll-height="400px" | ||
data-key="id" | ||
class="m-4 resize-x" | ||
> | ||
<template #header> | ||
<Toolbar class="p-1"> | ||
<template #center> | ||
<IconField iconPosition="left"> | ||
<InputIcon> <PhMagnifyingGlass /> </InputIcon> | ||
<InputText size="small" placeholder="Search"> </InputText> | ||
</IconField> | ||
</template> | ||
</Toolbar> | ||
</template> | ||
<template #list="{ items }: { items: UnwrapRef<Media>[] }"> | ||
<DataView :value="sortedAndFiltered" scrollable scroll-height="400px" data-key="id"> | ||
<template #header> | ||
<Toolbar class="border-none p-0"> | ||
<template #start> | ||
<InputGroup class="mr-2"> | ||
<InputGroupAddon class="p-0"> | ||
<PhMagnifyingGlass size="14" /> | ||
</InputGroupAddon> | ||
<InputText v-model="search" placeholder="Search"> </InputText> | ||
</InputGroup> | ||
<InputGroup> | ||
<Button | ||
size="small" | ||
class="p-0" | ||
outlined | ||
severity="secondary" | ||
:aria-label="'Sort ' + (sortDescending ? 'ascending' : 'descending')" | ||
@click="sortDescending = !sortDescending" | ||
> | ||
<template #icon> | ||
<PhSortAscending v-if="!sortDescending" size="14" /> | ||
<PhSortDescending v-else size="14" /> | ||
</template> | ||
</Button> | ||
<Dropdown | ||
v-model="sortBy" | ||
style="line-height: 1.2" | ||
aria-label="Sort by" | ||
:options="['Name', 'Duration', 'File type']" | ||
/> | ||
</InputGroup> | ||
</template> | ||
</Toolbar> | ||
</template> | ||
<template #list="{ items }: { items: UnwrapRef<Media>[] }"> | ||
<div | ||
class="grid-nogutter grid" | ||
role="grid" | ||
style="grid-template-columns: repeat(auto-fill, minmax(150px, 1fr))" | ||
> | ||
<div | ||
class="grid-nogutter grid" | ||
style="grid-template-columns: repeat(auto-fill, minmax(150px, 1fr))" | ||
v-for="item in items" | ||
:key="item.id" | ||
role="gridcell" | ||
class="border-round m-1 flex min-h-32 flex-col rounded-md border-solid border-white/10" | ||
style="border-width: 1px" | ||
:aria-label="item.name" | ||
> | ||
<div | ||
v-for="(item, index) in items" | ||
:key="item.id" | ||
class="border-round m-2 flex min-h-32 flex-col rounded-md border-solid border-white/10" | ||
style="border-width: 1px" | ||
class="bg-checkerboard flex aspect-video w-full items-center justify-center" | ||
> | ||
<div class="flex aspect-video w-full items-center justify-center"> | ||
<img class="max-h-full max-w-full rounded-t" :src="item.previewImage" /> | ||
</div> | ||
<p class="text-sm"> | ||
{{ item.name }} | ||
</p> | ||
<img | ||
v-if="item.previewImage" | ||
class="max-h-full max-w-full rounded-t-md" | ||
:aria-label="'Preview image for ' + item.name" | ||
:src="item.previewImage" | ||
/> | ||
<Skeleton | ||
v-else | ||
class="max-h-full max-w-full rounded-none rounded-t-md" | ||
height="100%" | ||
width="100%" | ||
/> | ||
</div> | ||
<p | ||
v-tooltip.bottom="{ value: item.name, showDelay: 1000 }" | ||
class="m-0 mt-1 max-w-full overflow-x-hidden overflow-ellipsis text-base" | ||
> | ||
{{ item.name }} | ||
</p> | ||
<p v-if="item.duration > 0"> | ||
{{ item.duration }} | ||
</p> | ||
</div> | ||
</template> | ||
</DataView> | ||
</SLCard> | ||
</div> | ||
</template> | ||
</DataView> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import type Media from '@/controllers/Media/Media'; | ||
import { PhFileSearch } from '@phosphor-icons/vue'; | ||
import { PhMagnifyingGlass, PhSortDescending } from '@phosphor-icons/vue'; | ||
import fuzzysearch from 'fuzzysearch'; | ||
import InputGroup from 'primevue/inputgroup'; | ||
import InputGroupAddon from 'primevue/inputgroupaddon'; | ||
import type { UnwrapRef } from 'vue'; | ||
const media = defineModel<(Media | UnwrapRef<Media>)[]>({ default: [] }); | ||
const props = defineProps<{ | ||
media: UnwrapRef<Media[]>; | ||
}>(); | ||
const emit = defineEmits<{ | ||
'update:modelValue': any[]; | ||
}>(); | ||
const media = useVModel(props, 'media', emit); | ||
const search = ref(''); | ||
const sortBy = ref<sortOptions>('Name'); | ||
const sortDescending = ref(false); | ||
const sortedAndFiltered = ref<Media[]>([]); | ||
watchDebounced([props.media, search, sortBy, sortDescending], sortAndFilter, { | ||
deep: true, | ||
debounce: 100, | ||
maxWait: 1000, | ||
immediate: true | ||
}); | ||
function sortAndFilter() { | ||
const filtered = media.value.filter((elem) => { | ||
if (search.value.length == 0) { | ||
return true; | ||
} | ||
return fuzzysearch(search.value.toLowerCase(), elem.name.toLowerCase()); | ||
}); | ||
filtered.sort((a, b) => { | ||
const item1 = sortDescending.value ? b : a; | ||
const item2 = sortDescending.value ? a : b; | ||
switch (sortBy.value) { | ||
case 'Duration': | ||
return item1.duration - item2.duration; | ||
case 'File type': | ||
return (item1.name.split('.').at(-1) ?? 'ZZZ').localeCompare( | ||
item2.name.split('.').at(-1) ?? 'ZZZZZ' | ||
); | ||
default: | ||
return item1.name.localeCompare(item2.name, undefined, { | ||
numeric: true, | ||
sensitivity: 'base' | ||
}); | ||
} | ||
}); | ||
sortedAndFiltered.value = filtered; | ||
} | ||
type sortOptions = 'Name' | 'Duration' | 'File type'; | ||
</script> | ||
|
||
<style lang="scss" scoped> | ||
.bg-checkerboard { | ||
/* This is beautifully simple | ||
https://stackoverflow.com/a/65129916 */ | ||
background: repeating-conic-gradient(#ffffff0a 0% 25%, transparent 0% 50%) 50% / 20px 20px; | ||
} | ||
:deep(.p-dataview-header) { | ||
@apply p-1; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.