Create and use the useVote composable

This commit is contained in:
Maciej Pędzich 2023-06-08 18:58:14 +02:00
parent 1fc0cb70a5
commit 085fcdb63e
2 changed files with 130 additions and 19 deletions

122
src/composables/useVote.ts Normal file
View File

@ -0,0 +1,122 @@
import { computed, reactive, ref, toRefs } from 'vue';
import glicko2 from 'glicko2-lite';
import photos from '@/photos.json';
import { Database, Photo } from '@/models';
import { randomNumber } from '@/utils/randomNumber';
const db = reactive<Database>(
JSON.parse(localStorage.getItem('db') as string) || {
photos,
votes: []
}
);
const photosInCurrentVote = ref<Photo[]>([]);
const photosForFirstPick = computed(() =>
db.photos.filter(({ fileName }) => {
const appearanceCount = db.votes.filter((vote) =>
vote.photos.includes(fileName)
).length;
return appearanceCount !== db.photos.length - 1;
})
);
export function useVote() {
const pickPhotosForNewVote = () => {
const firstPick =
photosForFirstPick.value[
randomNumber(0, photosForFirstPick.value.length - 1)
];
const photosForSecondPick = db.photos.filter(({ fileName }) => {
const votesWithFirstPick = db.votes.filter(({ photos }) =>
photos.includes(firstPick.fileName)
);
const fileNamesToExclude = [
...new Set(votesWithFirstPick.flatMap(({ photos }) => photos))
];
return !fileNamesToExclude.includes(fileName);
});
const secondPick =
photosForSecondPick[randomNumber(0, photosForSecondPick.length - 1)];
photosInCurrentVote.value.push(firstPick, secondPick);
};
const submitVote = (result: 0 | 0.5 | 1) => {
const fileNames = [...photosInCurrentVote.value].map(
({ fileName }) => fileName
) as [string, string];
db.votes.unshift({ photos: fileNames, result });
photosInCurrentVote.value = [];
pickPhotosForNewVote();
};
const updateRatings = () => {
const twelveMostRecentVotes = db.votes.slice(0, 12).reverse();
const votesGrouppedByPhotos = twelveMostRecentVotes.reduce((obj, vote) => {
const [firstPick, secondPick] = vote.photos;
const firstPhoto = db.photos.find(
({ fileName }) => fileName === firstPick
)!;
const secondPhoto = db.photos.find(
({ fileName }) => fileName === secondPick
)!;
const firstPhotoOpponentParams = [
secondPhoto.rating,
secondPhoto.rd,
vote.result
] as [number, number, number];
const secondPhotoOpponentParams = [
firstPhoto.rating,
firstPhoto.rd,
1 - vote.result
] as [number, number, number];
if (obj[firstPick]) {
obj[firstPick].push(firstPhotoOpponentParams);
} else {
obj[firstPick] = [firstPhotoOpponentParams];
}
if (obj[secondPick]) {
obj[secondPick].push(secondPhotoOpponentParams);
} else {
obj[secondPick] = [secondPhotoOpponentParams];
}
return obj;
}, {} as Record<string, [number, number, number][]>);
for (const [photoFileName, votes] of Object.entries(
votesGrouppedByPhotos
)) {
const photo = db.photos.find(
({ fileName }) => fileName === photoFileName
)!;
const newRatingParams = glicko2(photo.rating, photo.rd, photo.vol, votes);
Object.assign(photo, newRatingParams);
}
};
return {
photosInCurrentVote,
photosForFirstPick,
createVote: pickPhotosForNewVote,
submitVote,
updateRatings,
...toRefs(db)
};
}

View File

@ -1,18 +1,7 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { useVote } from '@/composables/useVote';
import { useSnackbar } from '@/composables/useSnackbar';
import { useRandomNumber } from '@/composables/useRandomNumber';
import { Vote } from '@/models/vote';
import { Photo } from '@/models/photo';
import { PhotoAppearanceCount } from '@/models/photoAppearanceCount';
const { showSnackbar } = useSnackbar();
const randomNumber = useRandomNumber();
const isLoading = ref(true);
const photosInCurrentVote = ref<Photo[]>([]);
const { photosInCurrentVote } = useVote();
</script>
<template>
@ -29,22 +18,22 @@ const photosInCurrentVote = ref<Photo[]>([]);
>
<div
v-for="(photo, index) in photosInCurrentVote"
:key="photo.$id"
:key="photo.fileName"
class="d-flex flex-column align-center px-5 py-4"
>
<v-img
:src="photo.url"
:src="`/images/${photo.fileName}`"
:alt="photo.altText"
max-width="480"
:aspect-ratio="16 / 9"
max-width="480"
/>
<p class="mt-2 text-h6">Photo {{ index + 1 }}</p>
</div>
</div>
<div id="vote-btns" class="mb-4 d-flex justify-center flex-wrap">
<v-btn :disabled="isLoading" size="large">Photo 1</v-btn>
<v-btn :disabled="isLoading" size="large">Photo 2</v-btn>
<v-btn :disabled="isLoading" size="large">I can't decide</v-btn>
<v-btn size="large">Photo 1</v-btn>
<v-btn size="large">Photo 2</v-btn>
<v-btn size="large">I can't decide</v-btn>
</div>
</section>
</template>