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:
parent
dd8c3173f6
commit
fcd9567677
|
@ -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)
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -56,3 +56,4 @@ QtObject:
|
|||
read = getFileUrlsAndSourcesJson
|
||||
write = setFileUrlsAndSourcesJson
|
||||
notify = fileUrlsAndSourcesJsonChanged
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0
|
||||
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0
|
||||
|
|
|
@ -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/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=91pnQgUZAqFJLcqM-0
|
|
@ -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/💬-Chat⎜Desktop?type=design&node-id=23155-66084&mode=design&t=VWBVK4DOUxr1BmTp-0
|
||||
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=23155-66084&mode=design&t=VWBVK4DOUxr1BmTp-0
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -20,7 +20,7 @@ Row {
|
|||
|
||||
Repeater {
|
||||
id: rptImages
|
||||
|
||||
|
||||
Item {
|
||||
height: chatImage.height
|
||||
width: chatImage.width
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue