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