fix: Fix flaky ratings on mobile (#7916)

This commit is contained in:
Michael Genson
2026-07-20 14:39:20 -05:00
committed by GitHub
parent 4979adafa3
commit 2bae99e731

View File

@@ -1,24 +1,22 @@
<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 && (userRating || isHovering || !ratingsLoaded)" v-if="isOwnGroup"
v-bind="hoverProps" :model-value="displayRating"
:model-value="userRating" :active-color="showGroupAverage ? 'grey-darken-1' : 'secondary'"
active-color="secondary"
color="secondary-lighten-3" color="secondary-lighten-3"
length="5" length="5"
:half-increments="showGroupAverage"
:density="small ? 'compact' : 'default'" :density="small ? 'compact' : 'default'"
:size="small ? 'x-small' : undefined" :size="small ? 'x-small' : undefined"
hover :hover="canHover"
clearable :clearable="!!userRatingDisplayValue"
@update:model-value="updateRating(+$event)" @update:model-value="updateRating(+$event)"
/> />
<!-- Group Rating --> <!-- Group Rating -->
<v-rating <v-rating
v-else v-else
v-bind="hoverProps"
:model-value="groupRating" :model-value="groupRating"
:half-increments="true" :half-increments="true"
active-color="grey-darken-1" active-color="grey-darken-1"
@@ -26,13 +24,13 @@
length="5" length="5"
:density="small ? 'compact' : 'default'" :density="small ? 'compact' : 'default'"
:size="small ? 'x-small' : undefined" :size="small ? 'x-small' : undefined"
hover readonly
/> />
</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( // while a write is in flight a refetch may still report the pre-click value, so ignore it
() => userRating.value, const pendingWrites = ref(0);
() => { watch(userRating, (value) => {
if (userRating.value) { if (!pendingWrites.value) {
hideGroupRating.value = true; localUserRating.value = value;
} }
},
);
const groupRating = computed(() => {
return hideGroupRating.value ? 0 : modelValue.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>