Mobile-first i pixel-perfect: wspólne --cell-size, grid dla podpowiedzi, dynamiczny rozmiar komórki z uwzględnieniem paddingów i szerokości opisów; poprawa touch/pointer i double-tap na mobile; wyrównanie layoutu bez nachodzenia na desktopie
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, computed } from 'vue';
|
||||
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
|
||||
import { usePuzzleStore } from '@/stores/puzzle';
|
||||
import { useHints } from '@/composables/useHints';
|
||||
import { useNonogram } from '@/composables/useNonogram';
|
||||
@@ -10,30 +10,81 @@ const store = usePuzzleStore();
|
||||
const { rowHints, colHints } = useHints(computed(() => store.solution));
|
||||
const { startDrag, onMouseEnter, stopDrag } = useNonogram();
|
||||
|
||||
// Global mouseup to stop dragging even if mouse leaves grid
|
||||
const cellSize = ref(30);
|
||||
const rowHintsRef = ref(null);
|
||||
|
||||
const getRowHintsWidth = () => {
|
||||
const el = rowHintsRef.value?.$el;
|
||||
if (!el) return 0;
|
||||
return el.offsetWidth || 0;
|
||||
};
|
||||
|
||||
const computeCellSize = () => {
|
||||
const vw = Math.min(window.innerWidth, 900);
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const hintWidth = getRowHintsWidth();
|
||||
const gapRaw = rootStyles.getPropertyValue('--gap-size') || '2px';
|
||||
const gridPadRaw = rootStyles.getPropertyValue('--grid-padding') || '5px';
|
||||
const gap = parseFloat(gapRaw);
|
||||
const gridPad = parseFloat(gridPadRaw);
|
||||
const bodyStyles = getComputedStyle(document.body);
|
||||
const bodyPadding = parseFloat(bodyStyles.paddingLeft) + parseFloat(bodyStyles.paddingRight);
|
||||
const availableForGrid = vw - bodyPadding - hintWidth;
|
||||
const size = Math.floor((availableForGrid - gridPad * 2 - (store.size - 1) * gap) / store.size);
|
||||
cellSize.value = Math.max(18, Math.min(36, size));
|
||||
};
|
||||
|
||||
const handleGlobalMouseUp = () => {
|
||||
stopDrag();
|
||||
stopDrag();
|
||||
};
|
||||
|
||||
const handleGlobalPointerUp = () => {
|
||||
stopDrag();
|
||||
};
|
||||
|
||||
const handlePointerMove = (e) => {
|
||||
const el = document.elementFromPoint(e.clientX, e.clientY);
|
||||
if (!el) return;
|
||||
const r = el.getAttribute('data-r');
|
||||
const c = el.getAttribute('data-c');
|
||||
if (r != null && c != null) {
|
||||
onMouseEnter(Number(r), Number(c));
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('mouseup', handleGlobalMouseUp);
|
||||
nextTick(() => {
|
||||
computeCellSize();
|
||||
});
|
||||
window.addEventListener('resize', computeCellSize);
|
||||
window.addEventListener('mouseup', handleGlobalMouseUp);
|
||||
window.addEventListener('pointerup', handleGlobalPointerUp);
|
||||
window.addEventListener('touchend', handleGlobalPointerUp, { passive: true });
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('mouseup', handleGlobalMouseUp);
|
||||
window.removeEventListener('resize', computeCellSize);
|
||||
window.removeEventListener('mouseup', handleGlobalMouseUp);
|
||||
window.removeEventListener('pointerup', handleGlobalPointerUp);
|
||||
window.removeEventListener('touchend', handleGlobalPointerUp);
|
||||
});
|
||||
|
||||
watch(() => store.size, async () => {
|
||||
await nextTick();
|
||||
computeCellSize();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="game-board-wrapper">
|
||||
<div class="game-container">
|
||||
<div class="game-container" :style="{ '--cell-size': `${cellSize}px` }">
|
||||
<div class="corner-spacer"></div>
|
||||
|
||||
<!-- Column Hints -->
|
||||
<Hints :hints="colHints" orientation="col" />
|
||||
<Hints :hints="colHints" orientation="col" :size="store.size" />
|
||||
|
||||
<!-- Row Hints -->
|
||||
<Hints :hints="rowHints" orientation="row" />
|
||||
<Hints ref="rowHintsRef" :hints="rowHints" orientation="row" :size="store.size" />
|
||||
|
||||
<!-- Grid -->
|
||||
<div
|
||||
@@ -42,6 +93,7 @@ onUnmounted(() => {
|
||||
gridTemplateColumns: `repeat(${store.size}, var(--cell-size))`,
|
||||
gridTemplateRows: `repeat(${store.size}, var(--cell-size))`
|
||||
}"
|
||||
@pointermove.prevent="handlePointerMove"
|
||||
@mouseleave="stopDrag"
|
||||
>
|
||||
<template v-for="(row, r) in store.playerGrid" :key="r">
|
||||
@@ -84,14 +136,13 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.corner-spacer {
|
||||
width: 100px; /* Must match Row Hints width */
|
||||
height: auto; /* Adapts to Col Hints height */
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--gap-size);
|
||||
padding: 5px;
|
||||
padding: var(--grid-padding);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user