resworbDownload resworb
Back

seating.lmth

All 56 lines. No libraries, nothing hidden. Read it before you run it.

seating.lmth
<!doctype html>
<meta charset="utf-8">
<meta name="resworb-write" content="./seating.csv">
<title>Seating</title>
<style>
  body { display: grid; grid-template-columns: 13rem 1fr; height: 100vh; margin: 0; color: #111; font: 13px 'Helvetica Neue', sans-serif; }
  #pile { overflow: auto; padding: 1rem; border-right: 1px solid #ddd; }
  #tables { display: flex; flex-wrap: wrap; align-content: start; gap: 1rem; padding: 1rem; overflow: auto; }
  .table { position: relative; width: 17rem; height: 17rem; }
  .table::before { content: attr(data-n); position: absolute; inset: 5.5rem; display: grid; place-items: center; border: 1px solid #111; border-radius: 50%; font-size: 1.4rem; }
  .table .guest { position: absolute; top: 50%; left: 50%; translate: -50% -50%; transform: rotate(calc(var(--seat) * 36deg)) translateY(-6.6rem) rotate(calc(var(--seat) * -36deg)); }
  .guest { display: inline-block; margin: 2px; padding: 2px 6px; border: 1px solid #111; background: #fff; white-space: nowrap; cursor: grab; }
  .apart { border-color: #d70015; background: #d70015; color: #fff; }
</style>
<div id="pile"></div>
<div id="tables"></div>
<script type="module">
  // guests.csv: a header row, then "name,apart" per guest, where apart names someone they must not sit with.
  // seating.csv: "guest,table" for the venue, saved on every drop. Tables seat 10.
  const guests = (await resworb.readFile('guests.csv')).trim().split('\n').slice(1).map(line => line.trim().split(','));
  const seating = (await resworb.exists('seating.csv')) ? (await resworb.readFile('seating.csv')).trim().split('\n').slice(1) : [];
  const seats = Object.fromEntries(seating.map(line => line.trim().split(',')));

  for (let n = 1; n <= Math.ceil(guests.length / 10); n++) {
    const table = document.createElement('div');
    table.className = 'table';
    table.dataset.n = n;
    tables.append(table);
  }

  addEventListener('dragover', event => event.target.closest('.table, #pile') && event.preventDefault());
  addEventListener('drop', async event => {
    const place = event.target.closest('.table, #pile');
    const name = event.dataTransfer.getData('text/plain');
    if (place === pile) delete seats[name];
    else if (place.children.length < 10) seats[name] = place.dataset.n;
    await resworb.writeFile('seating.csv', ['guest,table', ...Object.entries(seats).map(seat => seat.join(','))].join('\n') + '\n');
    draw();
  });

  draw();

  function draw() {
    for (const place of [pile, ...tables.children]) place.replaceChildren();
    for (const [name, apart] of guests) {
      const table = seats[name];
      const place = table ? tables.children[table - 1] : pile;
      const guest = Object.assign(document.createElement('span'), { className: 'guest', textContent: name, draggable: true });
      guest.classList.toggle('apart', Boolean(table) && guests.some(([other, otherApart]) => seats[other] === table && (other === apart || otherApart === name)));
      guest.style.setProperty('--seat', place.children.length);
      guest.ondragstart = event => event.dataTransfer.setData('text/plain', name);
      place.append(guest);
    }
    document.title = `Seating ยท ${Object.keys(seats).length} of ${guests.length} seated`;
  }
</script>
Download seating.lmth