toolshed/frontend/src/views/Search.vue
2026-08-23 15:55:08 +02:00

137 lines
No EOL
5.7 KiB
Vue

<template>
<BaseLayout hide-search>
<main class="content">
<div class="container-fluid p-0">
<h1 class="h3 mb-3">Search Inventories</h1>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Results for "{{ query }}"</h5>
<button v-if="layout === 'grid'" @click="layout = 'table'" class="btn">
<b-icon-list></b-icon-list>
</button>
<button v-else @click="layout = 'grid'" class="btn">
<b-icon-grid></b-icon-grid>
</button>
</div>
<table class="table table-striped" v-if="layout === 'table'">
<thead>
<tr>
<th style="width:40%;">Name</th>
<th style="width:25%">Owner</th>
<th class="d-none d-md-table-cell" style="width:25%">Amount</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="item in search_results" :key="item.id">
<td>
<router-link :to="item.route">{{ item.name }}</router-link>
</td>
<td>
<user-name-tag :user="item"/>
</td>
<td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
<td class="table-action">
</td>
</tr>
</tbody>
</table>
<div class="card-body" v-else>
<div class="row">
<div class="col-12 col-md-4 col-lg-3 col-xl-2" v-for="item in search_results"
:key="item.id">
<div class="card">
<authenticated-image v-if="only_images(item.files).length > 0"
:src="only_images(item.files)[0].name"
:owner="only_images(item.files)[0].owner"
class="card-img-top img-preview"/>
<div class="card-body">
<h5 class="card-title mb-0">
<router-link :to="item.route">
{{ item.name }}
</router-link></h5>
<div class="card-text text-black-50">
<user-name-tag :user="item" :width="24" :height="24"/>
<br>
<span class="float-right">{{ item.owned_quantity }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</BaseLayout>
</template>
<script>
import {mapActions} from 'vuex';
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
import SearchBox from "@/components/SearchBox.vue";
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
import UserNameTag from "@/components/UserNameTag.vue";
import {encodeHandleForUrl} from "@/router";
export default {
name: 'Search',
components: {
SearchBox,
AuthenticatedImage,
UserNameTag,
...BIcons,
BaseLayout
},
props: {
query: {
type: String,
required: true
}
},
data() {
return {
search_results: [],
layout: 'table'
}
},
methods: {
...mapActions(['fetchSearchResults']),
only_images(files) {
if (!files) return [];
return files.filter(file => file.mime_type.startsWith("image/"));
},
loadResults() {
// Search results are always personal-or-friend (see inventory_items() in
// toolshed/api/inventory.py - it never yields a group's items), so the owner is
// always a plain user handle - one route shape, no owner-is-me special case.
this.fetchSearchResults({query: this.query}).then((results) => {
this.search_results = results.map(e => (
{...e, route: `/inventory/${encodeHandleForUrl(e.owner)}/${e.id}`}))
});
}
},
watch: {
query: {
immediate: false,
handler() {
this.loadResults();
}
}
},
mounted() {
this.loadResults();
}
}
</script>
<style scoped>
.img-preview {
object-fit: cover;
}
</style>