Skip to content

Commit

Permalink
feat: filter by year/month and wrap search cols on different size dis…
Browse files Browse the repository at this point in the history
…plays
  • Loading branch information
sentriz committed Sep 23, 2021
1 parent dc18179 commit c87d94e
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 34 deletions.
8 changes: 8 additions & 0 deletions backend/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ type SearchMediasOptions struct {
Offset int
SortField string
SortOrder string
DateFrom time.Time
DateTo time.Time
}

func (db *DB) GetMediaByID(id int) (*Media, error) {
Expand Down Expand Up @@ -212,6 +214,12 @@ func (db *DB) SearchMedias(options SearchMediasOptions) ([]*Media, error) {
if options.Media != "" {
q = q.Where(sq.Eq{"medias.type": options.Media})
}
if !options.DateFrom.IsZero() {
q = q.Where(sq.GtOrEq{"medias.timestamp": options.DateFrom})
}
if !options.DateTo.IsZero() {
q = q.Where(sq.Lt{"medias.timestamp": options.DateTo})
}

sql, args, _ := q.ToSql()
var results []*Media
Expand Down
2 changes: 2 additions & 0 deletions backend/db/migrations/004_add_timestamp_indexes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
create index idx_medias_timestamp on medias (timestamp);

4 changes: 4 additions & 0 deletions backend/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ type ServeSearchPayload struct {
Field string `json:"field"`
Order string `json:"order"`
} `json:"sort"`
DateFrom time.Time `json:"date_from"`
DateTo time.Time `json:"date_to"`
}

func (s *Server) serveSearch(w http.ResponseWriter, r *http.Request) {
Expand All @@ -305,6 +307,8 @@ func (s *Server) serveSearch(w http.ResponseWriter, r *http.Request) {
SortOrder: payload.Sort.Order,
Directory: payload.Directory,
Media: db.MediaType(payload.Media),
DateFrom: payload.DateFrom,
DateTo: payload.DateTo,
})
if err != nil {
resp.Errorf(w, 500, "searching medias: %v", err)
Expand Down
2 changes: 2 additions & 0 deletions frontend/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export interface PayloadSearch {
sort: PayloadSort
directory?: string
media?: MediaType
date_from?: Date
date_to?: Date
}

export const reqSearch = (data: PayloadSearch) => {
Expand Down
90 changes: 60 additions & 30 deletions frontend/components/Search.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
<template>
<div class="space-y-6">
<div class="md:flex-row flex flex-col gap-2">
<input v-model="reqQuery" class="inp w-full" type="text" placeholder="enter media text query" />
<search-filter label="sort by" :items="reqSortOptions" v-model:selected="reqSortOption" />
<search-filter label="media" :items="reqMediaOptions" v-model:selected="reqMediaOption" />
<search-filter label="directory" :items="reqDirOptions" v-model:selected="reqDirOption" />
<div class="md:grid-cols-2 lg:grid-cols-12 grid grid-cols-1 gap-2">
<input class="lg:col-span-9 inp" v-model="reqQuery" type="text" placeholder="enter media text query" />
<search-filter class="lg:col-span-3" label="sort by" :items="reqSortOptions" v-model:selected="reqSort" />
<search-filter class="lg:col-span-3" label="media" :items="reqMediaOptions" v-model:selected="reqMedia" />
<search-filter class="lg:col-span-3" label="directory" :items="reqDirOptions" v-model:selected="reqDir" />
<search-filter class="lg:col-span-3" label="year" :items="reqYearOptions" v-model:selected="reqYear" />
<search-filter
class="lg:col-span-3"
label="month"
:items="reqMonthOptions"
v-model:selected="reqMonth"
:disabled="!reqYear.year"
/>
</div>
<div ref="scroller">
<p v-if="!loading" class="text-right text-gray-500">fetched {{ respTook.toFixed(2) }}ms</p>
Expand All @@ -14,7 +22,7 @@
<hr class="m-0" />
</div>
<div class="col-resp col-gap-4 space-y-4">
<media-background v-for="hash in page" :key="hash" :hash="hash" class="shadow-lg max-h-[400px] overflow-y-hidden">
<media-background v-for="hash in page" :key="hash" :hash="hash" class="shadow-lg max-h-[400px] overflow-y-auto">
<router-link :to="{ name: 'search', params: { hash } }" class="block">
<media-highlight thumb :hash="hash" />
</router-link>
Expand Down Expand Up @@ -58,6 +66,8 @@ import {
VideoCameraIcon,
FolderAddIcon,
FolderIcon,
CalendarIcon,
GlobeIcon,
} from '@heroicons/vue/outline'
const store = useStore()
Expand All @@ -78,19 +88,48 @@ const reqSortTimeAsc: Sort = { label: 'time asc', icon: ChevronUpIcon, field: 't
const reqSortOptionsDefault = [reqSortTimeDesc, reqSortTimeAsc]
const reqSortOptionsSimilarity = [reqSortSimilarity, reqSortTimeDesc, reqSortTimeAsc]
const reqSortOptions = ref(reqSortOptionsDefault)
const reqSortOption = ref(reqSortTimeDesc)
const reqSort = ref(reqSortTimeDesc)
type Dir = { label: string; icon: Component; directory?: string }
const reqDirAll: Dir = { label: 'all', icon: DocumentDuplicateIcon }
const reqDirOptions = ref([reqDirAll])
const reqDirOption = ref(reqDirAll)
const reqDir = ref(reqDirAll)
onMounted(async () => {
const resp = await reqDirectories()
if (isError(resp)) return
for (const d of resp.result)
reqDirOptions.value.push({
label: d.directory_alias,
icon: d.is_uploads ? FolderAddIcon : FolderIcon,
directory: d.directory_alias,
})
})
type Media = { label: string; icon: Component; media?: MediaType }
const reqMediaAny: Media = { label: 'any', icon: DocumentDuplicateIcon }
const reqMediaImage: Media = { label: 'image', icon: CameraIcon, media: MediaType.Image }
const reqMediaVideo: Media = { label: 'video', icon: VideoCameraIcon, media: MediaType.Video }
const reqMediaOptions = ref([reqMediaAny, reqMediaImage, reqMediaVideo])
const reqMediaOption = ref(reqMediaAny)
const reqMedia = ref(reqMediaAny)
const currentYear = new Date().getFullYear()
type Year = { label: string; icon: Component; year?: number }
const reqYearAny: Year = { label: `any`, icon: CalendarIcon }
const reqYearOptions = ref([reqYearAny])
const reqYear = ref(reqYearAny)
for (let i = currentYear; i >= currentYear - 10; i--)
reqYearOptions.value.push({ label: `${i}`, icon: CalendarIcon, year: i })
type Month = { label: string; icon: Component; month?: number }
const reqMonthAny: Month = { label: `any`, icon: GlobeIcon }
const reqMonthOptions = ref([reqMonthAny])
const reqMonth = ref(reqMonthAny)
for (const [month, label] of ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'].entries())
reqMonthOptions.value.push({ label, icon: CalendarIcon, month })
const respTook = ref(0)
const respHasMore = ref(true)
Expand All @@ -104,9 +143,13 @@ const fetchMedias = async () => {
body: reqQuery.value,
limit: reqPageSize,
offset: reqPageSize * reqPageNum.value,
sort: { field: reqSortOption.value.field, order: reqSortOption.value.order },
directory: reqDirOption.value.directory,
media: reqMediaOption.value.media,
sort: { field: reqSort.value.field, order: reqSort.value.order },
directory: reqDir.value.directory,
media: reqMedia.value.media,
}
if (reqYear.value.year) {
req.date_from = new Date(reqYear.value.year, reqMonth.value.month ?? 0, 1)
req.date_to = new Date(reqYear.value.year, reqMonth.value.month ?? 11, 31)
}
console.log('loading page #%d', reqPageNum.value)
Expand All @@ -130,39 +173,26 @@ const resetParameters = () => {
respHasMore.value = true
}
const resetDirs = () => {
const resetSortOptions = () => {
if (reqQuery.value && !reqSortOptions.value.includes(reqSortSimilarity)) {
reqSortOptions.value = reqSortOptionsSimilarity
reqSortOption.value = reqSortSimilarity
reqSort.value = reqSortSimilarity
}
if (!reqQuery.value && reqSortOptions.value.includes(reqSortSimilarity)) {
reqSortOptions.value = reqSortOptionsDefault
reqSortOption.value = reqSortTimeDesc
reqSort.value = reqSortTimeDesc
}
}
const scroller = useInfiniteScroll(fetchMedias)
watch([reqSortOption, reqDirOption, reqMediaOption, reqQueryDebounced], () => {
watch([reqSort, reqDir, reqMedia, reqYear, reqMonth, reqQueryDebounced], () => {
resetParameters()
resetDirs()
resetSortOptions()
fetchMedias()
})
onMounted(async () => {
await fetchMedias()
})
onMounted(async () => {
const resp = await reqDirectories()
if (isError(resp)) return
for (const dir of resp.result) {
reqDirOptions.value.push({
label: dir.directory_alias,
icon: dir.is_uploads ? FolderAddIcon : FolderIcon,
directory: dir.directory_alias,
})
}
})
</script>
14 changes: 11 additions & 3 deletions frontend/components/SearchFilter.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<template>
<div ref="elm" class="whitespace-nowrap flex bg-white border border-gray-300 divide-x divide-gray-300 rounded">
<div class="padded md:w-auto w-32 text-gray-600 bg-gray-200 rounded-l">{{ props.label }}</div>
<div class="relative" v-if="props.items.length">
<div
ref="elm"
class="whitespace-nowrap flex min-w-0 text-gray-700 bg-white border border-gray-300 divide-x divide-gray-300 rounded"
:class="{ 'pointer-events-none filter contrast-125 text-gray-500': disabled }"
>
<div class="padded w-[6.5rem] bg-gray-200 rounded-l flex-shrink-0 text-right lg:text-left">
{{ props.label }}
</div>
<div class="relative w-full" v-if="props.items.length">
<search-filter-item :label="props.selected.label" :icon="props.selected.icon" @click="toggle" />

<div v-if="isOpen" class="absolute z-10 py-2 ml-[-1px] mt-2 bg-white border border-gray-300 rounded">
<search-filter-item
v-for="(item, idx) in props.items"
Expand Down Expand Up @@ -34,6 +41,7 @@ const props = defineProps<{
label: string
items: Item[]
selected: Item
disabled?: boolean
}>()
const isOpen = ref(false)
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/SearchFilterItem.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="padded flex items-center w-full space-x-3 text-gray-800 cursor-pointer">
<div class="padded flex items-center w-full space-x-3 cursor-pointer">
<component :is="props.icon" class="h-5" />
<span class="select-none">{{ props.label }}</span>
</div>
Expand Down

0 comments on commit c87d94e

Please sign in to comment.