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.
This commit is contained in:
miruka 2019-12-17 17:59:53 -04:00
parent c16731b239
commit 2cb64c5346
48 changed files with 400 additions and 430 deletions

View File

@ -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)]

View File

@ -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

View File

@ -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),
)
}
}

View File

@ -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

View File

@ -1,6 +1,5 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import "../utils.js" as Utils
Drawer {
id: drawer

View File

@ -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

View File

@ -1,6 +1,5 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import "../utils.js" as Utils
TextEdit {
id: label

View File

@ -1,5 +1,4 @@
import QtQuick 2.12
import "../utils.js" as Utils
FocusScope {
signal deselectAll()

View File

@ -1,6 +1,5 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../utils.js" as Utils
HButton {
id: tile

View File

@ -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)
) : ""
}

View File

@ -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: [

View File

@ -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
{

View File

@ -1,6 +1,5 @@
import QtQuick 2.12
import "../../Base"
import "../utils.js" as ChatJS
Banner {
color: theme.chat.unknownDevices.background

View File

@ -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"

View File

@ -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"

View File

@ -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))

View File

@ -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)) : "",

View File

@ -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 : ""

View File

@ -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,
{

View File

@ -1,7 +1,6 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../Base"
import "../../utils.js" as Utils
HBox {
color: "transparent"

View File

@ -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

View File

@ -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 ? "" : (
"<div class='sender'>" +
Utils.coloredNameHtml(model.sender_name, model.sender_id) +
utils.coloredNameHtml(model.sender_name, model.sender_id) +
"</div>"
)
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 ?
`&nbsp;<font size=${theme.fontSize.small}px></font>` :
@ -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),
)
}

View File

@ -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},

View File

@ -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)
}

View File

@ -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,

View File

@ -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
)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) => {

View File

@ -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 },

View File

@ -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.

View File

@ -1,7 +1,6 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../Base"
import "../utils.js" as Utils
HDrawer {
id: mainPane

View File

@ -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,
{

View File

@ -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)
)

View File

@ -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 },

View File

@ -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:<br>%1")
.arg(Utils.coloredNameHtml(userId, userId, userId))
.arg(utils.coloredNameHtml(userId, userId, userId))
textFormat: Text.StyledText
wrapMode: Text.Wrap

View File

@ -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 {

View File

@ -1,7 +1,6 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../Base"
import "../../utils.js" as Utils
HBox {
id: addChatBox

View File

@ -1,7 +1,6 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../Base"
import "../../utils.js" as Utils
HBox {
id: addChatBox

View File

@ -1,7 +1,6 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../Base"
import "../../utils.js" as Utils
HBox {
id: addChatBox

View File

@ -1,7 +1,6 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../Base"
import "../utils.js" as Utils
HPopup {
id: popup

View File

@ -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 },

View File

@ -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(),

View File

@ -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

323
src/qml/Utils.qml Normal file
View File

@ -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 `<font color="${nameColor(name || userId.substring(1))}">` +
escapeHtml(displayText || name || userId) +
"</font>"
}
function escapeHtml(string) {
// Replace special HTML characters by encoded alternatives
return string.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
.replace("'", "&#039;")
}
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,
)
}
}

View File

@ -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 {

View File

@ -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 `<font color="${nameColor(name || userId.substring(1))}">` +
escapeHtml(displayText || name || userId) +
"</font>"
}
function escapeHtml(string) {
// Replace special HTML characters by encoded alternatives
return string.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
.replace("'", "&#039;")
}
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)
}