This commit is contained in:
j3d1 2026-09-06 05:22:56 +02:00
parent 109f7170c7
commit cb0437c98b
6 changed files with 103 additions and 111 deletions

View file

@ -57,3 +57,14 @@
}
}
}
.img-preview-fixed {
height: 160px;
object-fit: cover;
}
.img-preview-fluid {
width: 100%;
height: auto;
object-fit: cover;
}

View file

@ -0,0 +1,35 @@
// Shared by any view rendering a Pinterest-style image grid (Inventory.vue,
// Search.vue, Files.vue) - keeps their column counts and breakpoints in sync.
.masonry-grid {
column-count: 2;
column-gap: 1rem;
}
.masonry-item {
break-inside: avoid;
margin-bottom: 1rem;
}
@include media-breakpoint-up(md) {
.masonry-grid {
column-count: 3;
}
}
@include media-breakpoint-up(lg) {
.masonry-grid {
column-count: 4;
}
}
@include media-breakpoint-up(xl) {
.masonry-grid {
column-count: 6;
}
}
@include media-breakpoint-up(xxl) {
.masonry-grid {
column-count: 12;
}
}

View file

@ -95,6 +95,7 @@ $body-color: $gray-700;
@import "tags";
@import "dropdown";
@import "label-fonts";
@import "masonry";
#root, body, html {
height: 100%;

View file

@ -12,7 +12,7 @@
<div class="masonry-item" v-for="file in only_images(files)" :key="file.id">
<deletable-wrapper confirm-message="Delete this file? This cannot be undone." @delete="deleteFile(file)">
<authenticated-image :src="thumbnailPathForHash(file.hash)" :owner="file.owner"
class="img-preview-masonry"/>
class="img-preview-fluid"/>
</deletable-wrapper>
</div>
</div>
@ -72,51 +72,9 @@ export default {
}
</script>
<style scoped lang="scss">
@import "../scss/breakpoints";
.img-preview-masonry {
width: 100%;
height: auto;
object-fit: cover;
}
<style scoped>
.masonry-item :deep(span) {
display: block !important;
width: 100%;
}
.masonry-grid {
column-count: 2;
column-gap: 1rem;
}
.masonry-item {
break-inside: avoid;
margin-bottom: 1rem;
}
@include media-breakpoint-up(md) {
.masonry-grid {
column-count: 3;
}
}
@include media-breakpoint-up(lg) {
.masonry-grid {
column-count: 4;
}
}
@include media-breakpoint-up(xl) {
.masonry-grid {
column-count: 6;
}
}
@include media-breakpoint-up(xxl) {
.masonry-grid {
column-count: 12;
}
}
</style>

View file

@ -83,7 +83,7 @@
<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-masonry"/>
class="card-img-top img-preview-fluid"/>
<div class="card-body">
<h5 class="card-title mb-0">
<router-link :to="itemRoute(item)">
@ -127,7 +127,7 @@
<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-grid"/>
class="card-img-top img-preview-fixed"/>
<div class="card-body d-flex flex-column flex-grow-1">
<h5 class="card-title mb-0">
<router-link :to="itemRoute(item)">
@ -299,58 +299,11 @@ export default {
}
</script>
<style scoped lang="scss">
@import "../scss/breakpoints";
.img-preview-grid {
height: 160px;
object-fit: cover;
}
.img-preview-masonry {
width: 100%;
height: auto;
object-fit: cover;
}
<style scoped>
.table-action {
white-space: nowrap;
}
.masonry-grid {
column-count: 2;
column-gap: 1rem;
}
.masonry-item {
break-inside: avoid;
margin-bottom: 1rem;
}
@include media-breakpoint-up(md) {
.masonry-grid {
column-count: 3;
}
}
@include media-breakpoint-up(lg) {
.masonry-grid {
column-count: 4;
}
}
@include media-breakpoint-up(xl) {
.masonry-grid {
column-count: 6;
}
}
@include media-breakpoint-up(xxl) {
.masonry-grid {
column-count: 12;
}
}
.unguarded.btn-danger,
.unguarded.btn-danger:hover,
.unguarded.btn-danger:focus {

View file

@ -6,14 +6,19 @@
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap">
<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 class="btn-group">
<button v-if="layout === 'grid'" @click="layout = 'masonry'" class="btn">
<b-icon-columns-gap></b-icon-columns-gap>
</button>
<button v-else-if="layout === 'masonry'" @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>
</div>
<table class="table table-striped" v-if="layout === 'table'">
<thead>
@ -38,6 +43,29 @@
</tr>
</tbody>
</table>
<div class="card-body" v-else-if="layout === 'masonry'">
<div class="masonry-grid">
<div class="masonry-item" 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-fluid"/>
<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 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"
@ -46,7 +74,7 @@
<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"/>
class="card-img-top img-preview-fixed"/>
<div class="card-body">
<h5 class="card-title mb-0">
<router-link :to="item.route">
@ -71,7 +99,7 @@
</template>
<script>
import {mapActions} from 'vuex';
import {mapActions, mapGetters} from 'vuex';
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
import SearchBox from "@/components/SearchBox.vue";
@ -96,12 +124,23 @@ export default {
},
data() {
return {
search_results: [],
layout: 'table'
search_results: []
}
},
computed: {
...mapGetters(['getPreference']),
// Implicit preference: remembers the last-used view without a settings-page entry. See docs/implementation.md#preferences.
layout: {
get() {
return this.getPreference('ui.remembered.search_view_mode', 'grid')
},
set(value) {
this.setDevicePreference({key: 'ui.remembered.search_view_mode', value})
}
}
},
methods: {
...mapActions(['fetchSearchResults']),
...mapActions(['fetchSearchResults', 'setDevicePreference']),
only_images(files) {
if (!files) return [];
return files.filter(file => file.mime_type.startsWith("image/"));
@ -130,8 +169,3 @@ export default {
}
</script>
<style scoped>
.img-preview {
object-fit: cover;
}
</style>