mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-07-22 07:30:18 -04:00
fix: Fix flaky ratings on mobile (#7916)
This commit is contained in:
@@ -1,38 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div @click.prevent>
|
<div @click.prevent>
|
||||||
<!-- User Rating -->
|
<!-- User Rating: shows the group average until the user rates the recipe themselves -->
|
||||||
<v-hover v-slot="{ isHovering, props: hoverProps }">
|
<v-rating
|
||||||
<v-rating
|
v-if="isOwnGroup"
|
||||||
v-if="isOwnGroup && (userRating || isHovering || !ratingsLoaded)"
|
:model-value="displayRating"
|
||||||
v-bind="hoverProps"
|
:active-color="showGroupAverage ? 'grey-darken-1' : 'secondary'"
|
||||||
:model-value="userRating"
|
color="secondary-lighten-3"
|
||||||
active-color="secondary"
|
length="5"
|
||||||
color="secondary-lighten-3"
|
:half-increments="showGroupAverage"
|
||||||
length="5"
|
:density="small ? 'compact' : 'default'"
|
||||||
:density="small ? 'compact' : 'default'"
|
:size="small ? 'x-small' : undefined"
|
||||||
:size="small ? 'x-small' : undefined"
|
:hover="canHover"
|
||||||
hover
|
:clearable="!!userRatingDisplayValue"
|
||||||
clearable
|
@update:model-value="updateRating(+$event)"
|
||||||
@update:model-value="updateRating(+$event)"
|
/>
|
||||||
/>
|
<!-- Group Rating -->
|
||||||
<!-- Group Rating -->
|
<v-rating
|
||||||
<v-rating
|
v-else
|
||||||
v-else
|
:model-value="groupRating"
|
||||||
v-bind="hoverProps"
|
:half-increments="true"
|
||||||
:model-value="groupRating"
|
active-color="grey-darken-1"
|
||||||
:half-increments="true"
|
color="secondary-lighten-3"
|
||||||
active-color="grey-darken-1"
|
length="5"
|
||||||
color="secondary-lighten-3"
|
:density="small ? 'compact' : 'default'"
|
||||||
length="5"
|
:size="small ? 'x-small' : undefined"
|
||||||
:density="small ? 'compact' : 'default'"
|
readonly
|
||||||
:size="small ? 'x-small' : undefined"
|
/>
|
||||||
hover
|
|
||||||
/>
|
|
||||||
</v-hover>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useMediaQuery } from "@vueuse/core";
|
||||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||||
import { useUserSelfRatings } from "~/composables/use-users";
|
import { useUserSelfRatings } from "~/composables/use-users";
|
||||||
|
|
||||||
@@ -51,42 +49,61 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const modelValue = defineModel<number>({ default: 0 });
|
const modelValue = defineModel<number>({ default: 0 });
|
||||||
|
const groupRating = computed(() => modelValue.value);
|
||||||
|
|
||||||
const { isOwnGroup } = useLoggedInState();
|
const { isOwnGroup } = useLoggedInState();
|
||||||
const { userRatings, setRating, ready: ratingsLoaded } = useUserSelfRatings();
|
const { userRatings, setRating } = useUserSelfRatings();
|
||||||
|
|
||||||
const userRating = computed(() => {
|
// on touch devices a tap fires mouseenter without a matching mouseleave, which leaves v-rating
|
||||||
return userRatings.value.find(r => r.recipeId === props.recipeId)?.rating ?? undefined;
|
// rendering the stuck hover value instead of the model value
|
||||||
|
const canHover = useMediaQuery("(hover: hover) and (pointer: fine)");
|
||||||
|
|
||||||
|
const userRating = computed<number | null>(() => {
|
||||||
|
return userRatings.value.find(r => r.recipeId === props.recipeId)?.rating ?? null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// if a user unsets their rating, we don't want to fall back to the group rating since it's out of sync
|
const localUserRating = ref(userRating.value);
|
||||||
const hideGroupRating = ref(!!userRating.value);
|
|
||||||
watch(
|
|
||||||
() => userRating.value,
|
|
||||||
() => {
|
|
||||||
if (userRating.value) {
|
|
||||||
hideGroupRating.value = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const groupRating = computed(() => {
|
// while a write is in flight a refetch may still report the pre-click value, so ignore it
|
||||||
return hideGroupRating.value ? 0 : modelValue.value;
|
const pendingWrites = ref(0);
|
||||||
|
watch(userRating, (value) => {
|
||||||
|
if (!pendingWrites.value) {
|
||||||
|
localUserRating.value = value;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateRating(val?: number) {
|
const userRatingDisplayValue = computed(() => localUserRating.value ?? 0);
|
||||||
|
const showGroupAverage = computed(() => localUserRating.value === null);
|
||||||
|
|
||||||
|
const displayRating = computed(() => {
|
||||||
|
return showGroupAverage.value ? groupRating.value : userRatingDisplayValue.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function updateRating(val?: number) {
|
||||||
if (!isOwnGroup.value) {
|
if (!isOwnGroup.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val === userRating.value) {
|
// the group average can be a half value, but user ratings are always whole stars
|
||||||
val = 0;
|
let rating = Math.round(val ?? 0);
|
||||||
|
if (rating === localUserRating.value) {
|
||||||
|
rating = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.emitOnly) {
|
localUserRating.value = rating;
|
||||||
setRating(props.slug, val || 0, null);
|
|
||||||
|
if (props.emitOnly) {
|
||||||
|
modelValue.value = rating;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingWrites.value++;
|
||||||
|
try {
|
||||||
|
await setRating(props.slug, rating, null);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
pendingWrites.value--;
|
||||||
}
|
}
|
||||||
modelValue.value = val ?? 0;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user