All checks were successful
Build and Deploy Nuxt / build (push) Successful in 29s
238 lines
4.2 KiB
Vue
238 lines
4.2 KiB
Vue
<script setup>
|
|
const GRID_COLS = 25
|
|
const GRID_ROWS = 80
|
|
|
|
// Palette of swatches
|
|
const palette = [
|
|
"#172038",
|
|
"#253a5e",
|
|
"#3c5e8b",
|
|
"#4f8fba",
|
|
"#73bed3",
|
|
"#a4dddb",
|
|
"#193024",
|
|
"#245938",
|
|
"#2b8435",
|
|
"#62ac4c",
|
|
"#a2dc6e",
|
|
"#c5e49b",
|
|
"#19332d",
|
|
"#25562e",
|
|
"#468232",
|
|
"#75a743",
|
|
"#a8ca58",
|
|
"#d0da91",
|
|
"#5f6d43",
|
|
"#97933a",
|
|
"#a9b74c",
|
|
"#cfd467",
|
|
"#d5dc97",
|
|
"#d6dea6",
|
|
"#382a28",
|
|
"#43322f",
|
|
"#564238",
|
|
"#715a42",
|
|
"#867150",
|
|
"#b1a282",
|
|
"#4d2b32",
|
|
"#7a4841",
|
|
"#ad7757",
|
|
"#c09473",
|
|
"#d7b594",
|
|
"#e7d5b3",
|
|
"#341c27",
|
|
"#602c2c",
|
|
"#884b2b",
|
|
"#be772b",
|
|
"#de9e41",
|
|
"#e8c170",
|
|
"#241527",
|
|
"#411d31",
|
|
"#752438",
|
|
"#a53030",
|
|
"#cf573c",
|
|
"#da863e",
|
|
"#1e1d39",
|
|
"#402751",
|
|
"#7a367b",
|
|
"#a23e8c",
|
|
"#c65197",
|
|
"#df84a5",
|
|
"#090a14",
|
|
"#10141f",
|
|
"#151d28",
|
|
"#202e37",
|
|
"#394a50",
|
|
"#577277",
|
|
"#819796",
|
|
"#a8b5b2",
|
|
"#c7cfcc",
|
|
"#ebede9"
|
|
]
|
|
|
|
const colorMap = ref({})
|
|
const selectedColor = ref(null)
|
|
const selectedColorId = ref(null)
|
|
|
|
const { get, post } = useApi();
|
|
const { initSocket, onGridCellPaint, removeGridCellPaintListener } = useSocket();
|
|
|
|
onMounted(async () => {
|
|
initSocket()
|
|
|
|
try {
|
|
const cells = await get(`/grid-cells?minx=${1}&miny=${1}&maxx=${GRID_ROWS}&maxy=${GRID_COLS}`);
|
|
cells.forEach(cell => {
|
|
let key = `${cell.x},${cell.y}`;
|
|
if(cell.color > 0 && cell.color <= palette.length)
|
|
colorMap.value[key] = palette[cell.color - 1];
|
|
else
|
|
colorMap.value[key] = "#00000000"
|
|
; });
|
|
} catch(e) {
|
|
console.error(e);
|
|
}
|
|
|
|
onGridCellPaint((data) => {
|
|
const key = `${data.x},${data.y}`;
|
|
if (data.color > 0 && data.color <= palette.length) {
|
|
colorMap.value[key] = palette[data.color - 1];
|
|
} else {
|
|
delete colorMap.value[key];
|
|
}
|
|
});
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
removeGridCellPaintListener();
|
|
});
|
|
|
|
function onCellClick(r, c) {
|
|
if (!selectedColor.value) return
|
|
const key = `${r},${c}`
|
|
|
|
colorMap.value[key] = selectedColor.value
|
|
|
|
// Send color paint
|
|
post('/grid-cells/paint', {
|
|
x: r,
|
|
y: c,
|
|
color: selectedColorId.value
|
|
})
|
|
}
|
|
|
|
function onCellRightClick(r, c, e) {
|
|
e.preventDefault()
|
|
const key = `${r},${c}`
|
|
delete colorMap.value[key]
|
|
|
|
// Send color delete
|
|
post('/grid-cells/paint', {
|
|
x: r,
|
|
y: c,
|
|
color: 0
|
|
})
|
|
}
|
|
|
|
function onSwatchClick(color) {
|
|
selectedColor.value = color;
|
|
selectedColorId.value = palette.indexOf(color) + 1;
|
|
}
|
|
|
|
const gridStyle = computed(() => ({
|
|
display: 'grid',
|
|
gridTemplateColumns: `repeat(${GRID_ROWS}, 1fr)`,
|
|
}))
|
|
</script>
|
|
|
|
<template>
|
|
<div class="place-container">
|
|
<div class="palette-row" role="toolbar" aria-label="Color palette">
|
|
<button
|
|
v-for="(color, i) in palette"
|
|
:key="i"
|
|
type="button"
|
|
class="swatch"
|
|
:class="{ active: selectedColor === color }"
|
|
:style="{ backgroundColor: color }"
|
|
:aria-label="`Select color ${color}`"
|
|
@click="onSwatchClick(color)"
|
|
/>
|
|
</div>
|
|
<div class="grid-wrapper" :style="gridStyle">
|
|
<div
|
|
v-for="r in GRID_ROWS"
|
|
:key="`row-${r}`"
|
|
class="grid-row"
|
|
>
|
|
<div
|
|
v-for="c in GRID_COLS"
|
|
:key="`${r}-${c}`"
|
|
class="cell"
|
|
:class="{ filled: colorMap[`${r},${c}`] }"
|
|
:style="colorMap[`${r},${c}`] ? { backgroundColor: colorMap[`${r},${c}`] } : {}"
|
|
@click="onCellClick(r, c)"
|
|
@contextmenu="onCellRightClick(r, c, $event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.place-container {
|
|
width: 100%;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.palette-row {
|
|
display: flex;
|
|
gap: 6px;
|
|
padding: 12px 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.swatch {
|
|
width: 28px;
|
|
height: 28px;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: border-color 0.05s, transform 0.05s;
|
|
|
|
&:hover {
|
|
transform: scale(1.15);
|
|
}
|
|
|
|
&.active {
|
|
border: 2px solid var(--color-link, #a4dddb);
|
|
box-shadow: 0 0 0 1px var(--color-link, #a4dddb);
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
width: 28px;
|
|
height: 28px;
|
|
}
|
|
}
|
|
|
|
.grid-wrapper {
|
|
background-color: var(--color-background-line);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.cell {
|
|
aspect-ratio: 1 / 1;
|
|
cursor: pointer;
|
|
background-color: transparent;
|
|
transition: background-color 0.05s steps(1);
|
|
|
|
&:hover {
|
|
outline: 1px solid #fff;
|
|
}
|
|
}
|
|
|
|
.filled {
|
|
opacity: 1;
|
|
}
|
|
</style>
|