Skip to content

Commit

Permalink
feat(home page): add search and booru selector
Browse files Browse the repository at this point in the history
  • Loading branch information
AlejandroAkbal committed Jul 18, 2023
1 parent e523987 commit 26767b3
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 29 deletions.
10 changes: 8 additions & 2 deletions components/layout/navigation/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
</script>

<template>
<nav class="border-b border-b-base-0/20 bg-transparent">
<nav
id="navbar"
class="border-b border-b-base-0/20 bg-transparent"
>
<!-- -->

<!-- Container -->
Expand Down Expand Up @@ -38,7 +41,10 @@
</div>

<!-- Center: Logo -->
<div class="flex flex-1 items-center justify-center">
<div
id="navbar-logo"
class="flex flex-1 items-center justify-center"
>
<div class="group flex-shrink-0">
<img
alt="Icon"
Expand Down
156 changes: 156 additions & 0 deletions components/pages/home/SimpleSearch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<script lang="ts" setup>
import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions } from '@headlessui/vue'
import { CheckIcon, ChevronUpDownIcon, MagnifyingGlassIcon } from '@heroicons/vue/20/solid'
import { watchDebounced } from '@vueuse/core'
import { abbreviateNumber } from 'js-abbreviation-number'
import Tag from 'assets/js/tag.dto'
const props = defineProps<{
tagResults: Tag[]
}>()
const emit = defineEmits<{
searchTag: [tag: string]
submit: [payload: string | undefined]
}>()
const selectedTag = ref<Tag | undefined>()
const searchQuery = ref('')
// Change event
function onComboboxInputChange(event: InputEvent) {
let value = (event.target as HTMLInputElement).value
value = value.trim()
// Replace empty spaces with underscores
value = value.replace(/\s/g, '_')
searchQuery.value = value
}
watchDebounced(searchQuery, (value) => onSearchChange(value), { debounce: 350 })
function onSearchChange(tag: string) {
tag = tag.trim()
if (!tag || tag === '') {
return
}
emit('searchTag', tag)
}
const customTagFromQuery = computed(() => {
let tag = searchQuery.value
if (!tag || tag === '') {
return null
}
// If the tag is already in tagResults, return null
if (props.tagResults.some((tagResult) => tagResult.name === tag)) {
return null
}
return {
name: tag,
count: null
}
})
function onSubmitted() {
emit('submit', selectedTag.value?.name)
}
</script>

<template>
<!-- Search & Actions -->
<section>
<!-- Search -->
<Combobox v-model="selectedTag">
<div class="group relative">
<!-- Icon -->
<div class="group pointer-events-none absolute inset-y-0 left-0 flex items-center rounded-l-md px-2">
<MagnifyingGlassIcon class="h-5 w-5 text-base-content group-hover:text-base-content-hover" />
</div>

<!-- Input -->
<ComboboxInput
:displayValue="(tag) => tag?.name"
class="focus-visible:focus-outline-util hover:hover-bg-util hover:hover-text-util w-full rounded-full border-0 bg-base-1000 px-9 py-2 text-base-content-highlight ring-1 ring-inset ring-base-0/20 sm:text-sm"
@change="onComboboxInputChange"
/>

<!-- Button -->
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2">
<ChevronUpDownIcon class="h-5 w-5 text-base-content group-hover:text-base-content-hover" />
</ComboboxButton>

<!-- Options -->
<ComboboxOptions
class="absolute z-10 mt-2 max-h-72 w-full overflow-auto rounded-md bg-base-1000 py-1 text-base ring-1 ring-base-0/20 sm:text-sm"
>
<!-- Custom option -->
<!-- TODO: History based -->
<ComboboxOption
v-if="customTagFromQuery"
v-slot="{ active, selected }"
:value="customTagFromQuery"
>
<div
:class="[active ? 'bg-base-0/20 text-base-content-highlight' : 'text-base-content']"
class="relative cursor-default select-none py-2 pl-8"
>
<span :class="['block truncate', selected && 'font-semibold']">
Create “{{ customTagFromQuery.name }}” tag
</span>

<span
v-if="selected"
class="absolute inset-y-0 left-0 flex items-center pl-1.5 text-base-content-highlight"
>
<CheckIcon class="h-5 w-5" />
</span>
</div>
</ComboboxOption>

<!-- Options -->
<ComboboxOption
v-for="tag in tagResults"
:key="tag.name"
v-slot="{ active, selected }"
:value="tag"
>
<div
:class="[active ? 'bg-base-0/20 text-base-content-highlight' : 'text-base-content']"
class="relative cursor-default select-none py-2 pl-8 pr-12"
>
<!-- Check icon -->
<span
v-if="selected"
class="absolute inset-y-0 left-0 flex items-center pl-1.5 text-base-content-highlight"
>
<CheckIcon class="h-5 w-5" />
</span>

<!-- Tag -->
<span :class="['block truncate', selected && 'font-semibold']">
{{ tag.name }}
</span>

<!-- Tag count -->
<div class="absolute inset-y-0 right-0 flex items-center gap-2 pr-4">
<span v-if="tag.count">
{{ abbreviateNumber(tag.count, 0) }}
</span>
</div>
</div>
</ComboboxOption>
</ComboboxOptions>
</div>
</Combobox>
</section>
</template>
122 changes: 95 additions & 27 deletions pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,73 @@
<script lang="ts" setup>
import { toast } from 'vue-sonner'
import { Ref } from 'vue'
import Tag from 'assets/js/tag.dto'
import { useBooruList } from '~/composables/useBooruList'
const config = useRuntimeConfig()
const { pageHistory } = usePageHistory()
const { booruList } = useBooruList()
const { selectedDomainFromStorage } = useSelectedDomainFromStorage()
const selectedBooru = computed(() => {
let domain = selectedDomainFromStorage.value
// Fallback to first Booru
if (!domain) {
return booruList.value[0]
}
const booru = booruList.value.find((booru) => booru.domain === domain)
if (!booru) {
toast.error(`Booru "${domain}" not found`)
throw new Error(`Booru "${domain}" not found`)
}
// Save selected booru to storage
selectedDomainFromStorage.value = booru.domain
return booru
})
const searchTagResults: Ref<Tag[]> = ref([])
async function onSearchTag(tag: string) {
const apiUrl = config.public.API_URL + '/booru/' + selectedBooru.value.type.type + '/tags'
const response = await $fetch(apiUrl, {
params: {
baseEndpoint: selectedBooru.value.domain,
tag,
order: 'count',
limit: 20
},
onResponseError(context) {
if (context.response.status === 404) {
searchTagResults.value = []
return
}
toast.error(`Failed to load tags: "${context.response.statusText}"`)
}
})
searchTagResults.value = response.data
}
function onSearchSubmit(tag?: string | undefined) {
navigateTo({
path: '/posts',
query: {
tags: tag
}
})
}
definePageMeta({
middleware: [
/**
Expand All @@ -18,22 +85,9 @@
</script>

<template>
<!-- <SafeTeleport to="#navbar-actions">-->
<!-- <NuxtLink-->
<!-- class="focus-visible:focus-outline-util hover:hover-bg-util hover:hover-text-util relative rounded-md p-2"-->
<!-- href="/premium"-->
<!-- >-->
<!-- <span class="sr-only">Premium</span>-->

<!-- <SparklesIcon class="h-6 w-6 text-base-content-highlight" />-->

<!-- &lt;!&ndash; TODO: Add Highlighter, like in posts&ndash;&gt;-->
<!-- </NuxtLink>-->
<!-- </SafeTeleport>-->

<main class="container mx-auto max-w-3xl flex-1 px-4 py-4 sm:px-6 lg:px-8">
<!-- Header -->
<div class="-mt-4 mb-8 text-center">
<div class="-mt-4 text-center">
<h1
class="flex justify-center gap-2 text-2xl font-bold uppercase leading-10 tracking-tight text-base-content-highlight"
>
Expand All @@ -51,19 +105,43 @@
App
</h1>

<p class="mt-4">
<p class="mt-2">
Stream and download rule 34 hentai images, GIFs and videos from multiple Boorus in a mobile-first web app
</p>
</div>

<div class="space-y-4">
<div class="mt-8 space-y-8">
<!-- Search -->
<section>
<PageHeader as="h2">
<template #title>Search</template>
</PageHeader>

<!-- <SimpleSearch />-->
<div class="mt-2 flex items-center gap-2">
<DomainSelector
:boorus="booruList"
:compact="true"
:model-value="selectedBooru"
@update:model-value="onDomainChange"
/>

<SimpleSearch
:tag-results="searchTagResults"
class="flex-auto"
@submit="onSearchSubmit"
@search-tag="onSearchTag"
/>
</div>
</section>

<!-- History -->
<section v-if="pageHistory.length">
<PageHeader as="h2">
<template #title>History</template>
<template #text>Continue where you left off</template>
</PageHeader>

<PageHistory class="mt-4" />
</section>

<!-- Featured tags -->
Expand All @@ -77,16 +155,6 @@

<!-- TODO: Trending tags -->

<!-- History -->
<section v-if="pageHistory.length">
<PageHeader as="h2">
<template #title>History</template>
<template #text>Continue where you left off</template>
</PageHeader>

<PageHistory class="mt-4" />
</section>

<!-- News -->
<section>
<PageHeader as="h2">
Expand Down

0 comments on commit 26767b3

Please sign in to comment.