From 2cb64c53468137da63e747081af13d2bf75aee34 Mon Sep 17 00:00:00 2001 From: miruka Date: Tue, 17 Dec 2019 17:59:53 -0400 Subject: [PATCH] Turn utils.js into a Utils.qml QtObject When a .js module is used, every single file that imports it creates its own duplicated environment in memory for that module. Instead, use a simple QtObject with all the functions, and declare it in Window.qml so that it is available to all children everywhere in the app. --- src/python/theme_parser.py | 7 +- src/qml/Base/HAvatar.qml | 9 +- src/qml/Base/HBox.qml | 5 +- src/qml/Base/HCheckBox.qml | 1 - src/qml/Base/HDrawer.qml | 1 - src/qml/Base/HImage.qml | 3 +- src/qml/Base/HSelectableLabel.qml | 1 - src/qml/Base/HSelectableLabelContainer.qml | 1 - src/qml/Base/HTile.qml | 1 - src/qml/Base/MediaPlayer/OSD.qml | 13 +- src/qml/Chat/Banners/InviteBanner.qml | 3 +- src/qml/Chat/Banners/LeftBanner.qml | 3 +- src/qml/Chat/Banners/UnknownDevicesBanner.qml | 1 - src/qml/Chat/Chat.qml | 5 +- src/qml/Chat/ChatPage.qml | 1 - src/qml/Chat/Composer.qml | 5 +- src/qml/Chat/FileTransfer/Transfer.qml | 3 +- src/qml/Chat/RoomPane/MemberDelegate.qml | 3 +- src/qml/Chat/RoomPane/MemberView.qml | 5 +- src/qml/Chat/RoomPane/SettingsView.qml | 1 - src/qml/Chat/Timeline/EventAudio.qml | 1 - src/qml/Chat/Timeline/EventContent.qml | 11 +- src/qml/Chat/Timeline/EventDelegate.qml | 5 +- src/qml/Chat/Timeline/EventFile.qml | 3 +- src/qml/Chat/Timeline/EventImage.qml | 7 +- src/qml/Chat/Timeline/EventList.qml | 5 +- src/qml/Chat/Timeline/EventMediaLoader.qml | 3 +- src/qml/Chat/Timeline/EventVideo.qml | 1 - src/qml/DebugConsole.qml | 5 +- src/qml/Dialogs/SendFilePicker.qml | 3 +- src/qml/MainPane/AccountDelegate.qml | 3 +- src/qml/MainPane/AccountRoomList.qml | 7 +- src/qml/MainPane/MainPane.qml | 1 - src/qml/MainPane/RoomDelegate.qml | 17 +- .../Pages/AccountSettings/AccountSettings.qml | 5 +- .../AccountSettings/ImportExportKeys.qml | 5 +- src/qml/Pages/AccountSettings/Profile.qml | 5 +- src/qml/Pages/AddChat/AddChat.qml | 3 +- src/qml/Pages/AddChat/CreateRoom.qml | 1 - src/qml/Pages/AddChat/DirectChat.qml | 1 - src/qml/Pages/AddChat/JoinRoom.qml | 1 - src/qml/Popups/BoxPopup.qml | 1 - src/qml/Popups/SignOutPopup.qml | 3 +- src/qml/Shortcuts.qml | 25 +- src/qml/UI.qml | 1 - src/qml/Utils.qml | 323 ++++++++++++++++++ src/qml/Window.qml | 5 +- src/qml/utils.js | 307 ----------------- 48 files changed, 400 insertions(+), 430 deletions(-) create mode 100644 src/qml/Utils.qml delete mode 100644 src/qml/utils.js diff --git a/src/python/theme_parser.py b/src/python/theme_parser.py index 019c799d..25e0fe46 100644 --- a/src/python/theme_parser.py +++ b/src/python/theme_parser.py @@ -58,11 +58,10 @@ def convert_to_qml(theme_content: str) -> str: lines = [ "import QtQuick 2.12", 'import "Base"', - 'import "utils.js" as Utils', "QtObject {", - " function hsluv(h, s, l, a) { return Utils.hsluv(h, s, l, a) }", - " function hsl(h, s, l) { return Utils.hsl(h, s, l) }", - " function hsla(h, s, l, a) { return Utils.hsla(h, s, l, a) }", + " function hsluv(h, s, l, a) { return utils.hsluv(h, s, l, a) }", + " function hsl(h, s, l) { return utils.hsl(h, s, l) }", + " function hsla(h, s, l, a) { return utils.hsla(h, s, l, a) }", " id: theme", ] lines += [f" {line}" for line in _process_lines(theme_content)] diff --git a/src/qml/Base/HAvatar.qml b/src/qml/Base/HAvatar.qml index 9c36e501..ee06426c 100644 --- a/src/qml/Base/HAvatar.qml +++ b/src/qml/Base/HAvatar.qml @@ -1,15 +1,14 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import "../Base" -import "../utils.js" as Utils Rectangle { id: avatar implicitWidth: theme.controls.avatar.size implicitHeight: theme.controls.avatar.size - color: avatarImage.visible ? "transparent" : Utils.hsluv( - name ? Utils.hueFrom(name) : 0, + color: avatarImage.visible ? "transparent" : utils.hsluv( + name ? utils.hueFrom(name) : 0, name ? theme.controls.avatar.background.saturation : 0, theme.controls.avatar.background.lightness, theme.controls.avatar.background.opacity @@ -34,8 +33,8 @@ Rectangle { text: name ? name.charAt(0) : "?" font.pixelSize: parent.height / 1.4 - color: Utils.hsluv( - name ? Utils.hueFrom(name) : 0, + color: utils.hsluv( + name ? utils.hueFrom(name) : 0, name ? theme.controls.avatar.letter.saturation : 0, theme.controls.avatar.letter.lightness, theme.controls.avatar.letter.opacity diff --git a/src/qml/Base/HBox.qml b/src/qml/Base/HBox.qml index 2016c280..41e3dca7 100644 --- a/src/qml/Base/HBox.qml +++ b/src/qml/Base/HBox.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 -import "../utils.js" as Utils Rectangle { id: box @@ -114,10 +113,10 @@ Rectangle { property string name: modelData.name property Item next: buttonRepeater.itemAt( - Utils.numberWrapAt(index + 1, buttonRepeater.count), + utils.numberWrapAt(index + 1, buttonRepeater.count), ) property Item previous: buttonRepeater.itemAt( - Utils.numberWrapAt(index - 1, buttonRepeater.count), + utils.numberWrapAt(index - 1, buttonRepeater.count), ) } } diff --git a/src/qml/Base/HCheckBox.qml b/src/qml/Base/HCheckBox.qml index d1a2cd99..45803fa3 100644 --- a/src/qml/Base/HCheckBox.qml +++ b/src/qml/Base/HCheckBox.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 -import "../utils.js" as Utils CheckBox { id: box diff --git a/src/qml/Base/HDrawer.qml b/src/qml/Base/HDrawer.qml index 9d8a8364..156c7519 100644 --- a/src/qml/Base/HDrawer.qml +++ b/src/qml/Base/HDrawer.qml @@ -1,6 +1,5 @@ import QtQuick 2.13 import QtQuick.Controls 2.13 -import "../utils.js" as Utils Drawer { id: drawer diff --git a/src/qml/Base/HImage.qml b/src/qml/Base/HImage.qml index 8a499376..d75d7528 100644 --- a/src/qml/Base/HImage.qml +++ b/src/qml/Base/HImage.qml @@ -1,5 +1,4 @@ import QtQuick 2.12 -import "../utils.js" as Utils Image { id: image @@ -13,7 +12,7 @@ Image { property bool broken: false property bool animate: true - property bool animated: Utils.urlExtension(image.source) === "gif" + property bool animated: utils.urlExtension(image.source) === "gif" property alias showProgressBar: progressBarLoader.active property bool inderterminateProgressBar: false diff --git a/src/qml/Base/HSelectableLabel.qml b/src/qml/Base/HSelectableLabel.qml index 6d4965b3..0ac03783 100644 --- a/src/qml/Base/HSelectableLabel.qml +++ b/src/qml/Base/HSelectableLabel.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 -import "../utils.js" as Utils TextEdit { id: label diff --git a/src/qml/Base/HSelectableLabelContainer.qml b/src/qml/Base/HSelectableLabelContainer.qml index be859852..5e5410d8 100644 --- a/src/qml/Base/HSelectableLabelContainer.qml +++ b/src/qml/Base/HSelectableLabelContainer.qml @@ -1,5 +1,4 @@ import QtQuick 2.12 -import "../utils.js" as Utils FocusScope { signal deselectAll() diff --git a/src/qml/Base/HTile.qml b/src/qml/Base/HTile.qml index fcaa19a1..a9f2f112 100644 --- a/src/qml/Base/HTile.qml +++ b/src/qml/Base/HTile.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 -import "../utils.js" as Utils HButton { id: tile diff --git a/src/qml/Base/MediaPlayer/OSD.qml b/src/qml/Base/MediaPlayer/OSD.qml index 157432ce..0977421a 100644 --- a/src/qml/Base/MediaPlayer/OSD.qml +++ b/src/qml/Base/MediaPlayer/OSD.qml @@ -2,7 +2,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtAV 1.7 import "../../Base" -import "../../utils.js" as Utils HColumnLayout { id: osd @@ -118,7 +117,7 @@ HColumnLayout { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.margins: padding / 4 - text: Utils.formatDuration(previewToolTip.wantTimestamp) + text: utils.formatDuration(previewToolTip.wantTimestamp) padding: theme.spacing / 2 opacity: previewToolTip.wantTimestamp === -1 ? 0 : 1 @@ -197,7 +196,7 @@ HColumnLayout { OSDButton { id: speedButton icon.name: "player-speed" - text: qsTr("%1x").arg(Utils.round(media.playbackRate)) + text: qsTr("%1x").arg(utils.round(media.playbackRate)) toolTip.text: qsTr("Reset speed") onClicked: media.playbackRate = 1 } @@ -225,11 +224,11 @@ HColumnLayout { text: boundPosition && savedDuration ? qsTr("%1 / %2") - .arg(Utils.formatDuration(boundPosition)) - .arg(Utils.formatDuration(savedDuration)) : + .arg(utils.formatDuration(boundPosition)) + .arg(utils.formatDuration(savedDuration)) : boundPosition || savedDuration ? - Utils.formatDuration(boundPosition || savedDuration) : + utils.formatDuration(boundPosition || savedDuration) : "" } @@ -239,7 +238,7 @@ HColumnLayout { OSDLabel { text: boundPosition && savedDuration ? qsTr("-%1").arg( - Utils.formatDuration(savedDuration - boundPosition) + utils.formatDuration(savedDuration - boundPosition) ) : "" } diff --git a/src/qml/Chat/Banners/InviteBanner.qml b/src/qml/Chat/Banners/InviteBanner.qml index 5ec13242..d824b252 100644 --- a/src/qml/Chat/Banners/InviteBanner.qml +++ b/src/qml/Chat/Banners/InviteBanner.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import "../../Base" -import "../../utils.js" as Utils Banner { property string inviterId: chat.roomInfo.inviter @@ -14,7 +13,7 @@ Banner { avatar.mxc: inviterAvatar labelText: qsTr("%1 invited you to this room").arg( - Utils.coloredNameHtml(inviterName, inviterId) + utils.coloredNameHtml(inviterName, inviterId) ) buttonModel: [ diff --git a/src/qml/Chat/Banners/LeftBanner.qml b/src/qml/Chat/Banners/LeftBanner.qml index e956458c..9e9d2e45 100644 --- a/src/qml/Chat/Banners/LeftBanner.qml +++ b/src/qml/Chat/Banners/LeftBanner.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import "../../Base" -import "../../utils.js" as Utils Banner { color: theme.chat.leftBanner.background @@ -22,7 +21,7 @@ Banner { buttonCallbacks: ({ forget: button => { - Utils.makePopup( + utils.makePopup( "Popups/ForgetRoomPopup.qml", mainUI, // Must not be destroyed with chat { diff --git a/src/qml/Chat/Banners/UnknownDevicesBanner.qml b/src/qml/Chat/Banners/UnknownDevicesBanner.qml index b5ea400f..d4eaa74b 100644 --- a/src/qml/Chat/Banners/UnknownDevicesBanner.qml +++ b/src/qml/Chat/Banners/UnknownDevicesBanner.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import "../../Base" -import "../utils.js" as ChatJS Banner { color: theme.chat.unknownDevices.background diff --git a/src/qml/Chat/Chat.qml b/src/qml/Chat/Chat.qml index a2f727ec..365e746c 100644 --- a/src/qml/Chat/Chat.qml +++ b/src/qml/Chat/Chat.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" -import "../utils.js" as Utils import "RoomPane" Item { @@ -16,10 +15,10 @@ Item { property bool ready: userInfo !== "waiting" && roomInfo !== "waiting" readonly property var userInfo: - Utils.getItem(modelSources["Account"] || [], "user_id", userId) || + utils.getItem(modelSources["Account"] || [], "user_id", userId) || "waiting" - readonly property var roomInfo: Utils.getItem( + readonly property var roomInfo: utils.getItem( modelSources[["Room", userId]] || [], "room_id", roomId ) || "waiting" diff --git a/src/qml/Chat/ChatPage.qml b/src/qml/Chat/ChatPage.qml index 0871c248..1d41061a 100644 --- a/src/qml/Chat/ChatPage.qml +++ b/src/qml/Chat/ChatPage.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" -import "../utils.js" as Utils import "Banners" import "Timeline" import "FileTransfer" diff --git a/src/qml/Chat/Composer.qml b/src/qml/Chat/Composer.qml index 3a696a60..70e6726c 100644 --- a/src/qml/Chat/Composer.qml +++ b/src/qml/Chat/Composer.qml @@ -2,7 +2,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" import "../Dialogs" -import "../utils.js" as Utils Rectangle { property string indent: " " @@ -12,7 +11,7 @@ Rectangle { property string writingUserId: chat.userId readonly property var writingUserInfo: - Utils.getItem(modelSources["Account"] || [], "user_id", writingUserId) + utils.getItem(modelSources["Account"] || [], "user_id", writingUserId) property bool textChangedSinceLostFocus: false @@ -90,7 +89,7 @@ Rectangle { } onTextChanged: { - if (Utils.isEmptyObject(aliases)) { + if (utils.isEmptyObject(aliases)) { writingUserId = Qt.binding(() => chat.userId) toSend = text setTyping(Boolean(text)) diff --git a/src/qml/Chat/FileTransfer/Transfer.qml b/src/qml/Chat/FileTransfer/Transfer.qml index 44192d7c..7cb2e5d0 100644 --- a/src/qml/Chat/FileTransfer/Transfer.qml +++ b/src/qml/Chat/FileTransfer/Transfer.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HColumnLayout { id: transfer @@ -102,7 +101,7 @@ HColumnLayout { Repeater { model: [ - msLeft ? qsTr("-%1").arg(Utils.formatDuration(msLeft)) : "", + msLeft ? qsTr("-%1").arg(utils.formatDuration(msLeft)) : "", speed ? qsTr("%1/s").arg(CppUtils.formattedBytes(speed)) : "", diff --git a/src/qml/Chat/RoomPane/MemberDelegate.qml b/src/qml/Chat/RoomPane/MemberDelegate.qml index ce33816a..993b2f65 100644 --- a/src/qml/Chat/RoomPane/MemberDelegate.qml +++ b/src/qml/Chat/RoomPane/MemberDelegate.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import "../../Base" -import "../../utils.js" as Utils HTileDelegate { id: memberDelegate @@ -20,7 +19,7 @@ HTileDelegate { title.text: model.display_name || model.user_id title.color: memberDelegate.hovered ? - Utils.nameColor(model.display_name || model.user_id.substring(1)) : + utils.nameColor(model.display_name || model.user_id.substring(1)) : theme.chat.roomPane.member.name subtitle.text: model.display_name ? model.user_id : "" diff --git a/src/qml/Chat/RoomPane/MemberView.qml b/src/qml/Chat/RoomPane/MemberView.qml index 2a5f00a9..f5aa3cf4 100644 --- a/src/qml/Chat/RoomPane/MemberView.qml +++ b/src/qml/Chat/RoomPane/MemberView.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HColumnLayout { HListView { @@ -21,7 +20,7 @@ HColumnLayout { function filterSource() { model.source = - Utils.filterModelSource(originSource, filterField.text) + utils.filterModelSource(originSource, filterField.text) } @@ -77,7 +76,7 @@ HColumnLayout { topPadding: 0 // XXX bottomPadding: 0 - onClicked: Utils.makePopup( + onClicked: utils.makePopup( "Popups/InviteToRoomPopup.qml", chat, { diff --git a/src/qml/Chat/RoomPane/SettingsView.qml b/src/qml/Chat/RoomPane/SettingsView.qml index 388759c3..45fbb5fb 100644 --- a/src/qml/Chat/RoomPane/SettingsView.qml +++ b/src/qml/Chat/RoomPane/SettingsView.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HBox { color: "transparent" diff --git a/src/qml/Chat/Timeline/EventAudio.qml b/src/qml/Chat/Timeline/EventAudio.qml index 94d394c7..5d6e62c2 100644 --- a/src/qml/Chat/Timeline/EventAudio.qml +++ b/src/qml/Chat/Timeline/EventAudio.qml @@ -3,7 +3,6 @@ import QtQuick.Layouts 1.12 import QtAV 1.7 import "../../Base" import "../../Base/MediaPlayer" -import "../../utils.js" as Utils AudioPlayer { id: audio diff --git a/src/qml/Chat/Timeline/EventContent.qml b/src/qml/Chat/Timeline/EventContent.qml index 1eff8143..f0b1b0f4 100644 --- a/src/qml/Chat/Timeline/EventContent.qml +++ b/src/qml/Chat/Timeline/EventContent.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HRowLayout { id: eventContent @@ -12,11 +11,11 @@ HRowLayout { readonly property string senderText: hideNameLine ? "" : ( "
" + - Utils.coloredNameHtml(model.sender_name, model.sender_id) + + utils.coloredNameHtml(model.sender_name, model.sender_id) + "
" ) - readonly property string contentText: Utils.processedEventText(model) - readonly property string timeText: Utils.formatTime(model.date, false) + readonly property string contentText: utils.processedEventText(model) + readonly property string timeText: utils.formatTime(model.date, false) readonly property string localEchoText: model.is_local_echo ? ` ` : @@ -40,7 +39,7 @@ HRowLayout { TapHandler { enabled: debugMode onDoubleTapped: - Utils.debug(eventContent, null, con => { con.runJS("json()") }) + utils.debug(eventContent, null, con => { con.runJS("json()") }) } Item { @@ -149,7 +148,7 @@ HRowLayout { visible: model.event_type === "RoomMessageNotice" width: theme.chat.message.noticeLineWidth height: parent.height - color: Utils.nameColor( + color: utils.nameColor( model.sender_name || model.sender_id.substring(1), ) } diff --git a/src/qml/Chat/Timeline/EventDelegate.qml b/src/qml/Chat/Timeline/EventDelegate.qml index 201244b6..ab205ed0 100644 --- a/src/qml/Chat/Timeline/EventDelegate.qml +++ b/src/qml/Chat/Timeline/EventDelegate.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HColumnLayout { id: eventDelegate @@ -63,7 +62,7 @@ HColumnLayout { function json() { return JSON.stringify( { - "model": Utils.getItem( + "model": utils.getItem( modelSources[[ "Event", chat.userId, chat.roomId ]], @@ -172,7 +171,7 @@ HColumnLayout { HMenuItem { icon.name: "clear-messages" text: qsTr("Clear messages") - onTriggered: Utils.makePopup( + onTriggered: utils.makePopup( "Popups/ClearMessagesPopup.qml", chat, {userId: chat.userId, roomId: chat.roomId}, diff --git a/src/qml/Chat/Timeline/EventFile.qml b/src/qml/Chat/Timeline/EventFile.qml index 5a4ffafe..ec751b87 100644 --- a/src/qml/Chat/Timeline/EventFile.qml +++ b/src/qml/Chat/Timeline/EventFile.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HTile { id: file @@ -39,5 +38,5 @@ HTile { property EventMediaLoader loader readonly property bool cryptDict: loader.singleMediaInfo.media_crypt_dict - readonly property bool isEncrypted: ! Utils.isEmptyObject(cryptDict) + readonly property bool isEncrypted: ! utils.isEmptyObject(cryptDict) } diff --git a/src/qml/Chat/Timeline/EventImage.qml b/src/qml/Chat/Timeline/EventImage.qml index 354b800f..2c57778a 100644 --- a/src/qml/Chat/Timeline/EventImage.qml +++ b/src/qml/Chat/Timeline/EventImage.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import "../../Base" -import "../../utils.js" as Utils HMxcImage { id: image @@ -9,7 +8,7 @@ HMxcImage { horizontalAlignment: Image.AlignLeft animated: loader.singleMediaInfo.media_mime === "image/gif" || - Utils.urlExtension(loader.mediaUrl) === "gif" + utils.urlExtension(loader.mediaUrl) === "gif" thumbnail: ! animated && loader.thumbnailMxc mxc: thumbnail ? (loader.thumbnailMxc || loader.mediaUrl) : @@ -21,12 +20,12 @@ HMxcImage { property EventMediaLoader loader - readonly property bool isEncrypted: ! Utils.isEmptyObject(cryptDict) + readonly property bool isEncrypted: ! utils.isEmptyObject(cryptDict) readonly property real maxHeight: theme.chat.message.thumbnailMaxHeightRatio - readonly property size fitSize: Utils.fitSize( + readonly property size fitSize: utils.fitSize( // Minimum display size theme.chat.message.thumbnailMinSize.width, theme.chat.message.thumbnailMinSize.height, diff --git a/src/qml/Chat/Timeline/EventList.qml b/src/qml/Chat/Timeline/EventList.qml index 4e142e6b..aaaedef9 100644 --- a/src/qml/Chat/Timeline/EventList.qml +++ b/src/qml/Chat/Timeline/EventList.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import "../../Base" -import "../../utils.js" as Utils Rectangle { property alias selectableLabelContainer: selectableLabelContainer @@ -100,7 +99,7 @@ Rectangle { ! canTalkBreak(item, itemAfter) && ! canDayBreak(item, itemAfter) && item.sender_id === itemAfter.sender_id && - Utils.minutesBetween(item.date, itemAfter.date) <= 5 + utils.minutesBetween(item.date, itemAfter.date) <= 5 ) } @@ -109,7 +108,7 @@ Rectangle { return Boolean( ! canDayBreak(item, itemAfter) && - Utils.minutesBetween(item.date, itemAfter.date) >= 20 + utils.minutesBetween(item.date, itemAfter.date) >= 20 ) } diff --git a/src/qml/Chat/Timeline/EventMediaLoader.qml b/src/qml/Chat/Timeline/EventMediaLoader.qml index b66df087..5ef1423c 100644 --- a/src/qml/Chat/Timeline/EventMediaLoader.qml +++ b/src/qml/Chat/Timeline/EventMediaLoader.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import "../../Base" -import "../../utils.js" as Utils HLoader { id: loader @@ -58,7 +57,7 @@ HLoader { return EventDelegate.Media.File // If this is a preview for a link in a normal message - let ext = Utils.urlExtension(mediaUrl) + let ext = utils.urlExtension(mediaUrl) if (imageExtensions.includes(ext)) return EventDelegate.Media.Image if (videoExtensions.includes(ext)) return EventDelegate.Media.Video diff --git a/src/qml/Chat/Timeline/EventVideo.qml b/src/qml/Chat/Timeline/EventVideo.qml index 27d7fa6c..aafa325d 100644 --- a/src/qml/Chat/Timeline/EventVideo.qml +++ b/src/qml/Chat/Timeline/EventVideo.qml @@ -3,7 +3,6 @@ import QtQuick.Layouts 1.12 import QtAV 1.7 import "../../Base" import "../../Base/MediaPlayer" -import "../../utils.js" as Utils VideoPlayer { id: video diff --git a/src/qml/DebugConsole.qml b/src/qml/DebugConsole.qml index 7f9c383a..703e88f9 100644 --- a/src/qml/DebugConsole.qml +++ b/src/qml/DebugConsole.qml @@ -2,8 +2,6 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Layouts 1.12 import "Base" -import "utils.js" as Utils -import "utils.js" as U HDrawer { id: debugConsole @@ -31,9 +29,8 @@ HDrawer { `Javascript debugging console Useful variables: - window, theme, settings, shortcuts, mainUI, pageLoader + window, theme, settings, shortcuts, utils, mainUI, pageLoader py Python interpreter - U Utils/utils.js module this The console itself t Target item to debug for which this console was opened his History, list of commands entered diff --git a/src/qml/Dialogs/SendFilePicker.qml b/src/qml/Dialogs/SendFilePicker.qml index 4b1e6b06..8d8341fd 100644 --- a/src/qml/Dialogs/SendFilePicker.qml +++ b/src/qml/Dialogs/SendFilePicker.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import Qt.labs.platform 1.1 -import "../utils.js" as Utils HFileDialogOpener { fill: false @@ -11,7 +10,7 @@ HFileDialogOpener { for (let file of files) { let path = Qt.resolvedUrl(file).replace(/^file:/, "") - Utils.sendFile(userId, roomId, path, () => { + utils.sendFile(userId, roomId, path, () => { if (destroyWhenDone) destroy() }, (type, args, error, traceback) => { diff --git a/src/qml/MainPane/AccountDelegate.qml b/src/qml/MainPane/AccountDelegate.qml index 78a936d6..4d1d1aff 100644 --- a/src/qml/MainPane/AccountDelegate.qml +++ b/src/qml/MainPane/AccountDelegate.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" -import "../utils.js" as Utils HTileDelegate { id: accountDelegate @@ -113,7 +112,7 @@ HTileDelegate { icon.name: "sign-out" icon.color: theme.colors.negativeBackground text: qsTr("Sign out") - onTriggered: Utils.makePopup( + onTriggered: utils.makePopup( "Popups/SignOutPopup.qml", window, { "userId": model.data.user_id }, diff --git a/src/qml/MainPane/AccountRoomList.qml b/src/qml/MainPane/AccountRoomList.qml index d445bf41..99a3cf85 100644 --- a/src/qml/MainPane/AccountRoomList.qml +++ b/src/qml/MainPane/AccountRoomList.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" -import "../utils.js" as Utils HListView { id: mainPaneList @@ -28,12 +27,12 @@ HListView { if (item.type === "Account" || (filter ? - Utils.filterMatches(filter, item.data.filter_string) : + utils.filterMatches(filter, item.data.filter_string) : ! window.uiState.collapseAccounts[item.user_id])) { if (filter && show.length && item.type === "Account" && show[show.length - 1].type === "Account" && - ! Utils.filterMatches( + ! utils.filterMatches( filter, show[show.length - 1].data.filter_string) ) { // If filter active, current and previous items are @@ -48,7 +47,7 @@ HListView { let last = show[show.length - 1] if (show.length && filter && last.type === "Account" && - ! Utils.filterMatches(filter, last.data.filter_string)) + ! utils.filterMatches(filter, last.data.filter_string)) { // If filter active, last item is an account and last item // doesn't match filter, that account had no matching rooms. diff --git a/src/qml/MainPane/MainPane.qml b/src/qml/MainPane/MainPane.qml index 1541aa9b..7b09efe4 100644 --- a/src/qml/MainPane/MainPane.qml +++ b/src/qml/MainPane/MainPane.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" -import "../utils.js" as Utils HDrawer { id: mainPane diff --git a/src/qml/MainPane/RoomDelegate.qml b/src/qml/MainPane/RoomDelegate.qml index c491a4dd..80725971 100644 --- a/src/qml/MainPane/RoomDelegate.qml +++ b/src/qml/MainPane/RoomDelegate.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" -import "../utils.js" as Utils HTileDelegate { id: roomDelegate @@ -53,8 +52,8 @@ HTileDelegate { ! lastEvent || ! lastEvent.date ? "" : - Utils.dateIsToday(lastEvent.date) ? - Utils.formatTime(lastEvent.date, false) : // no seconds + utils.dateIsToday(lastEvent.date) ? + utils.formatTime(lastEvent.date, false) : // no seconds lastEvent.date.getFullYear() === new Date().getFullYear() ? Qt.formatDate(lastEvent.date, "d MMM") : // e.g. "5 Dec" @@ -76,10 +75,10 @@ HTileDelegate { // If it's a general event if (isEmote || isUnknownMsg || (! isMsg && ! isCryptMedia)) { - return Utils.processedEventText(lastEvent) + return utils.processedEventText(lastEvent) } - let text = Utils.coloredNameHtml( + let text = utils.coloredNameHtml( lastEvent.sender_name, lastEvent.sender_id ) + ": " + lastEvent.inline_content @@ -96,7 +95,7 @@ HTileDelegate { icon.name: "room-send-invite" text: qsTr("Invite members") - onTriggered: Utils.makePopup( + onTriggered: utils.makePopup( "Popups/InviteToRoomPopup.qml", window, { @@ -118,7 +117,7 @@ HTileDelegate { visible: invited icon.name: "invite-accept" icon.color: theme.colors.positiveBackground - text: qsTr("Accept %1's invite").arg(Utils.coloredNameHtml( + text: qsTr("Accept %1's invite").arg(utils.coloredNameHtml( model.data.inviter_name, model.data.inviter_id )) label.textFormat: Text.StyledText @@ -134,7 +133,7 @@ HTileDelegate { icon.color: theme.colors.negativeBackground text: invited ? qsTr("Decline invite") : qsTr("Leave") - onTriggered: Utils.makePopup( + onTriggered: utils.makePopup( "Popups/LeaveRoomPopup.qml", window, { @@ -150,7 +149,7 @@ HTileDelegate { icon.color: theme.colors.negativeBackground text: qsTr("Forget") - onTriggered: Utils.makePopup( + onTriggered: utils.makePopup( "Popups/ForgetRoomPopup.qml", window, { diff --git a/src/qml/Pages/AccountSettings/AccountSettings.qml b/src/qml/Pages/AccountSettings/AccountSettings.qml index 5a9c8354..11fd8f4f 100644 --- a/src/qml/Pages/AccountSettings/AccountSettings.qml +++ b/src/qml/Pages/AccountSettings/AccountSettings.qml @@ -2,7 +2,6 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HPage { id: accountSettings @@ -14,7 +13,7 @@ HPage { readonly property bool ready: accountInfo !== "waiting" && Boolean(accountInfo.profile_updated) - readonly property var accountInfo: Utils.getItem( + readonly property var accountInfo: utils.getItem( modelSources["Account"] || [], "user_id", userId ) || "waiting" @@ -22,7 +21,7 @@ HPage { hideHeaderUnderHeight: avatarPreferredSize headerLabel.text: qsTr("Account settings for %1").arg( - Utils.coloredNameHtml(headerName, userId) + utils.coloredNameHtml(headerName, userId) ) diff --git a/src/qml/Pages/AccountSettings/ImportExportKeys.qml b/src/qml/Pages/AccountSettings/ImportExportKeys.qml index 7905a073..9be1c394 100644 --- a/src/qml/Pages/AccountSettings/ImportExportKeys.qml +++ b/src/qml/Pages/AccountSettings/ImportExportKeys.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HBox { buttonModel: [ @@ -11,7 +10,7 @@ HBox { buttonCallbacks: ({ export: button => { - Utils.makeObject( + utils.makeObject( "Dialogs/ExportKeys.qml", accountSettings, { userId: accountSettings.userId }, @@ -22,7 +21,7 @@ HBox { ) }, import: button => { - Utils.makeObject( + utils.makeObject( "Dialogs/ImportKeys.qml", accountSettings, { userId: accountSettings.userId }, diff --git a/src/qml/Pages/AccountSettings/Profile.qml b/src/qml/Pages/AccountSettings/Profile.qml index 02b0aedf..2f1da836 100644 --- a/src/qml/Pages/AccountSettings/Profile.qml +++ b/src/qml/Pages/AccountSettings/Profile.qml @@ -3,7 +3,6 @@ import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import "../../Base" import "../../Dialogs" -import "../../utils.js" as Utils HGridLayout { function applyChanges() { @@ -77,7 +76,7 @@ HGridLayout { 1 : 0 anchors.fill: parent - color: Utils.hsluv(0, 0, 0, + color: utils.hsluv(0, 0, 0, (! avatar.mxc && overlayHover.hovered) ? 0.8 : 0.7 ) @@ -139,7 +138,7 @@ HGridLayout { HLabel { text: qsTr("User ID:
%1") - .arg(Utils.coloredNameHtml(userId, userId, userId)) + .arg(utils.coloredNameHtml(userId, userId, userId)) textFormat: Text.StyledText wrapMode: Text.Wrap diff --git a/src/qml/Pages/AddChat/AddChat.qml b/src/qml/Pages/AddChat/AddChat.qml index f6a5a9aa..2f7cd3d4 100644 --- a/src/qml/Pages/AddChat/AddChat.qml +++ b/src/qml/Pages/AddChat/AddChat.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HPage { id: addChatPage @@ -10,7 +9,7 @@ HPage { property string userId readonly property var account: - Utils.getItem(modelSources["Account"] || [], "user_id", userId) + utils.getItem(modelSources["Account"] || [], "user_id", userId) HTabContainer { diff --git a/src/qml/Pages/AddChat/CreateRoom.qml b/src/qml/Pages/AddChat/CreateRoom.qml index 991e31c5..7533def3 100644 --- a/src/qml/Pages/AddChat/CreateRoom.qml +++ b/src/qml/Pages/AddChat/CreateRoom.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HBox { id: addChatBox diff --git a/src/qml/Pages/AddChat/DirectChat.qml b/src/qml/Pages/AddChat/DirectChat.qml index bcfe419b..f149ff8a 100644 --- a/src/qml/Pages/AddChat/DirectChat.qml +++ b/src/qml/Pages/AddChat/DirectChat.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HBox { id: addChatBox diff --git a/src/qml/Pages/AddChat/JoinRoom.qml b/src/qml/Pages/AddChat/JoinRoom.qml index e9ed4207..10c9a997 100644 --- a/src/qml/Pages/AddChat/JoinRoom.qml +++ b/src/qml/Pages/AddChat/JoinRoom.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils HBox { id: addChatBox diff --git a/src/qml/Popups/BoxPopup.qml b/src/qml/Popups/BoxPopup.qml index 4b8bfffc..74656bb4 100644 --- a/src/qml/Popups/BoxPopup.qml +++ b/src/qml/Popups/BoxPopup.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" -import "../utils.js" as Utils HPopup { id: popup diff --git a/src/qml/Popups/SignOutPopup.qml b/src/qml/Popups/SignOutPopup.qml index 54153149..b54ccaf1 100644 --- a/src/qml/Popups/SignOutPopup.qml +++ b/src/qml/Popups/SignOutPopup.qml @@ -1,5 +1,4 @@ import QtQuick 2.12 -import "../utils.js" as Utils BoxPopup { id: popup @@ -25,7 +24,7 @@ BoxPopup { box.buttonCallbacks: ({ ok: button => { - Utils.makeObject( + utils.makeObject( "Dialogs/ExportKeys.qml", mainUI, { userId }, diff --git a/src/qml/Shortcuts.qml b/src/qml/Shortcuts.qml index 8c99ee4e..4f20c038 100644 --- a/src/qml/Shortcuts.qml +++ b/src/qml/Shortcuts.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import "Base" -import "utils.js" as Utils Item { visible: false @@ -35,7 +34,7 @@ Item { if (debugConsole) { debugConsole.visible = ! debugConsole.visible } else { - Utils.debug(mainUI || window) + utils.debug(mainUI || window) } } } @@ -72,37 +71,37 @@ Item { HShortcut { enabled: toFlick sequences: settings.keys.scrollUp - onActivated: Utils.flickPages(toFlick, -1 / 10) + onActivated: utils.flickPages(toFlick, -1 / 10) } HShortcut { enabled: toFlick sequences: settings.keys.scrollDown - onActivated: Utils.flickPages(toFlick, 1 / 10) + onActivated: utils.flickPages(toFlick, 1 / 10) } HShortcut { enabled: toFlick sequences: settings.keys.scrollPageUp - onActivated: Utils.flickPages(toFlick, -1) + onActivated: utils.flickPages(toFlick, -1) } HShortcut { enabled: toFlick sequences: settings.keys.scrollPageDown - onActivated: Utils.flickPages(toFlick, 1) + onActivated: utils.flickPages(toFlick, 1) } HShortcut { enabled: toFlick sequences: settings.keys.scrollToTop - onActivated: Utils.flickToTop(toFlick) + onActivated: utils.flickToTop(toFlick) } HShortcut { enabled: toFlick sequences: settings.keys.scrollToBottom - onActivated: Utils.flickToBottom(toFlick) + onActivated: utils.flickToBottom(toFlick) } @@ -112,7 +111,7 @@ Item { enabled: tabsTarget sequences: settings.keys.previousTab onActivated: tabsTarget.setCurrentIndex( - Utils.numberWrapAt(tabsTarget.currentIndex - 1, tabsTarget.count), + utils.numberWrapAt(tabsTarget.currentIndex - 1, tabsTarget.count), ) } @@ -120,7 +119,7 @@ Item { enabled: tabsTarget sequences: settings.keys.nextTab onActivated: tabsTarget.setCurrentIndex( - Utils.numberWrapAt(tabsTarget.currentIndex + 1, tabsTarget.count), + utils.numberWrapAt(tabsTarget.currentIndex + 1, tabsTarget.count), ) } @@ -185,7 +184,7 @@ Item { HShortcut { enabled: window.uiState.page === "Chat/Chat.qml" sequences: settings.keys.clearRoomMessages - onActivated: Utils.makePopup( + onActivated: utils.makePopup( "Popups/ClearMessagesPopup.qml", mainUI, { @@ -198,7 +197,7 @@ Item { HShortcut { enabled: window.uiState.page === "Chat/Chat.qml" sequences: settings.keys.sendFile - onActivated: Utils.makeObject( + onActivated: utils.makeObject( "Dialogs/SendFilePicker.qml", mainUI, { @@ -213,7 +212,7 @@ Item { HShortcut { enabled: window.uiState.page === "Chat/Chat.qml" sequences: settings.keys.sendFileFromPathInClipboard - onActivated: Utils.sendFile( + onActivated: utils.sendFile( window.uiState.pageProperties.userId, window.uiState.pageProperties.roomId, Clipboard.text.trim(), diff --git a/src/qml/UI.qml b/src/qml/UI.qml index 3d87943e..a9f4fc9a 100644 --- a/src/qml/UI.qml +++ b/src/qml/UI.qml @@ -5,7 +5,6 @@ import QtQuick.Window 2.7 import QtGraphicalEffects 1.12 import "Base" import "MainPane" -import "utils.js" as Utils Item { id: mainUI diff --git a/src/qml/Utils.qml b/src/qml/Utils.qml new file mode 100644 index 00000000..1492e2ed --- /dev/null +++ b/src/qml/Utils.qml @@ -0,0 +1,323 @@ +import QtQuick 2.12 + +QtObject { + function makeObject(urlComponent, parent=null, properties={}, + callback=null) { + let comp = urlComponent + + if (! Qt.isQtObject(urlComponent)) { + // It's an url or path string to a component + comp = Qt.createComponent(urlComponent, Component.Asynchronous) + } + + let ready = false + + comp.statusChanged.connect(status => { + if ([Component.Null, Component.Error].includes(status)) { + console.error("Failed creating component: ", comp.errorString()) + + } else if (! ready && status === Component.Ready) { + let incu = comp.incubateObject( + parent, properties, Qt.Asynchronous, + ) + + if (incu.status === Component.Ready) { + if (callback) callback(incu.object) + ready = true + return + } + + incu.onStatusChanged = (istatus) => { + if (incu.status === Component.Error) { + console.error("Failed incubating object: ", + incu.errorString()) + + } else if (istatus === Component.Ready && + callback && ! ready) { + if (callback) callback(incu.object) + ready = true + } + } + } + }) + + if (comp.status === Component.Ready) comp.statusChanged(comp.status) + } + + + function makePopup(urlComponent, parent=null, properties={}, callback=null, + autoDestruct=true) { + makeObject(urlComponent, parent, properties, (popup) => { + popup.open() + if (autoDestruct) popup.closed.connect(() => { popup.destroy() }) + if (callback) callback(popup) + }) + } + + + function debug(target, parent=null, callback=null) { + parent = parent || target + return utils.makeObject( + "DebugConsole.qml", parent, { target }, callback, + ) + } + + + function isEmptyObject(obj) { + return Object.entries(obj).length === 0 && obj.constructor === Object + } + + + function objectUpdateRecursive(current, update) { + for (const key of Object.keys(update)) { + if ((key in current) && typeof(current[key]) === "object" && + typeof(update[key]) === "object") { + objectUpdateRecursive(current[key], update[key]) + } else { + current[key] = update[key] + } + } + } + + + function numberWrapAt(num, max) { + return num < 0 ? max + (num % max) : (num % max) + } + + + function hsluv(hue, saturation, lightness, alpha=1.0) { + hue = numberWrapAt(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 + + let unknown = ev.event_type === "RoomMessageUnknown" + + if (ev.event_type.startsWith("RoomMessage") && ! unknown) + return ev.content + + if (ev.event_type.startsWith("RoomEncrypted")) 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 fitSize(minWidth, minHeight, width, height, maxWidth, maxHeight) { + if (width >= height) { + let new_width = Math.max(Math.min(width, maxWidth), minWidth) + return Qt.size(new_width, height / (width / new_width)) + } + + let new_height = Math.max(Math.min(height, maxHeight), minHeight) + return Qt.size(width / (height / new_height), new_height) + } + + + function minutesBetween(date1, date2) { + return ((date2 - date1) / 1000) / 60 + } + + + 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; + // also remove the timezone at the end + ) + } + + + function formatDuration(milliseconds) { + let totalSeconds = milliseconds / 1000 + + let hours = Math.floor(totalSeconds / 3600) + let minutes = Math.floor((totalSeconds % 3600) / 60) + let seconds = Math.floor(totalSeconds % 60) + + if (seconds < 10) seconds = `0${seconds}` + if (hours < 1) return `${minutes}:${seconds}` + + if (minutes < 10) minutes = `0${minutes}` + return `${hours}:${minutes}:${seconds}` + } + + + function round(floatNumber) { + return parseFloat(floatNumber.toFixed(2)) + } + + + function getItem(array, mainKey, value) { + for (let i = 0; i < array.length; i++) { + if (array[i][mainKey] === value) { return array[i] } + } + return undefined + } + + + function flickPages(flickable, pages) { + // Adapt velocity and deceleration for the number of pages to flick. + // If this is a repeated flicking, flick faster than a single flick. + if (! flickable.interactive && flickable.allowDragging) return + + const futureVelocity = -flickable.height * pages + const currentVelocity = -flickable.verticalVelocity + const goFaster = + (futureVelocity < 0 && currentVelocity < futureVelocity / 2) || + (futureVelocity > 0 && currentVelocity > futureVelocity / 2) + + const normalDecel = flickable.flickDeceleration + const fastMultiply = pages && 8 / (1 - Math.log10(Math.abs(pages))) + const magicNumber = 2.5 + + flickable.flickDeceleration = Math.max( + goFaster ? normalDecel : -Infinity, + Math.abs(normalDecel * magicNumber * pages), + ) + + flickable.flick( + 0, futureVelocity * magicNumber * (goFaster ? fastMultiply : 1), + ) + + flickable.flickDeceleration = normalDecel + } + + + function flickToTop(flickable) { + if (! flickable.interactive && flickable.allowDragging) 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 && flickable.allowDragging) return + if (flickable.visibleArea.yPosition < 0) return + + flickable.contentY = flickTarget.contentHeight - flickTarget.height + flickable.returnToBounds() + flickable.flick(0, 100) + } + + + function urlExtension(url) { + return url.toString().split("/").slice(-1)[0].split("?")[0].split(".") + .slice(-1)[0].toLowerCase() + } + + + function sendFile(userId, roomId, path, onSuccess, onError) { + py.callClientCoro( + userId, "send_file", [roomId, path], onSuccess, onError, + ) + } +} diff --git a/src/qml/Window.qml b/src/qml/Window.qml index 26e54c57..3e403d98 100644 --- a/src/qml/Window.qml +++ b/src/qml/Window.qml @@ -1,7 +1,6 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import "Base" -import "utils.js" as Utils ApplicationWindow { id: window @@ -55,7 +54,7 @@ ApplicationWindow { propertyValues[prop] = obj[prop] } - Utils.objectUpdateRecursive(uiState, { + utils.objectUpdateRecursive(uiState, { [obj.saveName]: { [obj.saveId || "ALL"]: propertyValues }, }) @@ -73,6 +72,8 @@ ApplicationWindow { Python { id: py } + Utils { id: utils } + HoverHandler { id: windowHover } HLoader { diff --git a/src/qml/utils.js b/src/qml/utils.js deleted file mode 100644 index 4a12c2ca..00000000 --- a/src/qml/utils.js +++ /dev/null @@ -1,307 +0,0 @@ -function makeObject(urlComponent, parent=null, properties={}, callback=null) { - let comp = urlComponent - - if (! Qt.isQtObject(urlComponent)) { - // It's an url or path string to a component - comp = Qt.createComponent(urlComponent, Component.Asynchronous) - } - - let ready = false - - comp.statusChanged.connect(status => { - if ([Component.Null, Component.Error].includes(status)) { - console.error("Failed creating component: ", comp.errorString()) - - } else if (! ready && status === Component.Ready) { - let incu = comp.incubateObject(parent, properties, Qt.Asynchronous) - - if (incu.status === Component.Ready) { - if (callback) callback(incu.object) - ready = true - return - } - - incu.onStatusChanged = (istatus) => { - if (incu.status === Component.Error) { - console.error("Failed incubating object: ", - incu.errorString()) - - } else if (istatus === Component.Ready && callback && ! ready) { - if (callback) callback(incu.object) - ready = true - } - } - } - }) - - if (comp.status === Component.Ready) comp.statusChanged(comp.status) -} - - -function makePopup(urlComponent, parent=null, properties={}, callback=null, - autoDestruct=true) { - makeObject(urlComponent, parent, properties, (popup) => { - popup.open() - if (autoDestruct) popup.closed.connect(() => { popup.destroy() }) - if (callback) callback(popup) - }) -} - - -function debug(target, parent=null, callback=null) { - parent = parent || target - return Utils.makeObject("DebugConsole.qml", parent, { target }, callback) -} - - -function isEmptyObject(obj) { - return Object.entries(obj).length === 0 && obj.constructor === Object -} - - -function objectUpdateRecursive(current, update) { - for (const key of Object.keys(update)) { - if ((key in current) && typeof(current[key]) === "object" && - typeof(update[key]) === "object") { - objectUpdateRecursive(current[key], update[key]) - } else { - current[key] = update[key] - } - } -} - - -function numberWrapAt(num, max) { - return num < 0 ? max + (num % max) : (num % max) -} - - -function hsluv(hue, saturation, lightness, alpha=1.0) { - hue = numberWrapAt(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 - - let unknown = ev.event_type === "RoomMessageUnknown" - - if (ev.event_type.startsWith("RoomMessage") && ! unknown) return ev.content - if (ev.event_type.startsWith("RoomEncrypted")) 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 fitSize(minWidth, minHeight, width, height, maxWidth, maxHeight) { - if (width >= height) { - let new_width = Math.max(Math.min(width, maxWidth), minWidth) - return Qt.size(new_width, height / (width / new_width)) - } - - let new_height = Math.max(Math.min(height, maxHeight), minHeight) - return Qt.size(width / (height / new_height), new_height) -} - - -function minutesBetween(date1, date2) { - return ((date2 - date1) / 1000) / 60 -} - - -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; - // also remove the timezone at the end - ) -} - - -function formatDuration(milliseconds) { - let totalSeconds = milliseconds / 1000 - - let hours = Math.floor(totalSeconds / 3600) - let minutes = Math.floor((totalSeconds % 3600) / 60) - let seconds = Math.floor(totalSeconds % 60) - - if (seconds < 10) seconds = `0${seconds}` - if (hours < 1) return `${minutes}:${seconds}` - - if (minutes < 10) minutes = `0${minutes}` - return `${hours}:${minutes}:${seconds}` -} - - -function round(float) { - return parseFloat(float.toFixed(2)) -} - - -function getItem(array, mainKey, value) { - for (let i = 0; i < array.length; i++) { - if (array[i][mainKey] === value) { return array[i] } - } - return undefined -} - - -function flickPages(flickable, pages) { - // Adapt velocity and deceleration for the number of pages to flick. - // If this is a repeated flicking, flick faster than a single flick. - if (! flickable.interactive && flickable.allowDragging) return - - const futureVelocity = -flickable.height * pages - const currentVelocity = -flickable.verticalVelocity - const goFaster = - (futureVelocity < 0 && currentVelocity < futureVelocity / 2) || - (futureVelocity > 0 && currentVelocity > futureVelocity / 2) - - const normalDecel = flickable.flickDeceleration - const fastMultiply = pages && 8 / (1 - Math.log10(Math.abs(pages))) - const magicNumber = 2.5 - - flickable.flickDeceleration = Math.max( - goFaster ? normalDecel : -Infinity, - Math.abs(normalDecel * magicNumber * pages), - ) - - flickable.flick( - 0, futureVelocity * magicNumber * (goFaster ? fastMultiply : 1), - ) - - flickable.flickDeceleration = normalDecel -} - - -function flickToTop(flickable) { - if (! flickable.interactive && flickable.allowDragging) 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 && flickable.allowDragging) return - if (flickable.visibleArea.yPosition < 0) return - - flickable.contentY = flickTarget.contentHeight - flickTarget.height - flickable.returnToBounds() - flickable.flick(0, 100) -} - - -function urlExtension(url) { - return url.toString().split("/").slice(-1)[0].split("?")[0].split(".") - .slice(-1)[0].toLowerCase() -} - - -function sendFile(userId, roomId, path, onSuccess, onError) { - py.callClientCoro(userId, "send_file", [roomId, path], onSuccess, onError) -}