function isEmptyObject(obj) { return Object.entries(obj).length === 0 && obj.constructor === Object } function numberWrapAround(num, max) { return num < 0 ? max + (num % max) : (num % max) } function hsluv(hue, saturation, lightness, alpha=1.0) { hue = numberWrapAround(hue, 360) let rgb = py.callSync("hsluv", [hue, saturation, lightness]) return Qt.rgba(rgb[0], rgb[1], rgb[2], alpha) } function hueFrom(string) { // Calculate and return a unique hue between 0 and 360 for the string let hue = 0 for (let i = 0; i < string.length; i++) { hue += string.charCodeAt(i) * 99 } return hue % 360 } function nameColor(name) { return hsluv( hueFrom(name), theme.controls.displayName.saturation, theme.controls.displayName.lightness, ) } function coloredNameHtml(name, userId, displayText=null, disambiguate=false) { // substring: remove leading @ return "" + escapeHtml(displayText || name || userId) + "" } function escapeHtml(string) { // Replace special HTML characters by encoded alternatives return string.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace('"', """) .replace("'", "'") } function processedEventText(ev) { if (ev.event_type == "RoomMessageEmote") { return "" + coloredNameHtml(ev.sender_name, ev.sender_id) + " " + ev.content + "" } if (ev.event_type.startsWith("RoomMessage")) { return ev.content } let text = qsTr(ev.content).arg( coloredNameHtml(ev.sender_name, ev.sender_id) ) if (text.includes("%2") && ev.target_id) { text = text.arg(coloredNameHtml(ev.target_name, ev.target_id)) } return text } function filterMatches(filter, text) { let filter_lower = filter.toLowerCase() if (filter_lower == filter) { // Consider case only if filter isn't all lowercase (smart case) filter = filter_lower text = text.toLowerCase() } for (let word of filter.split(" ")) { if (word && ! text.includes(word)) { return false } } return true } function filterModelSource(source, filter_text, property="filter_string") { if (! filter_text) return source let results = [] for (let i = 0; i < source.length; i++) { if (filterMatches(filter_text, source[i][property])) { results.push(source[i]) } } return results } function thumbnailParametersFor(width, height) { // https://matrix.org/docs/spec/client_server/latest#thumbnails if (width > 640 || height > 480) return {width: 800, height: 600, fillMode: Image.PreserveAspectFit} if (width > 320 || height > 240) return {width: 640, height: 480, fillMode: Image.PreserveAspectFit} if (width > 96 || height > 96) return {width: 320, height: 240, fillMode: Image.PreserveAspectFit} if (width > 32 || height > 32) return {width: 96, height: 96, fillMode: Image.PreserveAspectCrop} return {width: 32, height: 32, fillMode: Image.PreserveAspectCrop} } function minutesBetween(date1, date2) { return Math.round((((date2 - date1) % 86400000) % 3600000) / 60000) } function dateIsDay(date, dayDate) { return date.getDate() == dayDate.getDate() && date.getMonth() == dayDate.getMonth() && date.getFullYear() == dayDate.getFullYear() } function dateIsToday(date) { return dateIsDay(date, new Date()) } function dateIsYesterday(date) { const yesterday = new Date() yesterday.setDate(yesterday.getDate() - 1) return dateIsDay(date, yesterday) } function formatTime(time, seconds=true) { return Qt.formatTime( time, Qt.locale().timeFormat( seconds ? Locale.LongFormat : Locale.NarrowFormat ).replace(/\./g, ":").replace(/ t$/, "") // en_DK.UTF-8 locale wrongfully gives "." separators; // remove the timezone at the end ) } function getItem(array, mainKey, value) { for (let i = 0; i < array.length; i++) { if (array[i][mainKey] === value) { return array[i] } } return undefined } function smartVerticalFlick(flickable, baseVelocity, fastMultiply=4) { if (! flickable.interactive && flickable.enableFlicking) return baseVelocity = -baseVelocity let vel = -flickable.verticalVelocity let fast = (baseVelocity < 0 && vel < baseVelocity / 2) || (baseVelocity > 0 && vel > baseVelocity / 2) flickable.flick(0, baseVelocity * (fast ? fastMultiply : 1)) } function flickToTop(flickable) { if (! flickable.interactive) return if (flickable.visibleArea.yPosition < 0) return flickable.contentY -= flickable.contentHeight flickable.returnToBounds() flickable.flick(0, -100) // Force the delegates to load } function flickToBottom(flickable) { if (! flickable.interactive) return if (flickable.visibleArea.yPosition < 0) return flickable.contentY = flickTarget.contentHeight - flickTarget.height flickable.returnToBounds() flickable.flick(0, 100) } function copyToClipboard(text) { window.pseudoClipboard.clear() window.pseudoClipboard.insert(0, text) window.pseudoClipboard.selectAll() window.pseudoClipboard.copy() }