1 import { io } from 'socket.io-client';
2 import { TimeManager } from '../shared/time';
3 import * as CONSTANT from '../shared/constants';
5 function $<T>(selector: string, root: any = document): T {
6 return root.querySelector(selector) as T;
9 function $$<T>(selector: string, root: any = document): T[] {
10 return Array.from(root.querySelectorAll(selector)) as T[];
13 export function authToken(): string {
14 const token = localStorage.getItem('authToken');
18 function setTimeGradient() {
19 const gradientName = time.gradientName();
20 const $body = $<HTMLElement>('body');
21 $body.classList.value = gradientName;
23 $body.classList.add(time.getTimePeriod());
26 if(time.get24Hour() >= 5 && time.get24Hour() < 9) {
29 else if(time.get24Hour() >= 9 && time.get24Hour() < 17) {
32 else if(time.get24Hour() >= 17 && time.get24Hour() < 20) {
39 $<HTMLElement>('#time-of-day').innerHTML = `<img src="/assets/img/icons/time-of-day/${icon}.png"> ${time.getHour()}${time.getAmPm()}`;
42 function renderChatMessage(msg: string) {
43 $<HTMLElement>('#chat-messages').insertAdjacentHTML("beforeend", msg);
44 $<HTMLElement>('#chat-messages').scrollTop = $<HTMLElement>('#chat-messages').scrollHeight;
47 const time = new TimeManager();
50 'x-authtoken': authToken()
54 setInterval(setTimeGradient, 60 * 1000);
56 socket.on('connect', () => {
57 console.log(`Connected: ${socket.id}`);
60 socket.on('authToken', (authToken: string) => {
61 console.log(`recv auth token ${authToken}`);
62 if(localStorage.getItem('authToken') !== authToken) {
63 localStorage.setItem('authToken', authToken);
64 window.location.reload();
68 socket.on('status', html => $<HTMLElement>('#server-stats').innerHTML = html);
70 socket.on('chat', renderChatMessage);
71 socket.on('ready', bootstrap);
74 $$<HTMLElement>('nav a').forEach(el => {
75 el.addEventListener('click', e => {
76 const el = e.target as HTMLElement;
78 $$<HTMLElement>('a', el.closest('nav')).forEach(el => {
79 el.classList.remove('active');
82 el.classList.add('active');
84 const targetEl = $<HTMLElement>(el.getAttribute('hx-target')!.toString());
86 Array.from(targetEl.parentElement!.children).forEach(el => {
87 el.classList.remove('active');
89 targetEl.classList.add('active');
93 $<HTMLElement>('body').addEventListener('click', e => {
94 const target = e.target as HTMLElement;
96 if(target!.parentElement!.classList.contains('filter')) {
97 // ok this is afunky filter object!
98 const children = Array.from(target!.parentElement!.children);
99 children.forEach(el => el.classList.remove('active'));
100 target.classList.add('active');
102 const dataFilter = target.getAttribute('data-filter');
104 const targetPane = $<HTMLElement>(`.filter-result[data-filter="${dataFilter}"]`, target.closest('.filter-container'));
107 Array.from(targetPane!.parentElement!.children).forEach(el => {
108 if(el.getAttribute('data-filter') === dataFilter) {
109 el.classList.remove('hidden');
110 el.classList.add('active');
113 el.classList.add('hidden');
114 el.classList.remove('active');
120 if(target.getAttribute('formmethod') === 'dialog' && target.getAttribute('value') === 'cancel') {
121 target.closest('dialog')?.close();
122 // sometimes dialog buttons have a "nav direct"
123 const attr = target.getAttribute('nav-trigger');
125 const [selector, click_num] = attr.split('|');
126 $$<HTMLElement>(selector)[click_num].click();
131 const modalMutations = new MutationObserver((list, observer) => {
137 list.forEach(mutation => {
138 switch(((mutation.target) as HTMLElement).id) {
139 case 'modal-wrapper':
150 if($<HTMLElement>('#modal-wrapper').children.length) {
151 $$<HTMLDialogElement>('#modal-wrapper dialog').forEach(el => {
160 if($<HTMLElement>('#alerts').children.length) {
161 $$<HTMLElement>('#alerts .alert').forEach(el => {
162 if(!el.getAttribute('data-dismiss-at')) {
163 const dismiss = Date.now() + CONSTANT.ALERT_DISPLAY_LENGTH;
164 el.setAttribute('data-dismiss-at', dismiss.toString());
165 setTimeout(() => { el.remove(); }, CONSTANT.ALERT_DISPLAY_LENGTH);
173 modalMutations.observe($<HTMLElement>('#modal-wrapper'), { childList: true });
175 modalMutations.observe($<HTMLElement>('#alerts'), { childList: true });
178 function bootstrap() {
179 console.log('Server connection verified');
180 // target the explore tab
181 $$<HTMLElement>('nav a')[3].click();
183 document.body.addEventListener('htmx:configRequest', function(evt) {
185 evt.detail.headers['x-authtoken'] = authToken();
187 document.body.addEventListener('htmx:load', function(evt) {
188 $$<HTMLElement>('.disabled[data-block]').forEach(el => {
189 const blockTime = parseInt(el.getAttribute('data-block') || '0');
190 if(blockTime > Date.now()) {
191 const timeout = blockTime - Date.now();
192 setTimeout(() => { el.removeAttribute('disabled'); }, timeout);
195 el.removeAttribute('disabled');
199 document.body.addEventListener('htmx:beforeSwap', function(e) {
200 const el = e.target as HTMLElement;
201 if(el.id === 'chat-form') {
202 $<HTMLInputElement>('#message').value = '';
205 else if(e.detail.serverResponse === 'logout') {
206 localStorage.removeItem('authToken');
207 window.location.reload();