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:
parent
c16731b239
commit
2cb64c5346
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import "../utils.js" as Utils
|
||||
|
||||
Drawer {
|
||||
id: drawer
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import "../utils.js" as Utils
|
||||
|
||||
TextEdit {
|
||||
id: label
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import QtQuick 2.12
|
||||
import "../utils.js" as Utils
|
||||
|
||||
FocusScope {
|
||||
signal deselectAll()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../utils.js" as Utils
|
||||
|
||||
HButton {
|
||||
id: tile
|
||||
|
|
|
@ -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)
|
||||
) : ""
|
||||
}
|
||||
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import QtQuick 2.12
|
||||
import "../../Base"
|
||||
import "../utils.js" as ChatJS
|
||||
|
||||
Banner {
|
||||
color: theme.chat.unknownDevices.background
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)) : "",
|
||||
|
||||
|
|
|
@ -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 : ""
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../../Base"
|
||||
import "../../utils.js" as Utils
|
||||
|
||||
HBox {
|
||||
color: "transparent"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ?
|
||||
` <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),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../Base"
|
||||
import "../utils.js" as Utils
|
||||
|
||||
HDrawer {
|
||||
id: mainPane
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../../Base"
|
||||
import "../../utils.js" as Utils
|
||||
|
||||
HBox {
|
||||
id: addChatBox
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../../Base"
|
||||
import "../../utils.js" as Utils
|
||||
|
||||
HBox {
|
||||
id: addChatBox
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../../Base"
|
||||
import "../../utils.js" as Utils
|
||||
|
||||
HBox {
|
||||
id: addChatBox
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import "../Base"
|
||||
import "../utils.js" as Utils
|
||||
|
||||
HPopup {
|
||||
id: popup
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
323
src/qml/Utils.qml
Normal 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("&", "&")
|
||||
.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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
307
src/qml/utils.js
307
src/qml/utils.js
|
@ -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("&", "&")
|
||||
.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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user