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

feat: migrated search dialog to full view #1393

Merged
merged 5 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
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
46 changes: 46 additions & 0 deletions frontend/src/components/Gallery/AppBar/Search/Base.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import FilterBtn from "@/components/Gallery/AppBar/common/FilterBtn.vue";
import FilterDrawer from "@/components/Gallery/AppBar/common/FilterDrawer/Base.vue";
import GalleryViewBtn from "@/components/Gallery/AppBar/common/GalleryViewBtn.vue";
import SearchTextField from "@/components/Gallery/AppBar/Search/SearchTextField.vue";
import PlatformSelector from "@/components/Gallery/AppBar/Search/PlatformSelector.vue";
import SelectingBtn from "@/components/Gallery/AppBar/common/SelectingBtn.vue";
import SearchBtn from "@/components/Gallery/AppBar/Search/SearchBtn.vue";
import { useDisplay } from "vuetify";
import { useI18n } from "vue-i18n";

// Props
const { t } = useI18n();
const { xs } = useDisplay();
</script>

<template>
<v-app-bar
id="gallery-app-bar"
elevation="0"
density="compact"
mode="shift"
app
fixed
top
>
<filter-btn />
<template v-if="!xs">
<search-text-field />
<platform-selector />
</template>
<template #append>
<search-btn v-if="!xs" />
<selecting-btn />
<gallery-view-btn />
</template>
</v-app-bar>

<filter-drawer />
</template>

<style scoped>
#gallery-app-bar {
z-index: 999 !important;
}
</style>
130 changes: 130 additions & 0 deletions frontend/src/components/Gallery/AppBar/Search/PlatformSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<script setup lang="ts">
import PlatformIcon from "@/components/common/Platform/Icon.vue";
import type { Platform } from "@/stores/platforms";
import romApi from "@/services/api/rom";
import storeRoms from "@/stores/roms";
import storeGalleryFilter from "@/stores/galleryFilter";
import type { Events } from "@/types/emitter";
import { inject, ref } from "vue";
import { useDisplay } from "vuetify";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { useI18n } from "vue-i18n";

// Props
const { xs } = useDisplay();
const { t } = useI18n();
const romsStore = storeRoms();
const emitter = inject<Emitter<Events>>("emitter");
const searching = ref(false);
const searched = ref(false);
const selectedPlatform = ref<Platform | null>(null);
const galleryFilterStore = storeGalleryFilter();
const { filterPlatforms } = storeToRefs(galleryFilterStore);

// Functions
function setFilters() {
galleryFilterStore.setFilterGenres([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.genres.map((genre) => genre))
.sort(),
),
]);
galleryFilterStore.setFilterFranchises([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.franchises.map((franchise) => franchise))
.sort(),
),
]);
galleryFilterStore.setFilterCompanies([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.companies.map((company) => company))
.sort(),
),
]);
galleryFilterStore.setFilterCollections([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.collections.map((collection) => collection))
.sort(),
),
]);
galleryFilterStore.setFilterAgeRatings([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.age_ratings.map((ageRating) => ageRating))
.sort(),
),
]);
}

function filterRoms() {
if (selectedPlatform.value) {
romsStore.setFiltered(
romsStore.allRoms.filter(
(rom) => rom.platform_id === selectedPlatform.value?.id,
),
galleryFilterStore,
);
} else {
romsStore.setFiltered(romsStore.allRoms, galleryFilterStore);
}
setFilters();
}

function clearFilter() {
selectedPlatform.value = null;
filterRoms();
}
</script>

<template>
<v-select
:density="xs ? 'comfortable' : 'default'"
@click:clear="clearFilter"
:label="t('common.platform')"
class="bg-terciary"
item-title="platform_name"
:disabled="filterPlatforms.length == 0 || searching"
hide-details
rounded="0"
clearable
single-line
return-object
v-model="selectedPlatform"
@update:model-value="filterRoms"
:items="filterPlatforms"
>
<template #item="{ props, item }">
<v-list-item
class="py-2"
v-bind="props"
:title="item.raw.display_name ?? ''"
>
<template #prepend>
<platform-icon
:size="35"
:key="item.raw.slug"
:slug="item.raw.slug"
:name="item.raw.display_name"
/>
</template>
</v-list-item>
</template>
<template #selection="{ item }">
<v-list-item class="px-0" :title="item.raw.display_name ?? ''">
<template #prepend>
<platform-icon
:size="35"
:key="item.raw.slug"
:slug="item.raw.slug"
:name="item.raw.display_name"
/>
</template>
</v-list-item>
</template>
</v-select>
</template>
85 changes: 85 additions & 0 deletions frontend/src/components/Gallery/AppBar/Search/SearchBtn.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script setup lang="ts">
import { ref, inject } from "vue";
import { useI18n } from "vue-i18n";
import storeRoms from "@/stores/roms";
import romApi from "@/services/api/rom";
import storeGalleryFilter from "@/stores/galleryFilter";
import { storeToRefs } from "pinia";
import type { Platform } from "@/stores/platforms";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";

const romsStore = storeRoms();
const { t } = useI18n();
const searching = ref(false);
const searched = ref(false);
const searchedRoms = ref<Platform[]>([]);
const selectedPlatform = ref<Platform | null>(null);
const emitter = inject<Emitter<Events>>("emitter");
const galleryFilterStore = storeGalleryFilter();
const { searchText, filterPlatforms } = storeToRefs(galleryFilterStore);

async function fetchRoms() {
if (searchText.value) {
const inputElement = document.getElementById("search-text-field");
inputElement?.blur();
searching.value = true;
searched.value = true;
await romApi
.getRoms({ searchTerm: searchText.value })
.then(({ data }) => {
data = data.sort((a, b) => {
return a.platform_name.localeCompare(b.platform_name);
});
romsStore.set(data);
romsStore.setFiltered(data, galleryFilterStore);
})
.catch((error) => {
emitter?.emit("snackbarShow", {
msg: `Couldn't fetch roms: ${error}`,
icon: "mdi-close-circle",
color: "red",
timeout: 4000,
});
console.error(`Couldn't fetch roms: ${error}`);
})
.finally(() => {
searching.value = false;
});
searching.value = false;
filterRoms();
galleryFilterStore.activeFilterDrawer = false;
}
}

function clearFilter() {
selectedPlatform.value = null;
fetchRoms();
}

function filterRoms() {
if (selectedPlatform.value) {
romsStore.setFiltered(
romsStore.allRoms.filter(
(rom) => rom.platform_id === selectedPlatform.value?.id,
),
galleryFilterStore,
);
} else {
romsStore.setFiltered(romsStore.allRoms, galleryFilterStore);
}
}
</script>

<template>
<v-btn
id="search-btn"
type="submit"
@click="fetchRoms"
class="bg-terciary"
rounded="0"
variant="text"
icon="mdi-magnify"
:disabled="searching || !searchText"
/>
</template>
121 changes: 121 additions & 0 deletions frontend/src/components/Gallery/AppBar/Search/SearchTextField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script setup lang="ts">
import romApi from "@/services/api/rom";
import storeRoms from "@/stores/roms";
import storeGalleryFilter from "@/stores/galleryFilter";
import storePlatforms from "@/stores/platforms";
import type { Events } from "@/types/emitter";
import type { Emitter } from "mitt";
import { inject, ref } from "vue";
import { storeToRefs } from "pinia";
import { useDisplay } from "vuetify";
import type { Platform } from "@/stores/platforms";
import { useI18n } from "vue-i18n";

// Props
const { xs } = useDisplay();
const { t } = useI18n();
const romsStore = storeRoms();
const emitter = inject<Emitter<Events>>("emitter");
const searching = ref(false);
const platformsStore = storePlatforms();
const { allPlatforms } = storeToRefs(platformsStore);
const galleryFilterStore = storeGalleryFilter();
const { searchText, filterPlatforms } = storeToRefs(galleryFilterStore);

// Functions
function setFilters() {
galleryFilterStore.setFilterGenres([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.genres.map((genre) => genre))
.sort(),
),
]);
galleryFilterStore.setFilterFranchises([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.franchises.map((franchise) => franchise))
.sort(),
),
]);
galleryFilterStore.setFilterCompanies([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.companies.map((company) => company))
.sort(),
),
]);
galleryFilterStore.setFilterCollections([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.collections.map((collection) => collection))
.sort(),
),
]);
galleryFilterStore.setFilterAgeRatings([
...new Set(
romsStore.filteredRoms
.flatMap((rom) => rom.age_ratings.map((ageRating) => ageRating))
.sort(),
),
]);
}

async function fetchRoms() {
if (searchText.value) {
// Auto hide android keyboard
const inputElement = document.getElementById("search-text-field");
inputElement?.blur();
searching.value = true;
await romApi
.getRoms({ searchTerm: searchText.value })
.then(({ data }) => {
data = data.sort((a, b) => {
return a.platform_name.localeCompare(b.platform_name);
});
romsStore.set(data);
romsStore.setFiltered(data, galleryFilterStore);
})
.catch((error) => {
emitter?.emit("snackbarShow", {
msg: `Couldn't fetch roms: ${error}`,
icon: "mdi-close-circle",
color: "red",
timeout: 4000,
});
console.error(`Couldn't fetch roms: ${error}`);
})
.finally(() => {
searching.value = false;
});
galleryFilterStore.setFilterPlatforms([
...new Map(
romsStore.filteredRoms.map((rom) => {
const platform = allPlatforms.value.find(
(p) => p.id === rom.platform_id,
);
return [rom.platform_name, platform];
}),
).values(),
] as Platform[]);
setFilters();
searching.value = false;
galleryFilterStore.activeFilterDrawer = false;
}
}
</script>

<template>
<v-text-field
rounded="0"
:density="xs ? 'comfortable' : 'default'"
clearable
autofocus
@keyup.enter="fetchRoms"
v-model="searchText"
:disabled="searching"
:label="t('common.search')"
hide-details
class="bg-terciary"
/>
</template>
Loading
Loading