@ -1,12 +0,0 @@ | |||
document.addEventListener('DOMContentLoaded', () => { | |||
document.querySelectorAll('.copyable-text').forEach(el => { | |||
const contentEl = el.querySelector('.content'); | |||
contentEl.addEventListener('click', () => { | |||
window.getSelection().selectAllChildren(contentEl); | |||
}); | |||
el.querySelector('.copy-button').addEventListener('click', () => { | |||
window.getSelection().selectAllChildren(contentEl); | |||
document.execCommand('copy'); | |||
}); | |||
}); | |||
}); |
@ -1,17 +0,0 @@ | |||
document.addEventListener('DOMContentLoaded', () => { | |||
const menuButton = document.getElementById('menu-button'); | |||
const mainMenu = document.getElementById('main-menu'); | |||
menuButton.addEventListener('click', (e) => { | |||
e.stopPropagation(); | |||
mainMenu.classList.toggle('open'); | |||
}); | |||
mainMenu.addEventListener('click', (e) => { | |||
e.stopPropagation(); | |||
}); | |||
document.addEventListener('click', () => { | |||
mainMenu.classList.remove('open'); | |||
}); | |||
}); |
@ -1,30 +0,0 @@ | |||
document.addEventListener('DOMContentLoaded', () => { | |||
window.updateTooltips = () => { | |||
console.debug('Update tooltips'); | |||
const elements = document.querySelectorAll('.tip, .dropdown'); | |||
// Calculate max potential displacement | |||
let max = 0; | |||
elements.forEach(el => { | |||
const box = el.getBoundingClientRect(); | |||
if (max < box.height) max = box.height; | |||
}); | |||
// Prevent displacement | |||
elements.forEach(el => { | |||
if (!el.tooltipSetup) { | |||
el.tooltipSetup = true; | |||
const box = el.getBoundingClientRect(); | |||
if (box.bottom >= document.body.clientHeight - (max + 32)) { | |||
el.classList.add('top'); | |||
} | |||
} | |||
}); | |||
}; | |||
window.addEventListener('popstate', () => { | |||
updateTooltips(); | |||
}); | |||
window.requestAnimationFrame(() => { | |||
updateTooltips(); | |||
}); | |||
}); |
@ -0,0 +1,39 @@ | |||
export default class PersistentWebsocket { | |||
private webSocket?: WebSocket; | |||
public constructor( | |||
protected readonly url: string, | |||
private readonly handler: MessageHandler, | |||
protected readonly reconnectOnClose: boolean = true, | |||
) { | |||
} | |||
public run() { | |||
this.webSocket = new WebSocket(this.url); | |||
this.webSocket.addEventListener('open', (e) => { | |||
console.debug('Websocket connected'); | |||
}); | |||
this.webSocket.addEventListener('message', (e) => { | |||
this.handler(this.webSocket!, e); | |||
}); | |||
this.webSocket.addEventListener('error', (e) => { | |||
console.error('Websocket error', e); | |||
}); | |||
this.webSocket.addEventListener('close', (e) => { | |||
this.webSocket = undefined; | |||
console.debug('Websocket closed', e.code, e.reason); | |||
if (this.reconnectOnClose) { | |||
setTimeout(() => this.run(), 1000); | |||
} | |||
}); | |||
} | |||
public send(data: string) { | |||
if (!this.webSocket) throw new Error('WebSocket not connected'); | |||
this.webSocket.send(data); | |||
} | |||
} | |||
export type MessageHandler = (webSocket: WebSocket, e: MessageEvent) => void; |
@ -0,0 +1,15 @@ | |||
document.addEventListener('DOMContentLoaded', () => { | |||
document.querySelectorAll('.copyable-text').forEach(el => { | |||
const contentEl = el.querySelector('.content'); | |||
const selection = window.getSelection(); | |||
if (contentEl && selection) { | |||
contentEl.addEventListener('click', () => { | |||
selection.selectAllChildren(contentEl); | |||
}); | |||
el.querySelector('.copy-button')?.addEventListener('click', () => { | |||
selection.selectAllChildren(contentEl); | |||
document.execCommand('copy'); | |||
}); | |||
} | |||
}); | |||
}); |
@ -0,0 +1,21 @@ | |||
document.addEventListener('DOMContentLoaded', () => { | |||
const menuButton = document.getElementById('menu-button'); | |||
const mainMenu = document.getElementById('main-menu'); | |||
if (menuButton) { | |||
menuButton.addEventListener('click', (e) => { | |||
e.stopPropagation(); | |||
mainMenu?.classList.toggle('open'); | |||
}); | |||
} | |||
if (mainMenu) { | |||
mainMenu.addEventListener('click', (e) => { | |||
e.stopPropagation(); | |||
}); | |||
document.addEventListener('click', () => { | |||
mainMenu.classList.remove('open'); | |||
}); | |||
} | |||
}); |
@ -1,21 +1,26 @@ | |||
import feather from "feather-icons"; | |||
document.addEventListener('DOMContentLoaded', () => { | |||
const messageTypeToIcon = { | |||
const messageTypeToIcon: { [p: string]: string } = { | |||
info: 'info', | |||
success: 'check', | |||
warning: 'alert-triangle', | |||
error: 'x-circle', | |||
question: 'help-circle', | |||
}; | |||
document.querySelectorAll('.message').forEach(el => { | |||
const type = el.dataset['type']; | |||
document.querySelectorAll<HTMLElement>('.message').forEach(el => { | |||
const icon = el.querySelector('.icon'); | |||
const type = el.dataset['type']; | |||
if (!icon || !type) return; | |||
if (!messageTypeToIcon[type]) throw new Error(`No icon for type ${type}`); | |||
const svgContainer = document.createElement('div'); | |||
svgContainer.innerHTML = feather.icons[messageTypeToIcon[type]].toSvg(); | |||
el.insertBefore(svgContainer.firstChild, icon); | |||
if (svgContainer.firstChild) el.insertBefore(svgContainer.firstChild, icon); | |||
icon.remove(); | |||
}); | |||
feather.replace(); | |||
}); | |||
}); |
@ -0,0 +1,31 @@ | |||
export function updateTooltips() { | |||
console.debug('Updating tooltips'); | |||
const elements = document.querySelectorAll<HTMLElement>('.tip, .dropdown'); | |||
// Calculate max potential displacement | |||
let max = 0; | |||
elements.forEach(el => { | |||
const box = el.getBoundingClientRect(); | |||
if (max < box.height) max = box.height; | |||
}); | |||
// Prevent displacement | |||
elements.forEach(el => { | |||
if (!el.dataset.tooltipSetup) { | |||
el.dataset.tooltipSetup = 'true'; | |||
const box = el.getBoundingClientRect(); | |||
if (box.bottom >= document.body.clientHeight - (max + 32)) { | |||
el.classList.add('top'); | |||
} | |||
} | |||
}); | |||
} | |||
document.addEventListener('DOMContentLoaded', () => { | |||
window.addEventListener('popstate', () => { | |||
updateTooltips(); | |||
}); | |||
window.requestAnimationFrame(() => { | |||
updateTooltips(); | |||
}); | |||
}); |
@ -0,0 +1,18 @@ | |||
{ | |||
"extends": "./tsconfig.json", | |||
"compilerOptions": { | |||
"outDir": "public/js", | |||
"target": "ES6", | |||
"strict": true, | |||
"lib": [ | |||
"es2020", | |||
"DOM" | |||
], | |||
"typeRoots": [ | |||
"./node_modules/@types" | |||
] | |||
}, | |||
"include": [ | |||
"assets/ts/**/*" | |||
] | |||
} |