Bug and animations fixes

This commit is contained in:
Simon Cambier 2022-02-22 21:56:18 +01:00
parent cc86729dbc
commit d31e17e3be
13 changed files with 114 additions and 80 deletions

View File

@ -21,7 +21,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { darkMode, isDarkModeDefault } from '@/composables/settings' import { darkMode } from '@/composables/settings'
import AppHeader from './components/AppHeader.vue' import AppHeader from './components/AppHeader.vue'
import SideMenu from './components/SideMenu.vue' import SideMenu from './components/SideMenu.vue'
@ -29,6 +29,6 @@ import SideMenu from './components/SideMenu.vue'
onMounted(() => { onMounted(() => {
darkMode.value = localStorage.getItem('n0_dark') darkMode.value = localStorage.getItem('n0_dark')
? localStorage.getItem('n0_dark') === 'true' ? localStorage.getItem('n0_dark') === 'true'
: isDarkModeDefault() : true
}) })
</script> </script>

View File

@ -141,7 +141,7 @@ export function isSolvable(result: number, plaquettes: number[]): boolean {
} }
if (histories.length) { if (histories.length) {
// printHistory(histories[0]) printHistory(histories[0])
} }
return found return found

View File

@ -12,7 +12,7 @@
</h1> </h1>
<button @click="isSideMenuVisible = true"> <button @click="isSideMenuVisible = true">
<IconMenu <IconMenu
class="text-xl hover:text-cyan-500 dark:text-stone-400 transition-opacity" /> class="text-xl btn" />
</button> </button>
</div> </div>
</template> </template>

View File

@ -2,63 +2,61 @@
<div class="relative font-mono text-xl text-center"> <div class="relative font-mono text-xl text-center">
<TransitionGroup name="slide_up"> <TransitionGroup name="slide_up">
<div <div
class="" class="inline-block"
v-for="(op, i) in operations" v-for="(op, i) in operations"
:style="{ :style="{
transitionDelay: `${transDelay * (operations.length - i)}ms`, transitionDelay: `${transDelay * (operations.length - i)}ms`,
}" }"
:key="i"> :key="i">
<div class="inline-block"> <!-- OPERATION LINE -->
<!-- OPERATION LINE --> <div
<div class="flex items-center pb-1 border-b border-stone-600 border-dashed md:py-2"
class="flex items-center pb-1 border-b border-stone-600 border-dashed md:py-2" :class="{ 'text-red-500': isOperationInvalid(op) }">
:class="{ 'text-red-500': isOperationInvalid(op) }"> <!-- LEFT OPERAND -->
<!-- LEFT OPERAND --> <div class="w-[2.5em] transition-all">
<div class="w-[2.5em] transition-all"> {{ op.left?.value ?? '&nbsp;' }}
{{ op.left?.value ?? '&nbsp;' }}
</div>
<!-- OPERATOR -->
<div class="flex justify-center w-[2.5em] transition-all">
<Component
class="text-lg text-stone-400"
v-if="op.operator"
:is="operatorIcons[op.operator]" />
<span v-else>&nbsp;</span>
</div>
<!-- RIGHT OPERAND -->
<div class="w-[2.5em] transition-all">
{{ op.right?.value ?? '&nbsp;' }}
</div>
<!-- EQUALS -->
<div class="mx-4 text-stone-400 border-none">
<IconEquals />
</div>
<!-- RESULT -->
<div class="w-[3.2em] transition-all">
<IconSad v-if="isOperationInvalid(op)" />
<span
:class="{ 'text-cyan-500': op.result.value === result }"
v-else-if="op.result">
{{ op.result?.value }}
</span>
<span v-else>?</span>
</div>
<!-- UNDO -->
<button
class="ml-4"
@click="undoOperation(i)">
<IconUndo
class="text-stone-600 hover:text-cyan-500"
:class="{
invisible: !canOperationBeDeleted(op),
}" />
</button>
</div> </div>
<!-- OPERATOR -->
<div class="flex justify-center w-[2.5em] transition-all">
<Component
class="text-lg text-stone-400"
v-if="op.operator"
:is="operatorIcons[op.operator]" />
<span v-else>&nbsp;</span>
</div>
<!-- RIGHT OPERAND -->
<div class="w-[2.5em] transition-all">
{{ op.right?.value ?? '&nbsp;' }}
</div>
<!-- EQUALS -->
<div class="mx-4 text-stone-400 border-none">
<IconEquals />
</div>
<!-- RESULT -->
<div class="w-[3.2em] transition-all">
<IconSad v-if="isOperationInvalid(op)" />
<span
:class="{ 'text-cyan-500': op.result.value === result }"
v-else-if="op.result">
{{ op.result?.value }}
</span>
<span v-else>?</span>
</div>
<!-- UNDO -->
<button
class="ml-4"
@click="undoOperation(i)">
<IconUndo
class="text-stone-600 hover:text-cyan-500"
:class="{
invisible: !canOperationBeDeleted(op),
}" />
</button>
</div> </div>
</div> </div>
</TransitionGroup> </TransitionGroup>
@ -93,9 +91,7 @@ function undoOperation(index: number): void {
let popped: Operation let popped: Operation
if (i === index) { if (i === index) {
popped = operations[index] popped = operations[index]
setTimeout(() => { operations[index] = getEmptyOperation()
operations[index] = getEmptyOperation()
}, transDelay * l)
} }
else { else {
popped = operations.pop()! popped = operations.pop()!

View File

@ -2,7 +2,7 @@
<div class="flex gap-2 justify-center my-4"> <div class="flex gap-2 justify-center my-4">
<PlaquetteBox <PlaquetteBox
is="button" is="button"
class="aspect-square w-[1.5em] text-2xl border btn" class="aspect-square w-[1.5em] text-2xl border btn-border"
@click="emit('click', item)" @click="emit('click', item)"
v-for="(item, i) in operators" v-for="(item, i) in operators"
:key="i"> :key="i">

View File

@ -9,7 +9,8 @@
@click="click(item)" @click="click(item)"
class="h-11 border" class="h-11 border"
:class="{ :class="{
'dark:text-stone-600 dark:border-stone-600 text-stone-500 border-stone-400': !item.free, 'dark:text-stone-600 dark:border-stone-600 text-stone-500 border-stone-400':
!item.free,
'hover:border-cyan-500': item.free, 'hover:border-cyan-500': item.free,
}" }"
:style="{ transitionDelay: `${initDelay * i}ms` }" :style="{ transitionDelay: `${initDelay * i}ms` }"
@ -40,4 +41,6 @@ function click(item: Plaquette): void {
initDelay.value = 0 initDelay.value = 0
emit('clickNumber', item) emit('clickNumber', item)
} }
defineExpose({ initDelay })
</script> </script>

View File

@ -6,7 +6,7 @@
<div class="h-12"> <div class="h-12">
<button @click="isSideMenuVisible = false"> <button @click="isSideMenuVisible = false">
<IconClose <IconClose
class="absolute top-0 right-1 h-12 text-xl hover:text-cyan-500" /> class="absolute top-0 right-1 h-12 text-xl btn" />
</button> </button>
</div> </div>
<div class="flex-1"> <div class="flex-1">

View File

@ -36,3 +36,8 @@ export function numberOfGamesSinceStart(): number {
) )
return differenceInDays(startDate, getCurrentDate()) return differenceInDays(startDate, getCurrentDate())
} }
export function clearOperationsList(): void {
operations.splice(0)
operations.push(getEmptyOperation())
}

View File

@ -59,17 +59,17 @@
.route-move, .route-move,
.route-enter-active, .route-enter-active,
.route-leave-active { .route-leave-active {
transition: all .8s ease; transition: all 1s ease;
} }
.route-enter-from { .route-enter-from {
opacity: 0; opacity: 0;
transform: translateX(100vw); transform: translateX(100%);
} }
.route-leave-to { .route-leave-to {
opacity: 0; opacity: 0;
transform: translateX(-100vw); transform: translateX(-100%);
} }
.route-leave-active { .route-leave-active {
@ -82,17 +82,17 @@
.route_back-move, .route_back-move,
.route_back-enter-active, .route_back-enter-active,
.route_back-leave-active { .route_back-leave-active {
transition: all .8s ease; transition: all 1s ease;
} }
.route_back-enter-from { .route_back-enter-from {
opacity: 0; opacity: 0;
transform: translateX(-100vw); transform: translateX(-100%);
} }
.route_back-leave-to { .route_back-leave-to {
opacity: 0; opacity: 0;
transform: translateX(100vw); transform: translateX(100%);
} }
.route_back-leave-active { .route_back-leave-active {
@ -128,9 +128,11 @@
.slide_up-enter-from, .slide_up-enter-from,
.slide_up-leave-to { .slide_up-leave-to {
opacity: 0; opacity: 0;
transform: translateY(30px); transform: translateY(60px);
} }
/* ZERO HEIGHT */
.zero_height-enter-active, .zero_height-enter-active,
.zero_height-leave-active { .zero_height-leave-active {
transition: all 0.5s ease; transition: all 0.5s ease;
@ -144,10 +146,20 @@
@layer utils { @layer utils {
} }
@layer components { @layer components {
.btn { .btn {
@apply hover:text-cyan-500 dark:text-stone-400 dark:hover:text-cyan-500;
@apply transition-colors duration-200;
}
.btn-border {
@apply w-fit p-2; @apply w-fit p-2;
@apply rounded border border-stone-600 transition-opacity; @apply rounded border border-stone-600 transition-opacity;
@apply hover:border-cyan-500 hover:text-cyan-500; @apply hover:border-cyan-500 hover:text-cyan-500;
@apply transition-colors duration-200;
}
.btn-disabled {
pointer-events: none;
opacity: 0.5;
} }
} }

View File

@ -2,12 +2,13 @@
"playAgain": "New number", "playAgain": "New number",
"easy": "easy", "easy": "easy",
"dailyGame": "Daily challenge\n", "dailyGame": "Daily challenge\n",
"endGame.failureLabel": "Too bad...<br>Another game?", "endGame.failureLabel": "Almost there...",
"endGame.victoryLabel": "Well done!", "endGame.victoryLabel": "Well done!",
"hard": "hard", "hard": "hard",
"impossible": "☠", "impossible": "☠",
"medium": "medium", "medium": "medium",
"randomGame": "Random number", "randomGame": "Random number",
"startGame": "Start", "startGame": "Start",
"dailyDescription": "The daily challenge changes every day at midnight, and is common to all players." "dailyDescription": "The daily challenge changes every day at midnight, and is common to all players.",
"share": "Share"
} }

View File

@ -1,7 +1,7 @@
{ {
"playAgain": "Nouveau nombre", "playAgain": "Nouveau nombre",
"endGame.victoryLabel": "Bien joué !", "endGame.victoryLabel": "Bien joué !",
"endGame.failureLabel": "Dommage...<br>Une autre partie ?", "endGame.failureLabel": "Presque...",
"startGame": "Démarrer", "startGame": "Démarrer",
"easy": "facile", "easy": "facile",
"medium": "moyen", "medium": "moyen",
@ -11,5 +11,6 @@
"randomGame": "Nombre au hasard", "randomGame": "Nombre au hasard",
"gameDescription": "Combinez les nombres imposés afin d'atteindre le résultat, ou de vous en approcher le plus possible.", "gameDescription": "Combinez les nombres imposés afin d'atteindre le résultat, ou de vous en approcher le plus possible.",
"dailyDescription": "Le défi quotidien change chaque jour à minuit, et est commun à tous les joueurs.", "dailyDescription": "Le défi quotidien change chaque jour à minuit, et est commun à tous les joueurs.",
"randomDescription": "Une partie au hasard, pour le plaisir." "randomDescription": "Une partie au hasard, pour le plaisir.",
"share": "Partager"
} }

View File

@ -17,7 +17,7 @@
<button <button
@click="startGame" @click="startGame"
class="py-2 px-4 mt-4 btn"> class="py-2 px-4 mt-4 btn-border">
{{ t('startGame') }} {{ t('startGame') }}
</button> </button>
</div> </div>
@ -25,6 +25,7 @@
<!-- PLAQUETTES --> <!-- PLAQUETTES -->
<PlaquettesList <PlaquettesList
ref="cmpPlaquettes"
:plaquettes="shownPlaquettes" :plaquettes="shownPlaquettes"
@click-number="selectNumber" /> @click-number="selectNumber" />
@ -34,8 +35,7 @@
<OperatorsList @click="selectOperator" /> <OperatorsList @click="selectOperator" />
<!-- Divider --> <!-- Divider -->
<div <div class="my-4 mx-auto max-w-sm border-b border-cyan-500/50" />
class="my-4 mx-auto max-w-sm border-b border-cyan-500/50" />
<!-- List of Operations --> <!-- List of Operations -->
<OperationsList v-show="gameIsRunning" /> <OperationsList v-show="gameIsRunning" />
@ -53,10 +53,16 @@
v-else v-else
v-html="t('endGame.failureLabel')" /> v-html="t('endGame.failureLabel')" />
<button <button
class="btn" v-if="!isDaily"
class="btn-border"
@click="reboot"> @click="reboot">
{{ t('playAgain') }} {{ t('playAgain') }}
</button> </button>
<button
v-else
class="btn-border">
{{ t('share') }}
</button>
</div> </div>
</Transition> </Transition>
</div> </div>
@ -78,6 +84,7 @@ import OperationsList from '@/components/OperationsList.vue'
import OperatorsList from '@/components/OperatorsList.vue' import OperatorsList from '@/components/OperatorsList.vue'
import PlaquettesList from '@/components/PlaquettesList.vue' import PlaquettesList from '@/components/PlaquettesList.vue'
import { import {
clearOperationsList,
currentOperation, currentOperation,
gameIsRunning, gameIsRunning,
gameState, gameState,
@ -98,6 +105,7 @@ const { t } = useI18n() // call `useI18n`, and spread `t` from `useI18n` return
*/ */
const difficultyLevel = ref<1 | 2>(1) const difficultyLevel = ref<1 | 2>(1)
const cmpPlaquettes = ref<InstanceType<typeof PlaquettesList>>()
const isDaily = computed(() => useRoute()?.name === 'daily') const isDaily = computed(() => useRoute()?.name === 'daily')
const shownPlaquettes = computed(() => const shownPlaquettes = computed(() =>
@ -128,6 +136,13 @@ watch(
function startGame(): void { function startGame(): void {
gameState.value = GameState.Loading gameState.value = GameState.Loading
setTimeout(() => (gameState.value = GameState.Playing), 500) setTimeout(() => (gameState.value = GameState.Playing), 500)
setTimeout(() => {
if (cmpPlaquettes.value) {
// Remove the animation delay for the plaquettes,
// or else the mouse hover effect is delayed until the first click
cmpPlaquettes.value.initDelay = 0
}
}, 2000)
} }
function selectNumber(p: Plaquette): void { function selectNumber(p: Plaquette): void {
@ -175,8 +190,7 @@ function reboot(): void {
})() })()
// result.value = 29 // result.value = 29
// Reset Operations list // Reset Operations list
operations.splice(0) clearOperationsList()
operations.push(getEmptyOperation())
// Generate result and plaquettes // Generate result and plaquettes
plaquettes.value = [] plaquettes.value = []
@ -221,6 +235,8 @@ onMounted(() => {
reboot() reboot()
gameState.value = GameState.Waiting gameState.value = GameState.Waiting
}, 800) }, 800)
// But make sure the operations list is empty asap
clearOperationsList()
}) })
onUnmounted(() => { onUnmounted(() => {

View File

@ -9,7 +9,7 @@
<div class="flex flex-col gap-4 items-center"> <div class="flex flex-col gap-4 items-center">
<RouterLink <RouterLink
to="/daily" to="/daily"
class="text-2xl btn"> class="text-2xl btn-border">
{{ t('dailyGame') }} {{ t('dailyGame') }}
</RouterLink> </RouterLink>
<span v-html="t('dailyDescription')" /> <span v-html="t('dailyDescription')" />
@ -19,7 +19,7 @@
<div class="flex flex-col gap-4 items-center"> <div class="flex flex-col gap-4 items-center">
<RouterLink <RouterLink
to="/random" to="/random"
class="text-2xl btn"> class="text-2xl btn-border">
{{ t('randomGame') }} {{ t('randomGame') }}
</RouterLink> </RouterLink>
{{ t('randomDescription') }} {{ t('randomDescription') }}