chore(release): 0.2.14
[risinglegends.git] / src / client / htmx.ts
1 import { io } from 'socket.io-client';
2 import { TimeManager } from '../shared/time';
3
4 function $<T>(selector: string, root: any = document): T {
5   return root.querySelector(selector) as T;
6 }
7
8 function $$<T>(selector: string, root: any = document): T[] {
9   return Array.from(root.querySelectorAll(selector)) as T[];
10 }
11
12 export function authToken(): string {
13   const token = localStorage.getItem('authToken');
14   return token || '';
15 }
16
17 function setTimeGradient() {
18   const gradientName = time.gradientName();
19   const $body = $<HTMLElement>('body');
20   $body.classList.value = gradientName; 
21
22   $body.classList.add(time.getTimePeriod());
23
24   let icon: string;
25   if(time.get24Hour() >= 5 && time.get24Hour() < 9) {
26     icon = 'morning';
27   }
28   else if(time.get24Hour() >= 9 && time.get24Hour() < 17) {
29     icon = 'afternoon';
30   }
31   else if(time.get24Hour() >= 17 && time.get24Hour() < 20) {
32     icon = 'evening'
33   }
34   else {
35     icon = 'night';
36   }
37   
38   $<HTMLElement>('#time-of-day').innerHTML = `<img src="/assets/img/icons/time-of-day/${icon}.png"> ${time.getHour()}${time.getAmPm()}`;
39 }
40
41 function renderChatMessage(msg: string) {
42   $<HTMLElement>('#chat-messages').innerHTML += msg;
43   $<HTMLElement>('#chat-messages').scrollTop = $<HTMLElement>('#chat-messages').scrollHeight;
44 }
45
46 const time = new TimeManager();
47 const socket = io({
48   extraHeaders: {
49     'x-authtoken': authToken()
50   }
51 });
52 setTimeGradient();
53 setInterval(setTimeGradient, 60 * 1000);
54
55 socket.on('connect', () => {
56   console.log(`Connected: ${socket.id}`);
57 });
58
59 socket.on('authToken', (authToken: string) => {
60   console.log(`recv auth token ${authToken}`);
61   if(localStorage.getItem('authToken') !== authToken) {
62     localStorage.setItem('authToken', authToken);
63     window.location.reload();
64   }
65 });
66
67 socket.on('status', html => $<HTMLElement>('#server-stats').innerHTML = html);
68
69 socket.on('chat', renderChatMessage);
70 socket.on('ready', bootstrap);
71
72
73 $$<HTMLElement>('nav a').forEach(el => {
74   el.addEventListener('click', e => {
75     const el = e.target as HTMLElement;
76
77     $$<HTMLElement>('a', el.closest('nav')).forEach(el => {
78       el.classList.remove('active');
79     })
80
81     el.classList.add('active');
82
83     const targetEl = $<HTMLElement>(el.getAttribute('hx-target')!.toString());
84
85     Array.from(targetEl.parentElement!.children).forEach(el => {
86       el.classList.remove('active');
87     });
88     targetEl.classList.add('active');
89   });
90 });
91
92 $<HTMLElement>('body').addEventListener('click', e => {
93   const target = e.target as HTMLElement;
94
95   if(target!.parentElement!.classList.contains('filter')) {
96     // ok this is afunky filter object!
97     const children = Array.from(target!.parentElement!.children);
98     children.forEach(el => el.classList.remove('active'));
99     target.classList.add('active');
100
101     const dataFilter = target.getAttribute('data-filter');
102
103     const targetPane = $<HTMLElement>(`.filter-result[data-filter="${dataFilter}"]`, target.closest('.filter-container'));
104
105     if(targetPane) {
106       Array.from(targetPane!.parentElement!.children).forEach(el => {
107         if(el.getAttribute('data-filter') === dataFilter) {
108           el.classList.remove('hidden');
109           el.classList.add('active');
110         }
111         else {
112           el.classList.add('hidden');
113           el.classList.remove('active');
114         }
115       })
116     }
117   }
118
119   if(target.getAttribute('formmethod') === 'dialog' && target.getAttribute('value') === 'cancel') {
120     target.closest('dialog')?.close();
121   }
122 });
123
124 const modalMutations = new MutationObserver((list, observer) => {
125   const states = {
126     modal: false,
127     alert: false
128   };
129
130   list.forEach(mutation => {
131     switch(((mutation.target) as HTMLElement).id) {
132       case 'modal-wrapper':
133         states.modal = true;
134         break;
135       case 'alerts':
136         states.alert = true;
137         break;
138
139     }
140   });
141
142   if(states.modal) {
143     if($<HTMLElement>('#modal-wrapper').children.length) {
144       $$<HTMLDialogElement>('#modal-wrapper dialog').forEach(el => {
145         if(!el.open) {
146           el.showModal();
147         }
148       })
149     }
150   }
151
152   if(states.alert) {
153     if($<HTMLElement>('#alerts').children.length) {
154       $$<HTMLElement>('#alerts .alert').forEach(el => {
155         if(!el.getAttribute('data-dismiss-at')) {
156           const dismiss = Date.now() + 3000;
157           el.setAttribute('data-dismiss-at', dismiss.toString());
158           setTimeout(() => { el.remove(); }, 3000);
159         }
160       });
161     }
162   }
163
164 });
165
166 modalMutations.observe($<HTMLElement>('#modal-wrapper'), { childList: true });
167
168 modalMutations.observe($<HTMLElement>('#alerts'), { childList: true });
169
170
171 function bootstrap() {
172   console.log('Server connection verified');
173   // target the explore tab
174   $$<HTMLElement>('nav a')[3].click();
175 }
176 document.body.addEventListener('htmx:configRequest', function(evt) {
177   //@ts-ignore
178   evt.detail.headers['x-authtoken'] = authToken();
179 });
180 document.body.addEventListener('htmx:load', function(evt) {
181   $$<HTMLElement>('.disabled[data-block]').forEach(el => {
182     setTimeout(() => { el.removeAttribute('disabled'); }, 3000);
183   });
184 });
185 document.body.addEventListener('htmx:beforeSwap', function(e) {
186   const el = e.target as HTMLElement;
187   if(el.id === 'chat-form') {
188     $<HTMLInputElement>('#message').value = '';
189   }
190   //@ts-ignore
191   else if(e.detail.serverResponse === 'logout') {
192     localStorage.removeItem('authToken');
193     window.location.reload();
194   }
195 });
196