feat: Add settings card to control link previews settings in chat input

This commit adds the link preview settings card in the chat input area and connects the settings to the controller.

Not included in this commit: Backend for the preserving the settings, syncing the settings and enforcing the settings on the backend side.

Whenever an url is detected in the chat input area, the link preview settings card is presented. This card enables the user to choose one of the following options:

1. `Show for this message` - All the link previews in the current message will be loaded without asking again. The current message can be defined as the message currently typed/pasted in the chat input. Deleting or sending the current content is resetting this setting and the link preview settings card will be presented again when a new url is detected.
2. `Always show previews` - All the link previews will be loaded automatically. The link preview settings card will not be presented again (in the current state, this settings is enabled for the lifetime of the controller. This will change once the settings are preserved and synced)
3. `Never show previews` - No link preview will be loaded. Same as the `Always show previews` option, this will be preserved for the lifetime of the controller for now.
4. Dismiss (x button) - The link preview settings card will be dismissed. It will be loaded again when a new link preview is detected

The same options can be loaded as a context menu on the link preview card.

Changes:
1. Adding `LinkPreviewSettingsCard`
2. Adding the settings context menu to `LinkPreviewSettingsCard` and `LinkPreviewMiniCard`
3. Connect settings events to the nim controller
4. Adding the controller logic for settings change
5. Adding the link preview dismiss settings flag to the preserverd properties and use it as a condition to load the settings.
6. Adding/Updating corresponding storybook pages
This commit is contained in:
Alex Jbanca 2023-10-09 11:45:16 +03:00 committed by Alex Jbanca
parent dd8c3173f6
commit fcd9567677
17 changed files with 540 additions and 67 deletions

View File

@ -10,6 +10,12 @@ import ../../../../../core/eventemitter
import ../../../../../core/unique_event_emitter
import ./link_preview_cache
type
LinkPreviewSetting* {.pure.} = enum
AlwaysAsk
Enabled
Disabled
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
@ -22,6 +28,8 @@ type
gifService: gif_service.Service
messageService: message_service.Service
linkPreviewCache: LinkPreviewCache
linkPreviewPersistentSetting: LinkPreviewSetting
linkPreviewCurrentMessageSetting: LinkPreviewSetting
proc newController*(
delegate: io_interface.AccessInterface,
@ -45,6 +53,8 @@ proc newController*(
result.gifService = gifService
result.messageService = messageService
result.linkPreviewCache = newLinkPreiewCache()
result.linkPreviewPersistentSetting = LinkPreviewSetting.AlwaysAsk
result.linkPreviewCurrentMessageSetting = LinkPreviewSetting.AlwaysAsk
proc onUrlsUnfurled(self: Controller, args: LinkPreviewV2DataArgs)
@ -89,6 +99,16 @@ proc getChatId*(self: Controller): string =
proc belongsToCommunity*(self: Controller): bool =
return self.belongsToCommunity
proc setLinkPreviewEnabledForThisMessage*(self: Controller, enabled: bool) =
self.linkPreviewCurrentMessageSetting = if enabled: LinkPreviewSetting.Enabled else: LinkPreviewSetting.Disabled
self.delegate.setAskToEnableLinkPreview(false)
proc resetLinkPreviews(self: Controller) =
self.delegate.setUrls(@[])
self.linkPreviewCache.clear()
self.linkPreviewCurrentMessageSetting = LinkPreviewSetting.AlwaysAsk
self.delegate.setAskToEnableLinkPreview(false)
proc sendImages*(self: Controller,
imagePathsAndDataJson: string,
@ -96,6 +116,7 @@ proc sendImages*(self: Controller,
replyTo: string,
preferredUsername: string = "",
linkPreviews: seq[LinkPreview]): string =
self.resetLinkPreviews()
self.chatService.sendImages(
self.chatId,
imagePathsAndDataJson,
@ -111,8 +132,8 @@ proc sendChatMessage*(self: Controller,
contentType: int,
preferredUsername: string = "",
linkPreviews: seq[LinkPreview]) =
self.chatService.sendChatMessage(
self.chatId,
self.resetLinkPreviews()
self.chatService.sendChatMessage(self.chatId,
msg,
replyTo,
contentType,
@ -165,11 +186,25 @@ proc addToRecentsGif*(self: Controller, item: GifDto) =
proc isFavorite*(self: Controller, item: GifDto): bool =
return self.gifService.isFavorite(item)
proc getLinkPreviewEnabled*(self: Controller): bool =
return self.linkPreviewPersistentSetting == LinkPreviewSetting.Enabled or self.linkPreviewCurrentMessageSetting == LinkPreviewSetting.Enabled
proc canAskToEnableLinkPreview(self: Controller): bool =
return self.linkPreviewPersistentSetting == LinkPreviewSetting.AlwaysAsk and self.linkPreviewCurrentMessageSetting == LinkPreviewSetting.AlwaysAsk
proc setText*(self: Controller, text: string) =
if(text == ""):
self.resetLinkPreviews()
return
let urls = self.messageService.getTextUrls(text)
self.delegate.setUrls(urls)
if len(urls) > 0:
let newUrls = self.linkPreviewCache.unknownUrls(urls)
let newUrls = self.linkPreviewCache.unknownUrls(urls)
let askToEnableLinkPreview = len(newUrls) > 0 and self.canAskToEnableLinkPreview()
self.delegate.setAskToEnableLinkPreview(askToEnableLinkPreview)
if self.getLinkPreviewEnabled() and len(urls) > 0:
self.messageService.asyncUnfurlUrls(newUrls)
proc linkPreviewsFromCache*(self: Controller, urls: seq[string]): Table[string, LinkPreview] =
@ -179,8 +214,22 @@ proc clearLinkPreviewCache*(self: Controller) =
self.linkPreviewCache.clear()
proc onUrlsUnfurled(self: Controller, args: LinkPreviewV2DataArgs) =
if not self.getLinkPreviewEnabled():
return
let urls = self.linkPreviewCache.add(args.linkPreviews)
self.delegate.updateLinkPreviewsFromCache(urls)
proc reloadLinkPreview*(self: Controller, url: string) =
self.messageService.asyncUnfurlUrls(@[url])
proc loadLinkPreviews*(self: Controller, urls: seq[string]) =
if self.getLinkPreviewEnabled():
self.messageService.asyncUnfurlUrls(urls)
proc setLinkPreviewEnabled*(self: Controller, enabled: bool) =
if(enabled):
self.linkPreviewPersistentSetting = LinkPreviewSetting.Enabled
self.linkPreviewCurrentMessageSetting = LinkPreviewSetting.Enabled
else:
self.linkPreviewPersistentSetting = LinkPreviewSetting.Disabled
self.linkPreviewCurrentMessageSetting = LinkPreviewSetting.Disabled
self.delegate.setAskToEnableLinkPreview(false)

View File

@ -111,5 +111,17 @@ method clearLinkPreviewCache*(self: AccessInterface) {.base.} =
method linkPreviewsFromCache*(self: AccessInterface, urls: seq[string]): Table[string, LinkPreview] {.base.} =
raise newException(ValueError, "No implementation available")
method reloadLinkPreview*(self: AccessInterface, url: string) {.base.} =
method loadLinkPreviews*(self: AccessInterface, urls: seq[string]) {.base.} =
raise newException(ValueError, "No implementation available")
method getLinkPreviewEnabled*(self: AccessInterface): bool =
raise newException(ValueError, "No implementation available")
method setLinkPreviewEnabled*(self: AccessInterface, enabled: bool) =
raise newException(ValueError, "No implementation available")
method setAskToEnableLinkPreview*(self: AccessInterface, value: bool) =
raise newException(ValueError, "No implementation available")
method setLinkPreviewEnabledForThisMessage*(self: AccessInterface, enabled: bool) =
raise newException(ValueError, "No implementation available")

View File

@ -167,5 +167,17 @@ method setUrls*(self: Module, urls: seq[string]) =
method linkPreviewsFromCache*(self: Module, urls: seq[string]): Table[string, LinkPreview] =
return self.controller.linkPreviewsFromCache(urls)
method reloadLinkPreview*(self: Module, url: string) =
self.controller.reloadLinkPreview(url)
method loadLinkPreviews*(self: Module, urls: seq[string]) =
self.controller.loadLinkPreviews(urls)
method getLinkPreviewEnabled*(self: Module): bool =
return self.controller.getLinkPreviewEnabled()
method setLinkPreviewEnabled*(self: Module, enabled: bool) =
self.controller.setLinkPreviewEnabled(enabled)
method setAskToEnableLinkPreview*(self: Module, value: bool) =
self.view.setAskToEnableLinkPreview(value)
method setLinkPreviewEnabledForThisMessage*(self: Module, value: bool) =
self.controller.setLinkPreviewEnabledForThisMessage(value)

View File

@ -56,3 +56,4 @@ QtObject:
read = getFileUrlsAndSourcesJson
write = setFileUrlsAndSourcesJson
notify = fileUrlsAndSourcesJsonChanged

View File

@ -17,6 +17,7 @@ QtObject:
preservedPropertiesVariant: QVariant
linkPreviewModel: link_preview_model.Model
linkPreviewModelVariant: QVariant
askToEnableLinkPreview: bool
proc delete*(self: View) =
self.QObject.delete
@ -40,6 +41,7 @@ QtObject:
result.preservedPropertiesVariant = newQVariant(result.preservedProperties)
result.linkPreviewModel = newLinkPreviewModel()
result.linkPreviewModelVariant = newQVariant(result.linkPreviewModel)
result.askToEnableLinkPreview = false
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -197,6 +199,17 @@ QtObject:
QtProperty[QVariant] linkPreviewModel:
read = getLinkPreviewModel
proc askToEnableLinkPreviewChanged(self: View) {.signal.}
proc getAskToEnableLinkPreview(self: View): bool {.slot.} =
return self.askToEnableLinkPreview
proc setAskToEnableLinkPreview*(self: View, value: bool) {.slot.} =
self.askToEnableLinkPreview = value
self.askToEnableLinkPreviewChanged()
QtProperty[bool] askToEnableLinkPreview:
read = getAskToEnableLinkPreview
notify = askToEnableLinkPreviewChanged
# Currently used to fetch link previews, but could be used elsewhere
proc setText*(self: View, text: string) {.slot.} =
self.delegate.setText(text)
@ -207,10 +220,36 @@ QtObject:
proc setUrls*(self: View, urls: seq[string]) =
self.linkPreviewModel.setUrls(urls)
self.updateLinkPreviewsFromCache(urls)
if(self.delegate.getLinkPreviewEnabled()):
self.updateLinkPreviewsFromCache(urls)
else:
self.linkPreviewModel.removeAllPreviewData()
proc clearLinkPreviewCache*(self: View) {.slot.} =
self.delegate.clearLinkPreviewCache()
proc reloadLinkPreview(self: View, link: string) {.slot.} =
self.delegate.reloadLinkPreview(link)
self.delegate.loadLinkPreviews(@[link])
proc loadLinkPreviews(self: View, links: seq[string]) =
self.delegate.loadLinkPreviews(links)
proc enableLinkPreview(self: View) {.slot.} =
self.delegate.setLinkPreviewEnabled(true)
let links = self.linkPreviewModel.getLinks()
self.linkPreviewModel.clearItems()
self.loadLinkPreviews(links)
proc disableLinkPreview(self: View) {.slot.} =
self.delegate.setLinkPreviewEnabled(false)
self.linkPreviewModel.removeAllPreviewData()
proc setLinkPreviewEnabledForCurrentMessage(self: View, enabled: bool) {.slot.} =
self.delegate.setLinkPreviewEnabledForThisMessage(enabled)
let links = self.linkPreviewModel.getLinks()
self.linkPreviewModel.clearItems()
self.setUrls(links)
self.loadLinkPreviews(links)
proc removeLinkPreviewData*(self: View, index: int) {.slot.} =
self.linkPreviewModel.removePreviewData(index)

View File

@ -213,8 +213,17 @@ QtObject:
defer: modelIndex.delete
self.dataChanged(modelIndex, modelIndex)
proc removeAllPreviewData*(self: Model) {.slot.} =
for i in 0 ..< self.items.len:
self.removePreviewData(i)
proc getUnfuledLinkPreviews*(self: Model): seq[LinkPreview] =
result = @[]
for item in self.items:
if item.unfurled and item.linkPreview.hostName != "":
result.add(item.linkPreview)
result.add(item.linkPreview)
proc getLinks*(self: Model): seq[string] =
result = @[]
for item in self.items:
result.add(item.linkPreview.url)

View File

@ -1,24 +1,81 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Storybook 1.0
import StatusQ.Core.Theme 0.1
import shared.controls.chat 1.0
Page {
Rectangle {
id: wrapper
anchors.fill: parent
color: Theme.palette.statusChatInput.secondaryBackgroundColor
SplitView {
ChatInputLinksPreviewArea {
id: chatInputLinkPreviewsArea
anchors.centerIn: parent
width: parent.width
imagePreviewModel: ["https://picsum.photos/200/300?random=1", "https://picsum.photos/200/300?random=1"]
linkPreviewModel: linkPreviewListModel
onLinkRemoved: linkPreviewListModel.remove(index)
Logs { id: logs }
orientation: Qt.Vertical
SplitView {
SplitView.fillWidth: true
SplitView.fillHeight: true
Pane {
SplitView.fillWidth: true
Rectangle {
id: wrapper
anchors.fill: parent
color: Theme.palette.statusChatInput.secondaryBackgroundColor
ChatInputLinksPreviewArea {
id: chatInputLinkPreviewsArea
anchors.centerIn: parent
width: parent.width
imagePreviewArray: ["https://picsum.photos/200/300?random=1", "https://picsum.photos/200/300?random=1"]
linkPreviewModel: showLinkPreviewSettings ? emptyModel : linkPreviewListModel
showLinkPreviewSettings: !linkPreviewEnabledSwitch.checked
visible: hasContent
onImageRemoved: (index) => logs.logEvent("ChatInputLinksPreviewArea::onImageRemoved: " + index)
onImageClicked: (chatImage) => logs.logEvent("ChatInputLinksPreviewArea::onImageClicked: " + chatImage)
onLinkReload: (link) => logs.logEvent("ChatInputLinksPreviewArea::onLinkReload: " + link)
onLinkClicked: (link) => logs.logEvent("ChatInputLinksPreviewArea::onLinkClicked: " + link)
onEnableLinkPreview: () => {
linkPreviewEnabledSwitch.checked = true
logs.logEvent("ChatInputLinksPreviewArea::onEnableLinkPreview")
}
onEnableLinkPreviewForThisMessage: () => logs.logEvent("ChatInputLinksPreviewArea::onEnableLinkPreviewForThisMessage")
onDisableLinkPreview: () => logs.logEvent("ChatInputLinksPreviewArea::onDisableLinkPreview")
onDismissLinkPreviewSettings: () => logs.logEvent("ChatInputLinksPreviewArea::onDismissLinkPreviewSettings")
onDismissLinkPreview: (index) => logs.logEvent("ChatInputLinksPreviewArea::onDismissLinkPreview: " + index)
}
}
}
Pane {
SplitView.preferredWidth: 300
SplitView.fillHeight: true
ColumnLayout {
Label {
text: "Links preview enabled"
}
Switch {
id: linkPreviewEnabledSwitch
}
}
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 100
SplitView.preferredHeight: 200
logsView.logText: logs.logText
}
ListModel {
id: emptyModel
}
ListModel {
@ -143,4 +200,4 @@ Page {
// category: Panels
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0

View File

@ -0,0 +1,30 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Core.Theme 0.1
import shared.controls.chat 1.0
Pane {
id: root
layer.enabled: true
layer.samples: 4
background: Rectangle {
color: Theme.palette.statusChatInput.secondaryBackgroundColor
}
LinkPreviewSettingsCard {
id: previewMiniCard
anchors.centerIn: parent
onDismiss: ToolTip.show(qsTr("Link previews disabled for this message"), 1000)
onEnableLinkPreviewForThisMessage: ToolTip.show(qsTr("Link previews enabled for this message"), 1000)
onEnableLinkPreview: ToolTip.show(qsTr("Link previews enabled"), 1000)
onDisableLinkPreview: ToolTip.show(qsTr("Link previews disabled"), 1000)
}
}
//category: Controls
//https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=22341-184809&mode=design&t=91pnQgUZAqFJLcqM-0

View File

@ -94,31 +94,11 @@ SplitView {
sourceComponent: StatusChatInput {
id: chatInput
property var globalUtils: globalUtilsMock.globalUtils
property string unformattedText: textInput.getText(0, textInput.length)
property string unformattedText: chatInput.textInput.getText(0, chatInput.textInput.length)
onUnformattedTextChanged: {
textEditConnection.enabled = false
var words = unformattedText.split(" ")
fakeLinksModel.clear()
words.forEach(function(word){
if(Utils.isURL(word)) {
fakeLinksModel.append({
url: encodeURI(word),
unfurled: Math.floor(Math.random() * 2),
immutable: false,
hostname: Math.floor(Math.random() * 2) ? "youtube.com" : "",
title: "PSY - GANGNAM STYLE(강남스타일) M/V",
description: "This is the description of the link",
linkType: Math.floor(Math.random() * 3),
thumbnailWidth: 480,
thumbnailHeight: 360,
thumbnailUrl: "https://picsum.photos/480/360?random=1",
thumbnailDataUri: ""
})
}
})
d.loadLinkPreviews(unformattedText)
textEditConnection.enabled = true
}
@ -133,6 +113,13 @@ SplitView {
enabled: enabledCheckBox.checked
linkPreviewModel: fakeLinksModel
askToEnableLinkPreview: askToEnableLinkPreviewSwitch.checked
onAskToEnableLinkPreviewChanged: {
if(askToEnableLinkPreview) {
fakeLinksModel.clear()
d.loadLinkPreviews(unformattedText)
}
}
usersStore: QtObject {
readonly property var usersModel: fakeUsersModel
}
@ -141,6 +128,26 @@ SplitView {
logs.logEvent("StatusChatInput::sendMessage", ["PlainText"], [globalUtilsMock.globalUtils.plainText(chatInput.getTextWithPublicKeys())])
logs.logEvent("StatusChatInput::sendMessage", ["RawText"], [chatInput.textInput.text])
}
onEnableLinkPreviewForThisMessage: {
linkPreviewSwitch.checked = true
askToEnableLinkPreviewSwitch.checked = false
}
onEnableLinkPreview: {
linkPreviewSwitch.checked = true
askToEnableLinkPreviewSwitch.checked = false
}
onDisableLinkPreview: {
linkPreviewSwitch.checked = false
askToEnableLinkPreviewSwitch.checked = false
}
onDismissLinkPreviewSettings: {
askToEnableLinkPreviewSwitch.checked = false
linkPreviewSwitch.checked = false
}
onDismissLinkPreview: (index) => {
fakeLinksModel.setProperty(index, "unfurled", false)
fakeLinksModel.setProperty(index, "immutable", true)
}
}
}
@ -152,6 +159,36 @@ SplitView {
logsView.logText: logs.logText
}
QtObject {
id: d
property bool linkPreviewsEnabled: linkPreviewSwitch.checked && !askToEnableLinkPreviewSwitch.checked
onLinkPreviewsEnabledChanged: {
loadLinkPreviews(chatInputLoader.item ? chatInputLoader.item.unformattedText : "")
}
function loadLinkPreviews(text) {
var words = text.split(" ")
fakeLinksModel.clear()
words.forEach(function(word){
if(Utils.isURL(word)) {
fakeLinksModel.append({
url: encodeURI(word),
unfurled: d.linkPreviewsEnabled,
immutable: !d.linkPreviewsEnabled,
hostname: Math.floor(Math.random() * 2) ? "youtube.com" : "",
title: "PSY - GANGNAM STYLE(강남스타일) M/V",
description: "This is the description of the link",
linkType: Math.floor(Math.random() * 3),
thumbnailWidth: 480,
thumbnailHeight: 360,
thumbnailUrl: "https://picsum.photos/480/360?random=1",
thumbnailDataUri: ""
})
}
})
}
}
}
Pane {
@ -207,6 +244,18 @@ SplitView {
text: "Links"
Layout.fillWidth: true
}
Switch {
id: linkPreviewSwitch
text: "Link Preview enabled"
}
Switch {
id: askToEnableLinkPreviewSwitch
text: "Ask to enable Link Preview"
checked: true
}
ComboBox {
id: linksNb
editable: true
@ -245,4 +294,4 @@ SplitView {
// category: Components
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=23155-66084&mode=design&t=VWBVK4DOUxr1BmTp-0
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-ChatDesktop?type=design&node-id=23155-66084&mode=design&t=VWBVK4DOUxr1BmTp-0

View File

@ -130,17 +130,17 @@ Item {
chatInput.validateImagesAndShowImageArea(filesList)
}
function restoreInputState() {
function restoreInputState(textInput) {
if (!d.activeChatContentModule) {
chatInput.setText("")
chatInput.clear()
chatInput.resetReplyArea()
chatInput.resetImageArea()
return
}
// Restore message text
chatInput.setText(d.activeChatContentModule.inputAreaModule.preservedProperties.text)
chatInput.setText(textInput)
d.restoreInputReply()
d.restoreInputAttachments()
@ -154,9 +154,14 @@ Item {
}
onActiveChatContentModuleChanged: {
let preservedText = ""
if (d.activeChatContentModule) {
preservedText = d.activeChatContentModule.inputAreaModule.preservedProperties.text
}
d.activeChatContentModule.inputAreaModule.clearLinkPreviewCache()
// Call later to make sure activeUsersStore and activeMessagesStore bindings are updated
Qt.callLater(d.restoreInputState)
Qt.callLater(d.restoreInputState, preservedText)
}
}
@ -243,7 +248,12 @@ Item {
store: root.rootStore
usersStore: d.activeUsersStore
linkPreviewModel: d.activeChatContentModule.inputAreaModule.linkPreviewModel
askToEnableLinkPreview: {
if(!d.activeChatContentModule || !d.activeChatContentModule.inputAreaModule || !d.activeChatContentModule.inputAreaModule.preservedProperties)
return false
return d.activeChatContentModule.inputAreaModule.askToEnableLinkPreview
}
textInput.placeholderText: {
if (!channelPostRestrictions.visible) {
if (d.activeChatContentModule.chatDetails.blocked)
@ -315,6 +325,11 @@ Item {
}
onLinkPreviewReloaded: (link) => d.activeChatContentModule.inputAreaModule.reloadLinkPreview(link)
onEnableLinkPreview: () => d.activeChatContentModule.inputAreaModule.enableLinkPreview()
onDisableLinkPreview: () => d.activeChatContentModule.inputAreaModule.disableLinkPreview()
onEnableLinkPreviewForThisMessage: () => d.activeChatContentModule.inputAreaModule.setLinkPreviewEnabledForCurrentMessage(true)
onDismissLinkPreviewSettings: () => d.activeChatContentModule.inputAreaModule.setLinkPreviewEnabledForCurrentMessage(false)
onDismissLinkPreview: (index) => d.activeChatContentModule.inputAreaModule.removeLinkPreviewData(index)
}
ChatPermissionQualificationPanel {

View File

@ -8,22 +8,44 @@ import StatusQ.Core 0.1
import shared.status 1.0
import shared.controls.chat 1.0
import utils 1.0
import SortFilterProxyModel 0.2
Control {
id: root
required property var imagePreviewModel
required property var imagePreviewArray
/*
Expected roles:
string title
string url
bool unfurled
bool immutable
string hostname
string description
int linkType
int thumbnailWidth
int thumbnailHeight
string thumbnailUrl
string thumbnailDataUri
*/
required property var linkPreviewModel
required property bool showLinkPreviewSettings
readonly property alias hoveredUrl: d.hoveredUrl
readonly property int contentItemsCount: imagePreviewModel.length + d.filteredModel.count
readonly property bool hasContent: imagePreviewArray.length > 0 || showLinkPreviewSettings || linkPreviewRepeater.count > 0
signal imageRemoved(int index)
signal imageClicked(var chatImage)
signal linkReload(string link)
signal linkClicked(string link)
signal linkRemoved(string link)
signal enableLinkPreview()
signal enableLinkPreviewForThisMessage()
signal disableLinkPreview()
signal dismissLinkPreviewSettings()
signal dismissLinkPreview(int index)
horizontalPadding: 12
topPadding: 12
@ -60,6 +82,8 @@ Control {
contentWidth: layout.width
contentHeight: layout.height
onFlickStarted: settingsContextMenu.close()
RowLayout {
id: layout
spacing: 8
@ -67,12 +91,13 @@ Control {
id: imageArea
Layout.preferredHeight: 64
spacing: layout.spacing
imageSource: imagePreviewModel
imageSource: imagePreviewArray
onImageClicked: root.imageClicked(chatImage)
onImageRemoved: root.imageRemoved(index)
visible: !!imagePreviewModel && imagePreviewModel.length > 0
visible: !!imagePreviewArray && imagePreviewArray.length > 0
}
Repeater {
id: linkPreviewRepeater
model: d.filteredModel
delegate: LinkPreviewMiniCard {
// Model properties
@ -104,9 +129,10 @@ Control {
unfurled && hostname === "" ? LinkPreviewMiniCard.State.LoadingFailed :
!unfurled ? LinkPreviewMiniCard.State.Loading : LinkPreviewMiniCard.State.Invalid
onClose: root.linkPreviewModel.removePreviewData(d.filteredModel.mapToSource(index))
onClose: root.dismissLinkPreview(d.filteredModel.mapToSource(index))
onRetry: root.linkReload(url)
onClicked: root.linkClicked(url)
onRightClicked: settingsContextMenu.popup()
onContainsMouseChanged: {
if (containsMouse) {
d.hoveredUrl = url
@ -121,6 +147,14 @@ Control {
}
}
}
LinkPreviewSettingsCard {
id: settingsCard
visible: root.showLinkPreviewSettings
onDismiss: root.dismissLinkPreviewSettings()
onEnableLinkPreviewForThisMessage: root.enableLinkPreviewForThisMessage()
onEnableLinkPreview: root.enableLinkPreview()
onDisableLinkPreview: root.disableLinkPreview()
}
}
}
}
@ -157,4 +191,12 @@ Control {
]
}
}
LinkPreviewSettingsCard.ContextMenu {
id: settingsContextMenu
onEnableLinkPreviewForThisMessage: root.enableLinkPreviewForThisMessage()
onEnableLinkPreview: root.enableLinkPreview()
onDisableLinkPreview: root.disableLinkPreview()
}
}

View File

@ -42,6 +42,7 @@ CalloutCard {
signal close()
signal retry()
signal clicked(var eventPoint)
signal rightClicked(var eventPoint)
implicitWidth: 260
implicitHeight: 64
@ -181,7 +182,6 @@ CalloutCard {
icon: "tiny/chevron-right"
color: Theme.palette.baseColor1
visible: secondTitleText.visible
}
StatusBaseText {
id: secondTitleText
@ -246,4 +246,8 @@ CalloutCard {
target: background
onTapped: root.clicked(eventPoint)
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: root.rightClicked(eventPoint)
}
}

View File

@ -0,0 +1,144 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import utils 1.0
CalloutCard {
id: root
signal dismiss()
signal enableLinkPreviewForThisMessage()
signal enableLinkPreview()
signal disableLinkPreview()
implicitHeight: 64
borderWidth: 0
topPadding: 13
bottomPadding: 13
horizontalPadding: Style.current.padding
contentItem: RowLayout {
spacing: Style.current.halfPadding
ColumnLayout {
spacing: 0
Layout.fillHeight: true
Layout.fillWidth: true
StatusBaseText {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.fillHeight: true
font.pixelSize: Style.current.additionalTextSize
font.weight: Font.Medium
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 1
text: qsTr("Show link previews?")
}
StatusBaseText {
Layout.fillWidth: true
Layout.fillHeight: true
font.pixelSize: Style.current.additionalTextSize
color: Theme.palette.baseColor1
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 1
text: qsTr("A preview of your link will be shown here before you send it")
}
}
ComboBox {
id: optionsComboBox
Layout.leftMargin: 12
Layout.preferredHeight: 38
leftPadding: 12
rightPadding: 12
hoverEnabled: true
flat: true
contentItem: RowLayout {
spacing: Style.current.halfPadding
StatusBaseText {
Layout.fillWidth: true
Layout.fillHeight: true
verticalAlignment: Text.AlignVCenter
font.pixelSize: Style.current.additionalTextSize
elide: Text.ElideRight
text: qsTr("Options")
color: Theme.palette.baseColor1
}
StatusIcon {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
icon: "chevron-down"
color: Theme.palette.baseColor1
}
}
background: Rectangle {
border.width: 1
border.color: Theme.palette.directColor7
color: optionsComboBox.popup.visible ? Theme.palette.baseColor2 : "transparent"
radius: Style.current.radius
HoverHandler {
cursorShape: Qt.PointingHandCursor
enabled: optionsComboBox.enabled
}
}
popup: ContextMenu {
y: - (height + 4)
onEnableLinkPreviewForThisMessage: root.enableLinkPreviewForThisMessage()
onEnableLinkPreview: root.enableLinkPreview()
onDisableLinkPreview: root.disableLinkPreview()
}
indicator: null
}
StatusFlatRoundButton {
id: closeButton
Layout.preferredHeight: 38
Layout.preferredWidth: 38
type: StatusFlatRoundButton.Type.Secondary
icon.name: "close"
icon.color: Theme.palette.directColor1
onClicked: root.dismiss()
}
}
component ContextMenu: StatusMenu {
id: contextMenu
signal enableLinkPreviewForThisMessage()
signal enableLinkPreview()
signal disableLinkPreview()
hideDisabledItems: false
StatusAction {
text: qsTr("Link previews")
enabled: false
}
StatusAction {
text: qsTr("Show for this message")
icon.name: "show"
onTriggered: contextMenu.enableLinkPreviewForThisMessage()
}
StatusAction {
text: qsTr("Always show previews")
icon.name: "show"
onTriggered: contextMenu.enableLinkPreview()
}
StatusMenuSeparator { }
StatusAction {
text: qsTr("Never show previews")
icon.name: "hide"
type: StatusAction.Type.Danger
onTriggered: contextMenu.disableLinkPreview()
}
}
}

View File

@ -5,6 +5,7 @@ FetchMoreMessagesButton 1.0 FetchMoreMessagesButton.qml
GapComponent 1.0 GapComponent.qml
LinkPreviewCard 1.0 LinkPreviewCard.qml
LinkPreviewMiniCard 1.0 LinkPreviewMiniCard.qml
LinkPreviewSettingsCard 1.0 LinkPreviewSettingsCard.qml
UsernameLabel 1.0 UsernameLabel.qml
UserProfileCard 1.0 UserProfileCard.qml
DateGroup 1.0 DateGroup.qml

View File

@ -28,9 +28,13 @@ Rectangle {
signal stickerSelected(string hashId, string packId, string url)
signal sendMessage(var event)
signal keyUpPress()
signal linkPreviewRemoved(string link)
signal linkPreviewReloaded(string link)
signal enableLinkPreview()
signal enableLinkPreviewForThisMessage()
signal disableLinkPreview()
signal dismissLinkPreviewSettings()
signal dismissLinkPreview(int index)
property var usersStore
property var store
@ -61,6 +65,8 @@ Rectangle {
property var linkPreviewModel: null
property bool askToEnableLinkPreview: false
property var imageErrorMessageLocation: StatusChatInput.ImageErrorMessageLocation.Top // TODO: Remove this property?
property alias suggestions: suggestionsBox
@ -1179,11 +1185,12 @@ Rectangle {
ChatInputLinksPreviewArea {
id: linkPreviewArea
Layout.fillWidth: true
visible: contentItemsCount > 0
visible: hasContent
horizontalPadding: 12
topPadding: 12
imagePreviewModel: control.fileUrlsAndSources
imagePreviewArray: control.fileUrlsAndSources
linkPreviewModel: control.linkPreviewModel
showLinkPreviewSettings: control.askToEnableLinkPreview
onImageRemoved: (index) => {
//Just do a copy and replace the whole thing because it's a plain JS array and thre's no signal when a single item is removed
let urls = control.fileUrlsAndSources
@ -1194,8 +1201,12 @@ Rectangle {
}
onImageClicked: (chatImage) => Global.openImagePopup(chatImage)
onLinkReload: (link) => control.linkPreviewReloaded(link)
onLinkRemoved: (link) => control.linkPreviewRemoved(link)
onLinkClicked: (link) => Global.openLink(link)
onEnableLinkPreview: () => control.enableLinkPreview()
onEnableLinkPreviewForThisMessage: () => control.enableLinkPreviewForThisMessage()
onDisableLinkPreview: () => control.disableLinkPreview()
onDismissLinkPreviewSettings: () => control.dismissLinkPreviewSettings()
onDismissLinkPreview: (index) => control.dismissLinkPreview(index)
}
RowLayout {

View File

@ -20,7 +20,7 @@ Row {
Repeater {
id: rptImages
Item {
height: chatImage.height
width: chatImage.width

View File

@ -117,9 +117,7 @@ Item {
}
const newStackSize = Math.ceil(root.maxStackSize / 2)
print("Reducing undo stack to " + newStackSize + " items")
for(var i = 1; i <= newStackSize; i++) {
print("Removing " + Math.ceil(root.maxStackSize / newStackSize) + " items from index " + i)
d.undoStack.splice(i, Math.ceil(root.maxStackSize / newStackSize))
}
}