diff --git a/client/index.html b/client/index.html index 810ffe5..69fbc97 100644 --- a/client/index.html +++ b/client/index.html @@ -5,8 +5,6 @@ Posti DM - - diff --git a/client/login.html b/client/login.html index b328eec..67ef3e6 100644 --- a/client/login.html +++ b/client/login.html @@ -5,8 +5,6 @@ Posti DM - - @@ -32,6 +30,6 @@ - + diff --git a/client/orario.html b/client/orario.html new file mode 100644 index 0000000..facc3d7 --- /dev/null +++ b/client/orario.html @@ -0,0 +1,34 @@ + + + + + + + Posti DM + + + +
+ + + diff --git a/client/src/common.js b/client/src/common.js new file mode 100644 index 0000000..16acdd9 --- /dev/null +++ b/client/src/common.js @@ -0,0 +1,103 @@ +import './style.scss' + +/** + * `BASE_URL` string from environment file without final slash (for more readable interpolated strings) + */ +export const BASE_URL = import.meta.env.BASE_URL.replace(/\/$/, '') + +const NotCached = Symbol('not cached') + +export const User = { + _user: NotCached, + + async getLogged() { + if (User._user === NotCached) { + console.log('Caching user data...') + + const res = await fetch(`${BASE_URL}/api/user`) + const user = await res.json() + + User._user = user + } + + return User._user + }, + async login(username, password) { + try { + const response = await fetch(`${BASE_URL}/api/login`, { + method: 'POST', + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username, password }), + }) + + if (!response.ok) { + return { error: await response.text() } + } + + // If successful redirect to homepage + location.href = `${BASE_URL}/` + } catch (e) { + return { error: e } + } + }, + async logout() { + await fetch(`${BASE_URL}/api/logout`, { method: 'POST' }) + location.href = `${BASE_URL}/` + }, +} + +export function createRoomEventStream(roomId) { + return new EventSource(`${BASE_URL}/api/room_events?id=${roomId}`) +} + +export const Database = { + async getSeats(roomId) { + const seatList = await (await fetch(`${BASE_URL}/api/room/seats?id=${roomId}`)).json() + const seats = {} + + seatList.forEach(seat => { + seats[seat.id] = seat + }) + + return seats + }, + async occupySeat(seatId) { + const response = await fetch(`${BASE_URL}/api/seat/occupy?id=${seatId}`, { + method: 'POST', + }) + + if (!response.ok) { + throw new Error(await response.text()) + } + + await response.json() + }, + async leaveSeat(seatId) { + const response = await fetch(`${BASE_URL}/api/seat/leave?id=${seatId}`, { + method: 'POST', + }) + + if (!response.ok) { + throw new Error(await response.text()) + } + + await response.json() + }, +} + +// Always append BASE_URL to all links in the page. +document.querySelectorAll('a').forEach($a => { + const url = $a.getAttribute('href') + let newUrl = BASE_URL + url + + if (import.meta.env.MODE === 'development') { + if (newUrl.endsWith('/')) newUrl += 'index' + + newUrl += '.html' + } + + $a.href = newUrl +}) diff --git a/client/src/components/gridlines.js b/client/src/components/grid-lines.js similarity index 97% rename from client/src/components/gridlines.js rename to client/src/components/grid-lines.js index e741c90..bb1ccd8 100644 --- a/client/src/components/gridlines.js +++ b/client/src/components/grid-lines.js @@ -47,5 +47,5 @@ export function createGridLineCanvas($roomGrid) { const render = () => renderGridLinesCanvas($canvas, [rows, cols]) window.addEventListener('resize', render) - render() + requestAnimationFrame(render) } diff --git a/client/src/components/nav-user.js b/client/src/components/nav-user.js new file mode 100644 index 0000000..1cae32f --- /dev/null +++ b/client/src/components/nav-user.js @@ -0,0 +1,40 @@ +import { User } from '../common.js' + +function elements(el) { + return { + elLoggedLabel: el.querySelector('.logged-label'), + elLoginLabel: el.querySelector('.login-label'), + elLogoutLabel: el.querySelector('.logout-label'), + elLogoutButton: el.querySelector('.logout-button'), + } +} + +function setup(el) { + const { elLogoutButton } = elements(el) + + elLogoutButton.addEventListener('click', () => User.logout()) +} + +function update(el, { user }) { + const { elLoggedLabel, elLoginLabel, elLogoutLabel } = elements(el) + + elLoginLabel.classList.toggle('hidden', user) + + const roleString = + user && user.permissions.length > 0 ? ` (${user.permissions.join(', ')})` : '' + + elLoggedLabel.innerText = user ? `@${user.id}${roleString}` : '' + + elLoggedLabel.classList.toggle('hidden', !user) + elLogoutLabel.classList.toggle('hidden', !user) +} + +export function createNavUser(el) { + setup(el) + + // Load current user + ;(async () => { + const user = await User.getLogged() + update(el, { user }) + })() +} diff --git a/client/src/components/seats-widget.js b/client/src/components/seats-widget.js index 4cd4cb1..2b3cbc7 100644 --- a/client/src/components/seats-widget.js +++ b/client/src/components/seats-widget.js @@ -1,8 +1,8 @@ -import { BASE_URL, createRoomEventStream, Database, getLoggedUser } from '../index.js' -import { addTooltipElementListener, resetTooltip, setTooltipText } from './tooltip.js' +import { BASE_URL, createRoomEventStream, Database, User } from '../common' +import { addTooltipElementListener, setTooltipText } from './tooltip' async function renderWidget(elSeatMap, seats) { - const user = await getLoggedUser() + const user = await User.getLogged() Object.values(seats).forEach(seat => { const { id, occupiedBy } = seat @@ -26,7 +26,7 @@ async function renderWidget(elSeatMap, seats) { } export async function createSeatWidget($roomGrid, roomId) { - const user = await getLoggedUser() + const user = await User.getLogged() const elSeats = [...$roomGrid.querySelectorAll('[data-seat-id]')] const elSeatMap = {} diff --git a/client/src/index.js b/client/src/index.js deleted file mode 100644 index 7850f75..0000000 --- a/client/src/index.js +++ /dev/null @@ -1,52 +0,0 @@ -import './style.scss' - -export const BASE_URL = import.meta.env.BASE_URL.replace(/\/$/, '') - -let USER = false -export async function getLoggedUser() { - if (USER === false) { - console.log('Caching user data...') - USER = await (await fetch(`${BASE_URL}/api/user`)).json() - } - - return USER -} - -export function createRoomEventStream(roomId) { - return new EventSource(`${BASE_URL}/api/room_events?id=${roomId}`) -} - -export const Database = { - async getSeats(roomId) { - const seatList = await (await fetch(`${BASE_URL}/api/room/seats?id=${roomId}`)).json() - const seats = {} - - seatList.forEach(seat => { - seats[seat.id] = seat - }) - - return seats - }, - async occupySeat(seatId) { - const response = await fetch(`${BASE_URL}/api/seat/occupy?id=${seatId}`, { - method: 'POST', - }) - - if (!response.ok) { - throw new Error(await response.text()) - } - - await response.json() - }, - async leaveSeat(seatId) { - const response = await fetch(`${BASE_URL}/api/seat/leave?id=${seatId}`, { - method: 'POST', - }) - - if (!response.ok) { - throw new Error(await response.text()) - } - - await response.json() - }, -} diff --git a/client/src/pages/index.js b/client/src/pages/index.js index 5e1a5d5..ba81c3f 100644 --- a/client/src/pages/index.js +++ b/client/src/pages/index.js @@ -1,48 +1,30 @@ -import { createGridLineCanvas } from '../components/gridlines.js' -import { BASE_URL, getLoggedUser } from '../index.js' -import { createSeatWidget } from '../components/seats-widget.js' -import { createClock } from '../components/clock.js' -import { attachTooltip } from '../components/tooltip.js' +import '../common' -const elClock = document.querySelector('#clock') +import { createGridLineCanvas } from '../components/grid-lines' +import { BASE_URL } from '../common' +import { createSeatWidget } from '../components/seats-widget' +import { createClock } from '../components/clock' +import { attachTooltip } from '../components/tooltip' +import { createNavUser } from '../components/nav-user.js' -const elLoggedLabel = document.querySelector('#logged-label') -const elLoginLabel = document.querySelector('#login-label') -const elLogoutLabel = document.querySelector('#logout-label') +const elClock = document.querySelector('#clock') -const elLogoutButton = document.querySelector('#logout-button') +const elNavUser = document.querySelector('#nav-user') const elRoomGrid = document.querySelector('.room-grid') -async function logout() { - await fetch(`${BASE_URL}/api/logout`, { method: 'POST' }) - location.href = `${BASE_URL}` -} - async function main() { const urlSearchParams = new URLSearchParams(window.location.search) const params = Object.fromEntries(urlSearchParams.entries()) console.log(params) - elLogoutButton.addEventListener('click', () => logout()) - - const user = await getLoggedUser() - - if (user) { - elLoginLabel.classList.add('hidden') - - elLoggedLabel.innerText = - '@' + user.id + (user.permissions.length > 0 ? ` (${user.permissions.join(', ')})` : '') - elLoggedLabel.classList.remove('hidden') - - elLogoutLabel.classList.remove('hidden') - } - // Widgets // createClock(elClock) createGridLineCanvas(elRoomGrid) createSeatWidget(elRoomGrid, 'aula-stud') + createNavUser(elNavUser) + // Use tooltips only on desktop if (matchMedia('(pointer: fine)').matches) { attachTooltip() diff --git a/client/src/pages/login.js b/client/src/pages/login.js index 2c5265f..eacedd6 100644 --- a/client/src/pages/login.js +++ b/client/src/pages/login.js @@ -1,4 +1,6 @@ -import { BASE_URL, getLoggedUser } from '../index.js' +import '../common' + +import { BASE_URL, User } from '../common' // // Page Element @@ -16,8 +18,7 @@ const elLoginButton = document.querySelector('#button-login') // let errorTimeoutHandle = null - -function displayErrorString(e) { +function displayFormErrorMessage(e) { if (errorTimeoutHandle) clearTimeout(errorTimeoutHandle) elErrorString.classList.toggle('hidden', false) elErrorString.innerText = e.toString() @@ -31,28 +32,10 @@ function displayErrorString(e) { // Handle Login Button // -async function login() { - try { - const response = await fetch(`${BASE_URL}/api/login`, { - method: 'POST', - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - username: elLoginUsernameInput.value, - password: elLoginPasswordInput.value, - }), - }) - - if (!response.ok) { - displayErrorString(await response.text()) - return - } - - location.href = `${BASE_URL}/` - } catch (e) { - displayErrorString(e) +async function onLoginButtonClick() { + const res = await User.login(elLoginUsernameInput.value, elLoginPasswordInput.value) + if (res.error) { + displayFormErrorMessage(res.error) } } @@ -61,16 +44,17 @@ async function login() { // async function main() { - const user = await getLoggedUser() - console.log(user) + const user = await User.getLogged() if (user) { location.href = `${BASE_URL}/` } - elLoginButton.addEventListener('click', () => login()) + elLoginButton.addEventListener('click', () => { + onLoginButtonClick() + }) elLoginPasswordInput.addEventListener('keydown', e => { - if (e.key === 'Enter') login() + if (e.key === 'Enter') onLoginButtonClick() }) elLoginUsernameInput.focus() diff --git a/client/src/pages/orario.js b/client/src/pages/orario.js new file mode 100644 index 0000000..59506f2 --- /dev/null +++ b/client/src/pages/orario.js @@ -0,0 +1 @@ +import '../common' diff --git a/client/vite.config.js b/client/vite.config.js index a15c499..6a93074 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -2,6 +2,7 @@ import { defineConfig, loadEnv } from 'vite' import { resolve } from 'path' export default defineConfig(({ mode }) => { + // Load environment variables with no prefixes process.env = { ...process.env, ...loadEnv(mode, process.cwd(), '') } console.log(`BASE_URL = "${process.env.BASE_URL}"`) @@ -12,7 +13,7 @@ export default defineConfig(({ mode }) => { rollupOptions: { input: { main: resolve(__dirname, 'index.html'), - login: resolve(__dirname, 'login.html'), + login: resolve(__dirname, 'login/index.html'), }, }, },