// chat-client.js // Copyright (C) 2024 DTP Technologies, LLC // All Rights Reserved 'use strict'; const DTP_COMPONENT_NAME = 'DtpChatApp'; window.dtp = window.dtp || { }; import DtpApp from 'lib/dtp-app.js'; import QRCode from 'qrcode'; import Cropper from 'cropperjs'; export class ChatApp extends DtpApp { constructor (user) { super(DTP_COMPONENT_NAME, user); this.loadSettings(); this.log.info('DTP app client online'); window.addEventListener('dtp-load', this.onDtpLoad.bind(this)); } async onDtpLoad ( ) { this.log.info('dtp-load event received. Connecting to platform.'); await this.connect(); } async confirmNavigation (event) { const target = event.currentTarget || event.target; event.preventDefault(); event.stopPropagation(); const href = target.getAttribute('href'); const hrefTarget = target.getAttribute('target'); const text = target.textContent; const whitelist = [ 'digitaltelepresence.com', 'www.digitaltelepresence.com', 'chat.digitaltelepresence.com', 'sites.digitaltelepresence.com', ]; try { const url = new URL(href); if (!whitelist.includes(url.hostname)) { await UIkit.modal.confirm(`

You are navigating to ${href}, a link or button that was displayed as:

${text}

Please only open links to destinations you trust and want to visit.
`); } window.open(href, hrefTarget); } catch (error) { this.log.info('confirmNavigation', 'navigation canceled', { error }); } return true; } async generateOtpQR (canvas, keyURI) { QRCode.toCanvas(canvas, keyURI); } async generateQRCanvas (canvas, uri) { this.log.info('generateQRCanvas', 'creating QR code canvas', { uri }); QRCode.toCanvas(canvas, uri, { width: 256 }); } async closeAllDropdowns ( ) { const dropdowns = document.querySelectorAll('.uk-dropdown.uk-open'); for (const dropdown of dropdowns) { this.log.info('closeAllDropdowns', 'closing dropdown', { dropdown }); UIkit.dropdown(dropdown).hide(false); } } async muteChatUser (event) { const target = (event.currentTarget || event.target); event.preventDefault(); event.stopPropagation(); this.closeAllDropdowns(); const messageId = target.getAttribute('data-message-id'); const userId = target.getAttribute('data-user-id'); const username = target.getAttribute('data-username'); try { await UIkit.modal.confirm(`Are you sure you want to mute ${username}?`); } catch (error) { // canceled or error return; } this.log.info('muteChatUser', 'muting chat user', { messageId, userId, username }); this.mutedUsers.push({ userId, username }); window.localStorage.mutedUsers = JSON.stringify(this.mutedUsers); document.querySelectorAll(`.chat-message[data-author-id="${userId}"]`).forEach((message) => { message.parentElement.removeChild(message); }); } async unmuteChatUser (event) { const target = (event.currentTarget || event.target); event.preventDefault(); event.stopPropagation(); const userId = target.getAttribute('data-user-id'); const username = target.getAttribute('data-username'); this.log.info('muteChatUser', 'muting chat user', { userId, username }); this.mutedUsers = this.mutedUsers.filter((block) => block.userId !== userId); window.localStorage.mutedUsers = JSON.stringify(this.mutedUsers); const entry = document.querySelector(`.chat-muted-user[data-user-id="${userId}"]`); if (!entry) { return; } entry.parentElement.removeChild(entry); } async filterChatView ( ) { if (!this.mutedUsers || (this.mutedUsers.length === 0)) { return; } this.mutedUsers.forEach((block) => { document.querySelectorAll(`.chat-message[data-author-id="${block.userId}"]`).forEach((message) => { message.parentElement.removeChild(message); }); }); } async initSettingsView ( ) { this.log.info('initSettingsView', 'settings', { settings: this.settings }); const mutedUserList = document.querySelector('ul#muted-user-list'); for (const block of this.mutedUsers) { const li = document.createElement(`li`); li.setAttribute('data-user-id', block.userId); li.classList.add('chat-muted-user'); mutedUserList.appendChild(li); const grid = document.createElement('div'); grid.setAttribute('uk-grid', ''); grid.classList.add('uk-grid-small'); grid.classList.add('uk-flex-middle'); li.appendChild(grid); let column = document.createElement('div'); column.classList.add('uk-width-expand'); column.textContent = block.username; grid.appendChild(column); column = document.createElement('div'); column.classList.add('uk-width-auto'); grid.appendChild(column); const button = document.createElement('button'); button.setAttribute('type', 'button'); button.setAttribute('title', `Remove ${block.username} from your mute list`); button.setAttribute('data-user-id', block.userId); button.setAttribute('data-username', block.username); button.setAttribute('onclick', "return dtp.app.unmuteChatUser(event);"); button.classList.add('uk-button'); button.classList.add('uk-button-default'); button.classList.add('uk-button-small'); button.classList.add('uk-border-rounded'); button.textContent = 'Unmute'; column.appendChild(button); } } loadSettings ( ) { this.settings = { tutorials: { } }; if (window.localStorage) { if (window.localStorage.settings) { this.settings = JSON.parse(window.localStorage.settings); } else { this.saveSettings(); } this.mutedUsers = window.localStorage.mutedUsers ? JSON.parse(window.localStorage.mutedUsers) : [ ]; this.filterChatView(); } this.settings.tutorials = this.settings.tutorials || { }; } saveSettings ( ) { if (!window.localStorage) { return; } window.localStorage.settings = JSON.stringify(this.settings); } async selectImageFile (event) { event.preventDefault(); const imageId = event.target.getAttribute('data-image-id'); //z read the cropper options from the element on the page let cropperOptions = event.target.getAttribute('data-cropper-options'); if (cropperOptions) { cropperOptions = JSON.parse(cropperOptions); } this.log.debug('selectImageFile', 'cropper options', { cropperOptions }); //z remove when done const fileSelectContainerId = event.target.getAttribute('data-file-select-container'); if (!fileSelectContainerId) { UIkit.modal.alert('Missing file select container element ID information'); return; } const fileSelectContainer = document.getElementById(fileSelectContainerId); if (!fileSelectContainer) { UIkit.modal.alert('Missing file select element'); return; } const fileSelect = fileSelectContainer.querySelector('input[type="file"]'); if (!fileSelect.files || (fileSelect.files.length === 0)) { return; } const selectedFile = fileSelect.files[0]; if (!selectedFile) { return; } this.log.debug('selectImageFile', 'thumbnail file select', { event, selectedFile }); const filter = /^(image\/jpg|image\/jpeg|image\/png)$/i; if (!filter.test(selectedFile.type)) { UIkit.modal.alert(`Unsupported image file type selected: ${selectedFile.type}`); return; } const fileSizeId = event.target.getAttribute('data-file-size-element'); const FILE_MAX_SIZE = parseInt(fileSelect.getAttribute('data-file-max-size'), 10); const fileSize = document.getElementById(fileSizeId); fileSize.textContent = numeral(selectedFile.size).format('0,0.0b'); if (selectedFile.size > (FILE_MAX_SIZE)) { UIkit.modal.alert(`File is too large: ${fileSize.textContent}. Custom thumbnail images may be up to ${numeral(FILE_MAX_SIZE).format('0,0.00b')} in size.`); return; } // const IMAGE_WIDTH = parseInt(event.target.getAttribute('data-image-w')); // const IMAGE_HEIGHT = parseInt(event.target.getAttribute('data-image-h')); const reader = new FileReader(); reader.onload = (e) => { const img = document.getElementById(imageId); img.onload = (e) => { console.log('image loaded', e, img.naturalWidth, img.naturalHeight); // if (img.naturalWidth !== IMAGE_WIDTH || img.naturalHeight !== IMAGE_HEIGHT) { // UIkit.modal.alert(`This image must be ${IMAGE_WIDTH}x${IMAGE_HEIGHT}`); // img.setAttribute('hidden', ''); // img.src = ''; // return; // } fileSelectContainer.querySelector('#file-name').textContent = selectedFile.name; fileSelectContainer.querySelector('#file-modified').textContent = dayjs(selectedFile.lastModifiedDate).fromNow(); fileSelectContainer.querySelector('#image-resolution-w').textContent = img.naturalWidth.toString(); fileSelectContainer.querySelector('#image-resolution-h').textContent = img.naturalHeight.toString(); fileSelectContainer.querySelector('#file-select').setAttribute('hidden', true); fileSelectContainer.querySelector('#file-info').removeAttribute('hidden'); fileSelectContainer.querySelector('#file-save-btn').removeAttribute('hidden'); }; // set the image as the "src" of the in the DOM. img.src = e.target.result; //z create cropper and set options here this.createImageCropper(img, cropperOptions); }; // read in the file, which will trigger everything else in the event handler above. reader.readAsDataURL(selectedFile); } async createImageCropper (img, options) { // https://github.com/fengyuanchen/cropperjs/blob/main/README.md#options options = Object.assign({ aspectRatio: 1, viewMode: 1, // restrict the crop box not to exceed the size of the canvas dragMode: 'move', autoCropArea: 0.85, restore: false, guides: false, center: false, highlight: false, cropBoxMovable: true, cropBoxResizable: true, toggleDragModeOnDblclick: false, modal: true, }, options); this.log.info("createImageCropper", "Creating image cropper", { img }); this.cropper = new Cropper(img, options); } }