mirror of
https://github.com/maciejpedzich/racemash.git
synced 2024-11-27 16:15:47 +01:00
Create and use the useVote composable
This commit is contained in:
parent
1fc0cb70a5
commit
085fcdb63e
122
src/composables/useVote.ts
Normal file
122
src/composables/useVote.ts
Normal 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)
|
||||||
|
};
|
||||||
|
}
|
@ -1,18 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { useVote } from '@/composables/useVote';
|
||||||
|
|
||||||
import { useSnackbar } from '@/composables/useSnackbar';
|
const { photosInCurrentVote } = useVote();
|
||||||
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[]>([]);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -29,22 +18,22 @@ const photosInCurrentVote = ref<Photo[]>([]);
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(photo, index) in photosInCurrentVote"
|
v-for="(photo, index) in photosInCurrentVote"
|
||||||
:key="photo.$id"
|
:key="photo.fileName"
|
||||||
class="d-flex flex-column align-center px-5 py-4"
|
class="d-flex flex-column align-center px-5 py-4"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img
|
||||||
:src="photo.url"
|
:src="`/images/${photo.fileName}`"
|
||||||
:alt="photo.altText"
|
:alt="photo.altText"
|
||||||
max-width="480"
|
|
||||||
:aspect-ratio="16 / 9"
|
:aspect-ratio="16 / 9"
|
||||||
|
max-width="480"
|
||||||
/>
|
/>
|
||||||
<p class="mt-2 text-h6">Photo {{ index + 1 }}</p>
|
<p class="mt-2 text-h6">Photo {{ index + 1 }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="vote-btns" class="mb-4 d-flex justify-center flex-wrap">
|
<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 size="large">Photo 1</v-btn>
|
||||||
<v-btn :disabled="isLoading" size="large">Photo 2</v-btn>
|
<v-btn size="large">Photo 2</v-btn>
|
||||||
<v-btn :disabled="isLoading" size="large">I can't decide</v-btn>
|
<v-btn size="large">I can't decide</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user