Bug and animations fixes
This commit is contained in:
parent
cc86729dbc
commit
d31e17e3be
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 ?? ' ' }}
|
||||||
{{ op.left?.value ?? ' ' }}
|
|
||||||
</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> </span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- RIGHT OPERAND -->
|
|
||||||
<div class="w-[2.5em] transition-all">
|
|
||||||
{{ op.right?.value ?? ' ' }}
|
|
||||||
</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> </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RIGHT OPERAND -->
|
||||||
|
<div class="w-[2.5em] transition-all">
|
||||||
|
{{ op.right?.value ?? ' ' }}
|
||||||
|
</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()!
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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') }}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user