Added stash sorting to stash tooltip menu and profile page.

This commit is contained in:
2026-07-26 00:26:49 +02:00
parent d3f549c92f
commit 07d9dfaaaa
8 changed files with 281 additions and 16 deletions

View File

@@ -87,7 +87,7 @@
} }
.v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-container { .v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-container {
bottom: -7px; bottom: -6px;
} }
.v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-inner, .v-popper__popper[data-popper-placement^="top"] .v-popper__arrow-inner,

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="16"
height="16"
viewBox="0 0 16 16"
id="svg3"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs3" /><path
d="M5 12v-12h-2v12h-2.5l3.5 3.5 3.5-3.5h-2.5z"
id="path1" /><path
id="path1-3"
style="stroke-width:0.348033"
d="M 10.443359 2.2773438 C 10.354073 2.2861032 10.283203 2.3587981 10.283203 2.4472656 L 10.283203 4.328125 L 8.3339844 4.328125 C 8.2362054 4.328125 8.1582031 4.4056346 8.1582031 4.5 L 8.1582031 5.5253906 C 8.1582031 5.6197556 8.2362047 5.6953125 8.3339844 5.6953125 L 10.283203 5.6953125 L 10.283203 7.5292969 L 11.556641 6.46875 A 0.14617623 0.14617623 0 0 0 11.556641 6.4667969 C 11.601003 6.4299705 11.650097 6.3991617 11.701172 6.375 L 11.701172 5.6953125 L 13.648438 5.6953125 C 13.746218 5.6953125 13.826172 5.619755 13.826172 5.5253906 L 13.826172 4.5 C 13.826172 4.405635 13.746225 4.328125 13.648438 4.328125 L 11.701172 4.328125 L 11.701172 2.4472656 C 11.701172 2.3529006 11.621224 2.2773437 11.523438 2.2773438 L 10.460938 2.2773438 C 10.454826 2.2773438 10.449312 2.2767598 10.443359 2.2773438 z " /><path
d="M 16,5 C 16,2.243 13.757,0 11,0 8.243,0 6,2.243 6,5 V 5.5 C 6,5.776 6.224,6 6.5,6 6.776,6 7,5.776 7,5.5 V 5 c 0,-2.206 1.794,-4 4,-4 2.206,0 4,1.794 4,4 0,1.86 -1.277,3.428 -3,3.873 V 7 L 9,9.5 12,12 V 9.899 C 14.279,9.434 16,7.414 16,5 Z"
id="path3" /></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="16"
height="16"
viewBox="0 0 16 16"
id="svg3"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs3" /><path
d="M5 12v-12h-2v12h-2.5l3.5 3.5 3.5-3.5h-2.5z"
id="path1" /><path
id="path1-3"
style="stroke-width:0.348033"
d="M 10.443359 2.2773438 C 10.354073 2.2861032 10.283203 2.3587981 10.283203 2.4472656 L 10.283203 4.328125 L 8.3339844 4.328125 C 8.2362054 4.328125 8.1582031 4.4056346 8.1582031 4.5 L 8.1582031 5.5253906 C 8.1582031 5.6197556 8.2362047 5.6953125 8.3339844 5.6953125 L 10.283203 5.6953125 L 10.283203 6.3671875 C 10.340222 6.3923929 10.394355 6.4261166 10.443359 6.4667969 A 0.14617623 0.14617623 0 0 1 10.443359 6.46875 L 11.701172 7.5175781 L 11.701172 5.6953125 L 13.648438 5.6953125 C 13.746218 5.6953125 13.826172 5.619755 13.826172 5.5253906 L 13.826172 4.5 C 13.826172 4.405635 13.746225 4.328125 13.648438 4.328125 L 11.701172 4.328125 L 11.701172 2.4472656 C 11.701172 2.3529006 11.621224 2.2773437 11.523438 2.2773438 L 10.460938 2.2773438 C 10.454826 2.2773438 10.449312 2.2767598 10.443359 2.2773438 z " /><path
d="m 6,5 c 0,-2.757 2.243,-5 5,-5 2.757,0 5,2.243 5,5 V 5.5 C 16,5.776 15.776,6 15.5,6 15.224,6 15,5.776 15,5.5 V 5 C 15,2.794 13.206,1 11,1 8.794,1 7,2.794 7,5 7,6.86 8.277,8.428 10,8.873 V 7 L 13,9.5 10,12 V 9.899 C 7.721,9.434 6,7.414 6,5 Z"
id="path3" /></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="16"
height="16"
viewBox="0 0 16 16"
id="svg3"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs3" /><path
d="M5 12v-12h-2v12h-2.5l3.5 3.5 3.5-3.5h-2.5z"
id="path1" /><path
d="M 14.334849,4.1517112 H 11.883382 V 1.785868 c 0,-0.1187227 -0.09983,-0.2150766 -0.222859,-0.2150766 h -1.337165 c -0.12302,0 -0.222861,0.096353 -0.222861,0.2150766 V 4.1517112 H 7.6490291 c -0.1230181,0 -0.2228598,0.096353 -0.2228598,0.2150768 v 1.2904597 c 0,0.1187229 0.099841,0.2150767 0.2228598,0.2150767 h 2.4514679 v 2.365843 c 0,0.1187227 0.09983,0.2150768 0.222861,0.2150768 h 1.337165 c 0.123019,0 0.222859,-0.096353 0.222859,-0.2150768 v -2.365843 h 2.451467 c 0.12302,0 0.22286,-0.096355 0.22286,-0.2150767 V 4.366788 c 0,-0.1187229 -0.09983,-0.2150768 -0.22286,-0.2150768 z"
id="path1-3"
style="stroke-width:0.437868" /></svg>

After

Width:  |  Height:  |  Size: 1010 B

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="16"
height="16"
viewBox="0 0 16 16"
id="svg3"
sodipodi:docname="sort-heart.svg"
xml:space="preserve"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs3" /><sodipodi:namedview
id="namedview3"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="26.077075"
inkscape:cx="4.0265253"
inkscape:cy="4.640091"
inkscape:window-width="1920"
inkscape:window-height="1020"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="svg3"
showguides="true" /><path
d="M5 12v-12h-2v12h-2.5l3.5 3.5 3.5-3.5h-2.5z"
id="path1" /><path
d="M 13.78723,0 C 12.941692,0 12.207573,0.47431375 11.834786,1.1709458 11.461999,0.47431375 10.72788,0 9.8823418,0 c -1.2219697,0 -2.21277,0.99080032 -2.21277,2.2127699 0,2.9937477 4.1652142,5.0763547 4.1652142,5.0763547 0,0 4.165214,-2.082607 4.165214,-5.0763547 C 16,0.99080032 15.0092,0 13.78723,0 Z"
id="path1-5"
style="stroke-width:0.520651" /></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -261,6 +261,7 @@ async function reloadStashes(newStash) {
<StashMenu <StashMenu
:stashes="stashes" :stashes="stashes"
:item-stashes="itemStashes" :item-stashes="itemStashes"
:domain="domain"
@stash="(stash) => stashItem(stash)" @stash="(stash) => stashItem(stash)"
@unstash="(stash) => unstashItem(stash)" @unstash="(stash) => unstashItem(stash)"
@create="showStashDialog = true" @create="showStashDialog = true"
@@ -318,6 +319,7 @@ async function reloadStashes(newStash) {
<StashMenu <StashMenu
:stashes="stashes" :stashes="stashes"
:item-stashes="itemStashes" :item-stashes="itemStashes"
:domain="domain"
@stash="(stash) => stashItem(stash)" @stash="(stash) => stashItem(stash)"
@unstash="(stash) => unstashItem(stash)" @unstash="(stash) => unstashItem(stash)"
@create="showStashDialog = true" @create="showStashDialog = true"

View File

@@ -1,7 +1,9 @@
<script setup> <script setup>
import { computed, ref } from 'vue';
import Checkbox from '#/components/form/checkbox.vue'; import Checkbox from '#/components/form/checkbox.vue';
defineProps({ const props = defineProps({
stashes: { stashes: {
type: Array, type: Array,
default: () => [], default: () => [],
@@ -10,16 +12,63 @@ defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
domain: {
type: String,
default: 'scenes',
},
}); });
const emit = defineEmits(['stash', 'unstash', 'create']); const emit = defineEmits(['stash', 'unstash', 'create']);
const sortings = ['used', 'name', 'size', 'created'];
const sorting = ref(localStorage.getItem('stashMenuSorting') || 'used');
const domainKey = {
actors: 'stashedActors',
scenes: 'stashedScenes',
movies: 'stashedMovies',
}[props.domain];
const sortedStashes = computed(() => props.stashes
.toSorted((stashA, stashB) => {
return stashA.createdAt - stashB.createdAt;
})
.toSorted((stashA, stashB) => {
if (sorting.value === 'used') {
return stashB.lastStashAt - stashA.lastStashAt;
}
if (sorting.value === 'name') {
return stashA.name.localeCompare(stashB.name);
}
if (sorting.value === 'size') {
return stashB[domainKey] - stashA[domainKey];
}
if (sorting.value === 'created') {
return stashA.createdAt - stashB.createdAt;
}
return 0;
}));
/*
.toSorted((stashA, stashB) => {
return stashB.isPrimary - stashA.isPrimary;
}));
*/
function toggleSorting() {
sorting.value = sortings[(sortings.indexOf(sorting.value) + 1) % sortings.length];
localStorage.setItem('stashMenuSorting', sorting.value);
}
</script> </script>
<template> <template>
<div class="stash-menu noselect"> <div class="stash-menu noselect">
<ul class="stash-list nolist noselect"> <ul class="stash-list nolist noselect">
<li <li
v-for="userStash in stashes" v-for="userStash in sortedStashes"
:key="`stash-${userStash.id}`" :key="`stash-${userStash.id}`"
class="menu-item" class="menu-item"
> >
@@ -32,14 +81,48 @@ const emit = defineEmits(['stash', 'unstash', 'create']);
</li> </li>
</ul> </ul>
<div class="menu-actions">
<div <div
v-close-popper v-close-popper
class="menu-item menu-stash create" class="menu-item menu-stash menu-create"
@click="emit('create')" @click="emit('create')"
> >
<Icon icon="plus3" /> <Icon icon="plus3" />
New New
</div> </div>
<Icon
v-if="sorting === 'used'"
v-tooltip="'By last stash'"
icon="sort-heart"
class="menu-sort"
@click="toggleSorting"
/>
<Icon
v-if="sorting === 'name'"
v-tooltip="'By name'"
icon="sort-alpha-asc"
class="menu-sort"
@click="toggleSorting"
/>
<Icon
v-if="sorting === 'size'"
v-tooltip="`By total stashed ${domain}`"
icon="sort-amount-desc"
class="menu-sort"
@click="toggleSorting"
/>
<Icon
v-if="sorting === 'created'"
v-tooltip="'By stash age'"
icon="sort-time-asc"
class="menu-sort"
@click="toggleSorting"
/>
</div>
</div> </div>
</template> </template>
@@ -85,10 +168,22 @@ const emit = defineEmits(['stash', 'unstash', 'create']);
.check-container { .check-container {
margin-right: .5rem; margin-right: .5rem;
} }
}
&.create { .menu-actions {
flex-shrink: 0; flex-shrink: 0;
border-top: 1px solid var(--glass-weak-40); display: flex;
border-top: solid 1px var(--glass-weak-40);
}
.menu-sort {
padding: .75rem 1rem;
fill: var(--glass);
border-left: solid 1px var(--glass-weak-40);
&:hover {
fill: var(--primary);
cursor: pointer;
} }
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
<script setup> <script setup>
import { inject, ref } from 'vue'; import { computed, inject, ref } from 'vue';
import StashDialog from '#/components/stashes/create.vue'; import StashDialog from '#/components/stashes/create.vue';
import StashTile from '#/components/stashes/tile.vue'; import StashTile from '#/components/stashes/tile.vue';
@@ -13,16 +13,51 @@ const stashes = ref(pageContext.pageProps.stashes);
const profile = ref(pageContext.pageProps.profile); const profile = ref(pageContext.pageProps.profile);
const showStashDialog = ref(false); const showStashDialog = ref(false);
const sortings = ['used', 'name', 'size', 'created'];
const sorting = ref('size');
async function reloadStashes() { async function reloadStashes() {
// profile.value = await get(`/users/${profile.value.id}`); // profile.value = await get(`/users/${profile.value.id}`);
stashes.value = await get(`/users/${profile.value.id}/stashes`); stashes.value = await get(`/users/${profile.value.id}/stashes`);
} }
const sortedStashes = computed(() => stashes.value
.toSorted((stashA, stashB) => {
return stashA.createdAt - stashB.createdAt;
})
.toSorted((stashA, stashB) => {
if (sorting.value === 'used') {
return stashB.lastStashAt - stashA.lastStashAt;
}
if (sorting.value === 'name') {
return stashA.name.localeCompare(stashB.name);
}
if (sorting.value === 'size') {
return (stashB.stashedScenes + stashB.stashedMovies + stashB.stashedActors)
- (stashA.stashedScenes + stashA.stashedMovies + stashA.stashedActors);
}
if (sorting.value === 'created') {
return stashA.createdAt - stashB.createdAt;
}
return 0;
})
.toSorted((stashA, stashB) => {
// always put favorites first
return stashB.isPrimary - stashA.isPrimary;
}));
function toggleSorting() {
sorting.value = sortings[(sortings.indexOf(sorting.value) + 1) % sortings.length];
}
</script> </script>
<template> <template>
<section class="profile-section"> <section class="profile-section">
<div class="section-header"> <div class="section-header noselect">
<button <button
v-if="profile.id === user?.id" v-if="profile.id === user?.id"
class="button" class="button"
@@ -31,6 +66,38 @@ async function reloadStashes() {
<Icon icon="folder-plus2" /> <Icon icon="folder-plus2" />
<span class="button-label">New stash</span> <span class="button-label">New stash</span>
</button> </button>
<Icon
v-if="sorting === 'used'"
v-tooltip="'By last stash'"
icon="sort-heart"
class="sort"
@click="toggleSorting"
/>
<Icon
v-if="sorting === 'name'"
v-tooltip="'By name'"
icon="sort-alpha-asc"
class="sort"
@click="toggleSorting"
/>
<Icon
v-if="sorting === 'size'"
v-tooltip="'By total stashed'"
icon="sort-amount-desc"
class="sort"
@click="toggleSorting"
/>
<Icon
v-if="sorting === 'created'"
v-tooltip="'By stash age'"
icon="sort-time-asc"
class="sort"
@click="toggleSorting"
/>
</div> </div>
<StashDialog <StashDialog
@@ -41,7 +108,7 @@ async function reloadStashes() {
<ul class="stashes nolist"> <ul class="stashes nolist">
<li <li
v-for="stash in stashes" v-for="stash in sortedStashes"
:key="`stash-${stash.id}`" :key="`stash-${stash.id}`"
> >
<StashTile <StashTile
@@ -59,6 +126,7 @@ async function reloadStashes() {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-bottom: .5rem; margin-bottom: .5rem;
gap: 1rem;
} }
.stashes { .stashes {
@@ -68,6 +136,16 @@ async function reloadStashes() {
padding: 0 0 1rem 0; padding: 0 0 1rem 0;
} }
.sort {
padding: .5rem 1rem;
fill: var(--glass);
&:hover {
fill: var(--primary);
cursor: pointer;
}
}
@media(--compact) { @media(--compact) {
.stashes { .stashes {
padding: 0 1rem 1rem 1rem; padding: 0 1rem 1rem 1rem;