634 lines
22 KiB
JavaScript
634 lines
22 KiB
JavaScript
// for some reason it isn't showing that you are dragging a fruit when dragging
|
|
// from one cell to another. it actually is dragging but it doesn't show it
|
|
// this is a helper to create a drag image so it shows the fruit being dragged
|
|
function createDragImage(el) {
|
|
const dragImg = el.cloneNode(true);
|
|
dragImg.style.position = 'absolute';
|
|
dragImg.style.top = '-1000px';
|
|
dragImg.style.left = '-1000px';
|
|
document.body.appendChild(dragImg);
|
|
return dragImg;
|
|
}
|
|
|
|
// helper to place a fruit div inside a square div
|
|
function placeFruitInSquare(squareEl, fruitName) {
|
|
// clear existing content except the square number
|
|
const squareNumber = squareEl.dataset.squareId;
|
|
squareEl.innerHTML = '';
|
|
// record the fruit in the square for tracking
|
|
squareEl.dataset.fruit = fruitName;
|
|
// find the template fruit in the palette
|
|
const template = document.querySelector(`.fruit[data-fruit="${fruitName}"]`);
|
|
if (!template) return;
|
|
// clone and drop
|
|
const clone = template.cloneNode(true);
|
|
clone.classList.remove('fruit'); // remove extra styling if you like
|
|
clone.classList.add('dropped-fruit');
|
|
clone.setAttribute('draggable', true); // allow repositioning if desired
|
|
|
|
// add drag start for dragging from a square
|
|
clone.addEventListener('dragstart', e => {
|
|
e.dataTransfer.setData('text/plain', fruitName);
|
|
const dragImg = createDragImage(clone);
|
|
e.dataTransfer.setDragImage(dragImg, dragImg.offsetWidth / 2, dragImg.offsetHeight / 2);
|
|
clone.addEventListener('dragend', () => {
|
|
dragImg.remove();
|
|
}, { once: true });
|
|
});
|
|
|
|
squareEl.appendChild(clone);
|
|
|
|
// add X clear button
|
|
const clearBtn = document.createElement('button');
|
|
clearBtn.classList.add('clear-btn');
|
|
clearBtn.textContent = 'X';
|
|
clearBtn.addEventListener('click', async (e) => {
|
|
e.stopPropagation(); // prevent event bubbling
|
|
squareEl.innerHTML = '';
|
|
delete squareEl.dataset.fruit;
|
|
// re-add the square number after clearing
|
|
const numDiv = document.createElement('div');
|
|
numDiv.className = 'square-number';
|
|
numDiv.textContent = squareNumber;
|
|
squareEl.appendChild(numDiv);
|
|
const squareId = squareEl.dataset.squareId;
|
|
await fetch('/api/positions', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ squareId, fruit: '' }),
|
|
});
|
|
});
|
|
squareEl.appendChild(clearBtn);
|
|
|
|
// add the square number in the bottom right
|
|
const numDiv = document.createElement('div');
|
|
numDiv.className = 'square-number';
|
|
numDiv.textContent = squareNumber;
|
|
squareEl.appendChild(numDiv);
|
|
}
|
|
|
|
// Helper to show an input box for custom fruit name
|
|
function showCustomFruitInput(squareEl) {
|
|
// Prevent multiple inputs
|
|
if (squareEl.querySelector('.custom-fruit-input')) return;
|
|
|
|
const squareNumber = squareEl.dataset.squareId;
|
|
squareEl.innerHTML = '';
|
|
const input = document.createElement('input');
|
|
input.type = 'text';
|
|
input.placeholder = 'Enter name...';
|
|
input.className = 'custom-fruit-input';
|
|
input.style.width = '90%';
|
|
input.style.fontSize = '1em';
|
|
input.style.textAlign = 'center';
|
|
input.style.marginTop = '30px';
|
|
squareEl.appendChild(input);
|
|
|
|
// Add the square number in the bottom right
|
|
const numDiv = document.createElement('div');
|
|
numDiv.className = 'square-number';
|
|
numDiv.textContent = squareNumber;
|
|
squareEl.appendChild(numDiv);
|
|
|
|
input.focus();
|
|
|
|
// Handler for when user presses Enter
|
|
input.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter') {
|
|
input.blur();
|
|
}
|
|
});
|
|
|
|
input.addEventListener('blur', function () {
|
|
const customName = input.value.trim();
|
|
if (!customName) {
|
|
// Restore square if nothing entered
|
|
squareEl.innerHTML = '';
|
|
squareEl.appendChild(numDiv);
|
|
return;
|
|
}
|
|
// Wait for category selection
|
|
waitForCategorySelection(customName, squareEl, squareNumber);
|
|
});
|
|
}
|
|
|
|
// Wait for user to click a category, then send to server
|
|
function waitForCategorySelection(customName, squareEl, squareNumber) {
|
|
// Highlight categories and show a message
|
|
document.querySelectorAll('.categories').forEach(cat => {
|
|
cat.classList.add('category-selectable');
|
|
});
|
|
const msg = document.createElement('div');
|
|
msg.textContent = 'Click a category to add "' + customName + '"';
|
|
msg.className = 'category-select-msg';
|
|
msg.style.position = 'absolute';
|
|
msg.style.top = '40%';
|
|
msg.style.left = '50%';
|
|
msg.style.transform = 'translate(-50%, -50%)';
|
|
msg.style.background = 'rgba(0,0,0,0.8)';
|
|
msg.style.color = 'white';
|
|
msg.style.padding = '8px 16px';
|
|
msg.style.borderRadius = '8px';
|
|
msg.style.zIndex = '1000';
|
|
msg.style.fontSize = '1.1em';
|
|
document.body.appendChild(msg);
|
|
|
|
// Handler for category click
|
|
function onCategoryClick(e) {
|
|
e.stopPropagation();
|
|
document.querySelectorAll('.categories').forEach(cat => {
|
|
cat.classList.remove('category-selectable');
|
|
cat.removeEventListener('click', onCategoryClick, true);
|
|
});
|
|
document.body.removeChild(msg);
|
|
|
|
// Find category name
|
|
const catHeader = e.currentTarget.querySelector('.category-header h4');
|
|
const categoryName = catHeader ? catHeader.textContent : null;
|
|
if (!categoryName) return;
|
|
|
|
// Send to server to add fruit to category
|
|
fetch('/api/add-fruit', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ category: categoryName, fruit: customName })
|
|
})
|
|
.then(res => res.json())
|
|
.then(async data => {
|
|
if (data.success) {
|
|
// Reload categories in sidebar
|
|
if (window.loadCategories) await window.loadCategories();
|
|
initializeFruitAndCategoryEvents();
|
|
// Place the fruit in the square
|
|
placeFruitInSquare(squareEl, customName);
|
|
// Save the new fruit's position to the database
|
|
const squareId = squareEl.dataset.squareId;
|
|
await fetch('/api/positions', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ squareId, fruit: customName }),
|
|
});
|
|
} else {
|
|
alert('Could not add fruit: ' + (data.error || 'Unknown error'));
|
|
// Restore square
|
|
squareEl.innerHTML = '';
|
|
const numDiv = document.createElement('div');
|
|
numDiv.className = 'square-number';
|
|
numDiv.textContent = squareNumber;
|
|
squareEl.appendChild(numDiv);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Listen for click on any category (use capture to get before toggle)
|
|
document.querySelectorAll('.categories').forEach(cat => {
|
|
cat.addEventListener('click', onCategoryClick, true);
|
|
});
|
|
}
|
|
|
|
// Wrap all fruit/category event logic in a function so it can be called after dynamic loading
|
|
function initializeFruitAndCategoryEvents() {
|
|
// initialize drag & drop on fruits
|
|
document.querySelectorAll('.fruit').forEach(el => {
|
|
el.addEventListener('dragstart', e => {
|
|
e.dataTransfer.setData('text/plain', el.dataset.fruit);
|
|
const dragImg = createDragImage(el);
|
|
e.dataTransfer.setDragImage(dragImg, dragImg.offsetWidth / 2, dragImg.offsetHeight / 2);
|
|
// cleanup after drag
|
|
el.addEventListener('dragend', () => {
|
|
dragImg.remove();
|
|
}, { once: true });
|
|
});
|
|
|
|
// updated mouseover event
|
|
el.addEventListener('mouseover', e => {
|
|
const parentCategory = el.closest('.categories');
|
|
if (parentCategory) {
|
|
// Unhighlight all squares for fruits in this category first
|
|
parentCategory.querySelectorAll('.fruit').forEach(sib => {
|
|
const sq = document.querySelector(`.square[data-fruit="${sib.dataset.fruit}"]`);
|
|
if (sq) sq.classList.remove('highlight');
|
|
});
|
|
}
|
|
// Now highlight only the hovered fruit's square
|
|
const square = document.querySelector(`.square[data-fruit="${el.dataset.fruit}"]`);
|
|
if (square) square.classList.add('highlight');
|
|
updateHighlightedBorders();
|
|
});
|
|
|
|
// updated mouseout event
|
|
el.addEventListener('mouseout', e => {
|
|
// Remove highlight from the hovered fruit's square
|
|
const square = document.querySelector(`.square[data-fruit="${el.dataset.fruit}"]`);
|
|
if (square) square.classList.remove('highlight');
|
|
// If still inside the category container, restore highlighting to all fruits there
|
|
const parentCategory = el.closest('.categories');
|
|
if (parentCategory && parentCategory.contains(e.relatedTarget)) {
|
|
parentCategory.querySelectorAll('.fruit').forEach(sib => {
|
|
const sq = document.querySelector(`.square[data-fruit="${sib.dataset.fruit}"]`);
|
|
if (sq) sq.classList.add('highlight');
|
|
});
|
|
}
|
|
updateHighlightedBorders();
|
|
});
|
|
|
|
// Add click handler for delete popup (sidebar only)
|
|
el.addEventListener('click', function(e) {
|
|
// Only trigger for sidebar fruits (not dropped-fruit)
|
|
if (!el.classList.contains('fruit') || el.classList.contains('dropped-fruit')) return;
|
|
// Find category name
|
|
const catDiv = el.closest('.categories');
|
|
if (!catDiv) return;
|
|
const catHeader = catDiv.querySelector('.category-header h4');
|
|
const categoryName = catHeader ? catHeader.textContent : null;
|
|
if (!categoryName) return;
|
|
showDeleteFruitPopup(el.dataset.fruit, categoryName);
|
|
e.stopPropagation();
|
|
});
|
|
});
|
|
|
|
// make each square a drop target
|
|
document.querySelectorAll('.square').forEach(sq => {
|
|
sq.addEventListener('dragover', e => e.preventDefault());
|
|
sq.addEventListener('drop', async e => {
|
|
e.preventDefault();
|
|
const fruit = e.dataTransfer.getData('text/plain');
|
|
|
|
// check if the fruit is already placed in another square
|
|
const existingSquare = document.querySelector(`.square[data-fruit="${fruit}"]`);
|
|
if (existingSquare && existingSquare !== sq) {
|
|
existingSquare.innerHTML = '';
|
|
delete existingSquare.dataset.fruit;
|
|
const existingSquareId = existingSquare.dataset.squareId;
|
|
await fetch('/api/positions', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ squareId: existingSquareId, fruit: '' }),
|
|
});
|
|
}
|
|
|
|
placeFruitInSquare(sq, fruit);
|
|
|
|
// save to server
|
|
const squareId = sq.dataset.squareId;
|
|
await fetch('/api/positions', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ squareId, fruit }),
|
|
});
|
|
});
|
|
|
|
// Add double-click handler for custom fruit
|
|
sq.addEventListener('dblclick', e => {
|
|
// Only allow if square is empty (no fruit)
|
|
if (!sq.dataset.fruit && !sq.querySelector('.dropped-fruit')) {
|
|
showCustomFruitInput(sq);
|
|
}
|
|
});
|
|
});
|
|
|
|
// add highlighting for squares based on category hover
|
|
document.querySelectorAll('.categories').forEach(category => {
|
|
category.addEventListener('mouseenter', () => {
|
|
category.querySelectorAll('.fruit').forEach(fruitEl => {
|
|
const fruitName = fruitEl.dataset.fruit;
|
|
const square = document.querySelector(`.square[data-fruit="${fruitName}"]`);
|
|
if (square) {
|
|
square.classList.add('highlight');
|
|
}
|
|
});
|
|
updateHighlightedBorders();
|
|
});
|
|
category.addEventListener('mouseleave', () => {
|
|
category.querySelectorAll('.fruit').forEach(fruitEl => {
|
|
const fruitName = fruitEl.dataset.fruit;
|
|
const square = document.querySelector(`.square[data-fruit="${fruitName}"]`);
|
|
if (square) {
|
|
square.classList.remove('highlight');
|
|
}
|
|
});
|
|
updateHighlightedBorders();
|
|
});
|
|
});
|
|
|
|
// Collapsible categories logic
|
|
document.querySelectorAll('.categories').forEach(category => {
|
|
const toggleBtn = category.querySelector('.category-header .category-toggle');
|
|
const content = category.querySelector('.category-content');
|
|
if (toggleBtn && content) {
|
|
toggleBtn.addEventListener('click', () => {
|
|
const isOpen = content.style.display !== 'none';
|
|
content.style.display = isOpen ? 'none' : '';
|
|
toggleBtn.textContent = isOpen ? '►' : '▼';
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// on load, fetch saved positions and render
|
|
window.addEventListener('DOMContentLoaded', async () => {
|
|
try {
|
|
// Add square numbers to all squares on initial load
|
|
document.querySelectorAll('.square').forEach(sq => {
|
|
// Only add if not already present (avoid duplicates)
|
|
if (!sq.querySelector('.square-number')) {
|
|
const numDiv = document.createElement('div');
|
|
numDiv.className = 'square-number';
|
|
numDiv.textContent = sq.dataset.squareId;
|
|
sq.appendChild(numDiv);
|
|
}
|
|
});
|
|
|
|
const res = await fetch('/api/positions');
|
|
const mapping = await res.json();
|
|
for (const [squareId, fruit] of Object.entries(mapping)) {
|
|
const sq = document.querySelector(
|
|
`.square[data-square-id="${squareId}"]`
|
|
);
|
|
if (sq && fruit) {
|
|
placeFruitInSquare(sq, fruit);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Could not load saved positions', err);
|
|
}
|
|
|
|
// After categories are loaded, initialize events
|
|
if (window.loadCategories) {
|
|
await window.loadCategories();
|
|
initializeFruitAndCategoryEvents();
|
|
} else {
|
|
initializeFruitAndCategoryEvents();
|
|
}
|
|
});
|
|
|
|
// this is the stuff that let's you drag the map around and zoom in and out
|
|
(() => {
|
|
const mapWrapper = document.getElementById('mapWrapper');
|
|
const map = document.getElementById('map');
|
|
let isDragging = false;
|
|
let startX, startY, startPanX, startPanY;
|
|
let panX = 0, panY = 0;
|
|
let currentScale = 1;
|
|
const minScale = 0.5, maxScale = 3;
|
|
|
|
function updateTransform() {
|
|
map.style.transform = `translate(${panX}px, ${panY}px) scale(${currentScale})`;
|
|
}
|
|
|
|
mapWrapper.addEventListener('mousedown', (e) => {
|
|
if (e.target.closest('.fruit') || e.target.closest('.dropped-fruit')) return;
|
|
isDragging = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
startPanX = panX;
|
|
startPanY = panY;
|
|
map.style.cursor = 'grabbing';
|
|
});
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
if (!isDragging) return;
|
|
const deltaX = e.clientX - startX;
|
|
const deltaY = e.clientY - startY;
|
|
panX = startPanX + deltaX;
|
|
panY = startPanY + deltaY;
|
|
updateTransform();
|
|
});
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
if (isDragging) {
|
|
isDragging = false;
|
|
map.style.cursor = 'grab';
|
|
}
|
|
});
|
|
|
|
// mouse wheel zoom and smooth zoom
|
|
mapWrapper.addEventListener('wheel', (e) => {
|
|
e.preventDefault();
|
|
const sensitivity = 0.001; // adjust sensitivity of zoom
|
|
// calculate new scale smoothly using exponential curve
|
|
let newScale = currentScale * Math.exp(-e.deltaY * sensitivity);
|
|
// clamp to allowed values
|
|
newScale = Math.min(maxScale, Math.max(minScale, newScale));
|
|
const zoomFactor = newScale / currentScale;
|
|
|
|
// Get the mouse position relative to the mapWrapper
|
|
const rect = mapWrapper.getBoundingClientRect();
|
|
const offsetX = e.clientX - rect.left;
|
|
const offsetY = e.clientY - rect.top;
|
|
|
|
// Adjust panX and panY so the zoom is centered on the pointer
|
|
panX = offsetX - zoomFactor * (offsetX - panX);
|
|
panY = offsetY - zoomFactor * (offsetY - panY);
|
|
|
|
currentScale = newScale;
|
|
updateTransform();
|
|
});
|
|
|
|
// new function for smooth zoom animation
|
|
function animateZoom(targetScale, targetPanX, targetPanY, duration = 300) {
|
|
const startScale = currentScale;
|
|
const startPanX = panX;
|
|
const startPanY = panY;
|
|
const startTime = performance.now();
|
|
|
|
function step(now) {
|
|
const elapsed = now - startTime;
|
|
const progress = Math.min(elapsed / duration, 1); // progress from 0 to 1 (linear easing)
|
|
|
|
currentScale = startScale + (targetScale - startScale) * progress;
|
|
panX = startPanX + (targetPanX - startPanX) * progress;
|
|
panY = startPanY + (targetPanY - startPanY) * progress;
|
|
updateTransform();
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(step);
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(step);
|
|
}
|
|
|
|
// zoom button handlers using smooth animation
|
|
const zoomInBtn = document.getElementById('zoomIn');
|
|
const zoomOutBtn = document.getElementById('zoomOut');
|
|
|
|
const zoomButtonHandler = (zoomFactor) => {
|
|
let targetScale = currentScale * zoomFactor;
|
|
targetScale = Math.min(maxScale, Math.max(minScale, targetScale));
|
|
// calculate the actual zoom factor applied.
|
|
const actualFactor = targetScale / currentScale;
|
|
|
|
// use the center of the mapWrapper as the zoom origin.
|
|
const rect = mapWrapper.getBoundingClientRect();
|
|
const centerX = rect.width / 2;
|
|
const centerY = rect.height / 2;
|
|
const targetPanX = centerX - actualFactor * (centerX - panX);
|
|
const targetPanY = centerY - actualFactor * (centerY - panY);
|
|
|
|
animateZoom(targetScale, targetPanX, targetPanY, 300); // change this for the zoom animation time
|
|
};
|
|
|
|
zoomInBtn.addEventListener('click', () => {
|
|
zoomButtonHandler(1.2);
|
|
});
|
|
|
|
zoomOutBtn.addEventListener('click', () => {
|
|
zoomButtonHandler(0.8);
|
|
});
|
|
})();
|
|
|
|
/*
|
|
this is a helper to update highlighted square borders.
|
|
for each highlighted square, if a neighboring square is also highlighted,
|
|
set the border color on that touching side to transparent.
|
|
this makes it look like the squares are connected.
|
|
*/
|
|
function updateHighlightedBorders() {
|
|
const squares = document.querySelectorAll('.square');
|
|
squares.forEach(sq => {
|
|
// Reset any inline style and data if not highlighted
|
|
if (!sq.classList.contains('highlight')) {
|
|
sq.style.borderLeftColor = '';
|
|
sq.style.borderRightColor = '';
|
|
sq.style.borderTopColor = '';
|
|
sq.style.borderBottomColor = '';
|
|
sq.removeAttribute('data-glow-connected');
|
|
return;
|
|
}
|
|
|
|
// set all borders to the highlight color
|
|
sq.style.borderLeftColor = '#d103f9';
|
|
sq.style.borderRightColor = '#d103f9';
|
|
sq.style.borderTopColor = '#d103f9';
|
|
sq.style.borderBottomColor = '#d103f9';
|
|
|
|
// find the parent row and index
|
|
const parentRow = sq.parentElement; // .map-row
|
|
const cells = Array.from(parentRow.children);
|
|
const idx = cells.indexOf(sq);
|
|
|
|
// for top and bottom, get the row index from map (#map container)
|
|
const map = parentRow.parentElement;
|
|
const rows = Array.from(map.children);
|
|
const rowIndex = rows.indexOf(parentRow);
|
|
|
|
// Track which sides are connected
|
|
let connected = '';
|
|
|
|
// check left neighbor in the same row
|
|
if (idx > 0 && cells[idx - 1].classList.contains('square') && cells[idx - 1].classList.contains('highlight')) {
|
|
sq.style.borderLeftColor = 'transparent';
|
|
connected += 'L';
|
|
}
|
|
// check right neighbor in the same row
|
|
if (idx < cells.length - 1 && cells[idx + 1].classList.contains('square') && cells[idx + 1].classList.contains('highlight')) {
|
|
sq.style.borderRightColor = 'transparent';
|
|
connected += 'R';
|
|
}
|
|
// check top neighbor
|
|
if (rowIndex > 0) {
|
|
const topRow = rows[rowIndex - 1];
|
|
const topCells = Array.from(topRow.children);
|
|
if (topCells[idx] && topCells[idx].classList.contains('square') && topCells[idx].classList.contains('highlight')) {
|
|
sq.style.borderTopColor = 'transparent';
|
|
connected += 'T';
|
|
}
|
|
}
|
|
// check bottom neighbor
|
|
if (rowIndex < rows.length - 1) {
|
|
const bottomRow = rows[rowIndex + 1];
|
|
const bottomCells = Array.from(bottomRow.children);
|
|
if (bottomCells[idx] && bottomCells[idx].classList.contains('square') && bottomCells[idx].classList.contains('highlight')) {
|
|
sq.style.borderBottomColor = 'transparent';
|
|
connected += 'B';
|
|
}
|
|
}
|
|
// Set a data attribute for CSS to use for glow masking
|
|
sq.setAttribute('data-glow-connected', connected);
|
|
});
|
|
}
|
|
|
|
// add Socket.io client side for real time updates
|
|
const socket = io();
|
|
|
|
socket.on('update', data => {
|
|
// find the square with the given squareId
|
|
const sq = document.querySelector(`.square[data-square-id="${data.squareId}"]`);
|
|
if (sq) {
|
|
if (data.fruit) {
|
|
if (sq.dataset.fruit !== data.fruit) {
|
|
placeFruitInSquare(sq, data.fruit);
|
|
}
|
|
} else {
|
|
sq.innerHTML = '';
|
|
delete sq.dataset.fruit;
|
|
// re-add the square number after clearing
|
|
const numDiv = document.createElement('div');
|
|
numDiv.className = 'square-number';
|
|
numDiv.textContent = sq.dataset.squareId;
|
|
sq.appendChild(numDiv);
|
|
}
|
|
// add highlight and remove after 3 seconds
|
|
sq.classList.add('highlight-update');
|
|
setTimeout(() => {
|
|
sq.classList.remove('highlight-update');
|
|
}, 1000);
|
|
}
|
|
});
|
|
|
|
// Helper to show a delete confirmation popup for a fruit
|
|
function showDeleteFruitPopup(fruitName, categoryName) {
|
|
// Remove any existing popup
|
|
const existing = document.getElementById('delete-fruit-popup');
|
|
if (existing) existing.remove();
|
|
|
|
const popup = document.createElement('div');
|
|
popup.id = 'delete-fruit-popup';
|
|
popup.style.position = 'fixed';
|
|
popup.style.top = '50%';
|
|
popup.style.left = '50%';
|
|
popup.style.transform = 'translate(-50%, -50%)';
|
|
popup.style.background = '#222';
|
|
popup.style.color = 'white';
|
|
popup.style.padding = '24px 32px';
|
|
popup.style.borderRadius = '12px';
|
|
popup.style.boxShadow = '0 4px 32px #000b';
|
|
popup.style.zIndex = '2000';
|
|
popup.style.textAlign = 'center';
|
|
|
|
popup.innerHTML = `
|
|
<div style="margin-bottom:18px;font-size:1.1em;">
|
|
Remove "<b>${fruitName}</b>" from <b>${categoryName}</b>?
|
|
</div>
|
|
<button id="delete-fruit-confirm" style="margin-right:16px;padding:6px 18px;background:#d103f9;color:white;border:none;border-radius:5px;cursor:pointer;">Delete</button>
|
|
<button id="delete-fruit-cancel" style="padding:6px 18px;background:#444;color:white;border:none;border-radius:5px;cursor:pointer;">Cancel</button>
|
|
`;
|
|
|
|
document.body.appendChild(popup);
|
|
|
|
document.getElementById('delete-fruit-cancel').onclick = () => popup.remove();
|
|
|
|
document.getElementById('delete-fruit-confirm').onclick = async () => {
|
|
// Send delete request
|
|
await fetch('/api/delete-fruit', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ category: categoryName, fruit: fruitName })
|
|
});
|
|
popup.remove();
|
|
if (window.loadCategories) await window.loadCategories();
|
|
initializeFruitAndCategoryEvents();
|
|
// Remove fruit from any square if present
|
|
document.querySelectorAll(`.square[data-fruit="${fruitName}"]`).forEach(sq => {
|
|
sq.innerHTML = '';
|
|
delete sq.dataset.fruit;
|
|
// re-add the square number after clearing
|
|
const numDiv = document.createElement('div');
|
|
numDiv.className = 'square-number';
|
|
numDiv.textContent = sq.dataset.squareId;
|
|
sq.appendChild(numDiv);
|
|
});
|
|
};
|
|
}
|