mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-21 20:09:37 +00:00
feat: Generate link previews in StatusChatInput - introduce ChatInputLinksPreviewArea
This component is a wrapper for LinkPreviewMiniCard and StatusChatInputImageArea. The purpose of this component is to arrange the cards in a row layout and provide scrolling behaviour. This component also has an opacity mask that will provide a fade out appearance when the items are scrolled out of view. + adding storybook page + integrate ChatInputLinksPreviewArea in StatusChatInput
This commit is contained in:
parent
2c8ad61947
commit
3ce9d66d25
154
storybook/pages/ChatInputLinksPreviewAreaPage.qml
Normal file
154
storybook/pages/ChatInputLinksPreviewAreaPage.qml
Normal file
@ -0,0 +1,154 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import shared.controls.chat 1.0
|
||||
|
||||
SplitView {
|
||||
Pane {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
Rectangle {
|
||||
id: wrapper
|
||||
anchors.fill: parent
|
||||
color: Theme.palette.statusChatInput.secondaryBackgroundColor
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Pane {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: linkPreviewListModel
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: true
|
||||
immutable: false
|
||||
hostname: "youtube.com"
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=1"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: false
|
||||
immutable: false
|
||||
hostname: "youtube.com"
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=2"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: true
|
||||
immutable: false
|
||||
hostname: ""
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=3"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: true
|
||||
hostname: "youtube.com"
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=4"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: true
|
||||
immutable: false
|
||||
hostname: "youtube.com"
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=5"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: true
|
||||
hostname: "youtube.com"
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=6"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: true
|
||||
immutable: false
|
||||
hostname: "youtube.com"
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=7"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: true
|
||||
immutable: false
|
||||
hostname: "youtube.com"
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=8"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
ListElement {
|
||||
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
|
||||
unfurled: true
|
||||
immutable: false
|
||||
hostname: "youtube.com"
|
||||
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
|
||||
description: ""
|
||||
linkType: 0
|
||||
thumbnailWidth: 480
|
||||
thumbnailHeight: 360
|
||||
thumbnailUrl: "https://picsum.photos/480/360?random=9"
|
||||
thumbnailDataUri: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Panels
|
||||
|
||||
// https://www.figma.com/file/Mr3rqxxgKJ2zMQ06UAKiWL/💬-Chat⎜Desktop?type=design&node-id=22341-184809&mode=design&t=VWBVK4DOUxr1BmTp-0
|
@ -75,6 +75,10 @@ SplitView {
|
||||
id: fakeUsersModel
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: fakeLinksModel
|
||||
}
|
||||
|
||||
SplitView {
|
||||
orientation: Qt.Vertical
|
||||
SplitView.fillWidth: true
|
||||
@ -85,11 +89,13 @@ SplitView {
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: chatInputLoader
|
||||
active: rootStoreMock.ready && globalUtilsMock.ready
|
||||
sourceComponent: StatusChatInput {
|
||||
id: chatInput
|
||||
property var globalUtils: globalUtilsMock.globalUtils
|
||||
enabled: enabledCheckBox.checked
|
||||
linkPreviewModel: fakeLinksModel
|
||||
usersStore: QtObject {
|
||||
readonly property var usersModel: fakeUsersModel
|
||||
}
|
||||
@ -122,18 +128,92 @@ SplitView {
|
||||
text: "enabled"
|
||||
checked: true
|
||||
}
|
||||
MenuSeparator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
UsersModelEditor {
|
||||
id: modelEditor
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: fakeUsersModel
|
||||
|
||||
onRemoveClicked: fakeUsersModel.remove(index, 1)
|
||||
onRemoveAllClicked: fakeUsersModel.clear()
|
||||
onAddClicked: fakeUsersModel.append(modelEditor.getNewUser(fakeUsersModel.count))
|
||||
TabBar {
|
||||
id: bar
|
||||
TabButton {
|
||||
text: "Attachments"
|
||||
}
|
||||
TabButton {
|
||||
text: "Users"
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
currentIndex: bar.currentIndex
|
||||
ColumnLayout {
|
||||
id: attachmentsTab
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Label {
|
||||
text: "Images"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ComboBox {
|
||||
id: imageNb
|
||||
editable: true
|
||||
model: 20
|
||||
validator: IntValidator {bottom: 0; top: 20;}
|
||||
focus: true
|
||||
onCurrentIndexChanged: {
|
||||
if(!chatInputLoader.item)
|
||||
return
|
||||
const urls = []
|
||||
for (let i = 0; i < imageNb.currentIndex ; i++) {
|
||||
urls.push("https://picsum.photos/200/300?random=" + i)
|
||||
}
|
||||
console.log(urls.length)
|
||||
chatInputLoader.item.fileUrlsAndSources = urls
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: "Links"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ComboBox {
|
||||
id: linksNb
|
||||
editable: true
|
||||
model: 20
|
||||
validator: IntValidator {bottom: 0; top: 20;}
|
||||
onCurrentIndexChanged: {
|
||||
if(!chatInputLoader.item)
|
||||
return
|
||||
chatInputLoader.item.textInput.clear()
|
||||
fakeLinksModel.clear()
|
||||
for (let i = 0; i < linksNb.currentIndex ; i++) {
|
||||
const url = "https://www.youtube.com/watch?v=9bZkp7q19f0" + Math.floor(Math.random() * 100)
|
||||
chatInputLoader.item.textInput.append(url + "\n")
|
||||
fakeLinksModel.append({
|
||||
url: url,
|
||||
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: ""
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
UsersModelEditor {
|
||||
id: modelEditor
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: fakeUsersModel
|
||||
|
||||
onRemoveClicked: fakeUsersModel.remove(index, 1)
|
||||
onRemoveAllClicked: fakeUsersModel.clear()
|
||||
onAddClicked: fakeUsersModel.append(modelEditor.getNewUser(fakeUsersModel.count))
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: "Attachments"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -215,31 +215,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// This is a non-designed preview of unfurled urls.
|
||||
// Should be replaced with a proper UI when it's ready.
|
||||
//
|
||||
// StatusListView {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.maximumHeight: 200
|
||||
// Layout.margins: Style.current.smallPadding
|
||||
|
||||
// // For a vertical list bind the imlicitHeight to contentHeight
|
||||
// implicitHeight: contentHeight
|
||||
// spacing: 10
|
||||
|
||||
// model: d.activeChatContentModule.inputAreaModule.linkPreviewModel
|
||||
|
||||
// delegate: StatusBaseText {
|
||||
// width: ListView.view.width
|
||||
// wrapMode: Text.WordWrap
|
||||
// text: {
|
||||
// const icon = unfurled ? (hostname !== "" ? '✅' : '❌') : '👀'
|
||||
// const thumbnailInfo = `thumbnail: (${thumbnailWidth}*${thumbnailHeight}, url: ${thumbnailUrl.length} symbols, data: ${thumbnailDataUri.length} symbols)`
|
||||
// return `${icon} ${url} (hostname: ${hostname}): ${title}\ndescription: ${description}\n${thumbnailInfo}`
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Style.current.smallPadding
|
||||
@ -268,6 +243,7 @@ Item {
|
||||
|
||||
store: root.rootStore
|
||||
usersStore: d.activeUsersStore
|
||||
linkPreviewModel: d.activeChatContentModule.inputAreaModule.linkPreviewModel
|
||||
|
||||
textInput.placeholderText: {
|
||||
if (!channelPostRestrictions.visible) {
|
||||
@ -290,9 +266,10 @@ Item {
|
||||
chatType: root.activeChatType
|
||||
|
||||
textInput.onTextChanged: {
|
||||
d.updateLinkPreviews()
|
||||
if (!!d.activeChatContentModule)
|
||||
if (!!d.activeChatContentModule) {
|
||||
d.activeChatContentModule.inputAreaModule.preservedProperties.text = textInput.text
|
||||
d.updateLinkPreviews()
|
||||
}
|
||||
}
|
||||
|
||||
onReplyMessageIdChanged: {
|
||||
@ -337,6 +314,9 @@ Item {
|
||||
onKeyUpPress: {
|
||||
d.activeMessagesStore.setEditModeOnLastMessage(root.rootStore.userProfileInst.pubKey)
|
||||
}
|
||||
|
||||
onLinkPreviewRemoved: (link) => d.activeChatContentModule.inputAreaModule.removeLinkPreview(link)
|
||||
onLinkPreviewReloaded: (link) => d.activeChatContentModule.inputAreaModule.reloadLinkPreview(link)
|
||||
}
|
||||
|
||||
ChatPermissionQualificationPanel {
|
||||
|
153
ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml
Normal file
153
ui/imports/shared/controls/chat/ChatInputLinksPreviewArea.qml
Normal file
@ -0,0 +1,153 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
import shared.status 1.0
|
||||
import shared.controls.chat 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
Control {
|
||||
id: root
|
||||
|
||||
required property var imagePreviewModel
|
||||
required property var linkPreviewModel
|
||||
|
||||
readonly property alias hoveredUrl: d.hoveredUrl
|
||||
readonly property int contentItemsCount: imagePreviewModel.length + d.filteredModel.count
|
||||
|
||||
signal imageRemoved(int index)
|
||||
signal imageClicked(var chatImage)
|
||||
signal linkReload(string link)
|
||||
signal linkClicked(string link)
|
||||
signal linkRemoved(string link)
|
||||
|
||||
horizontalPadding: 12
|
||||
topPadding: 12
|
||||
|
||||
contentItem: Item {
|
||||
id: opacityMaskWrapper
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
implicitWidth: flickable.implicitWidth
|
||||
implicitHeight: flickable.implicitHeight
|
||||
|
||||
opacity: 0
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root.leftPadding
|
||||
anchors.rightMargin: root.rightPadding
|
||||
anchors.bottomMargin: root.bottomPadding
|
||||
anchors.topMargin: root.topPadding
|
||||
|
||||
implicitHeight: contentHeight
|
||||
implicitWidth: contentWidth
|
||||
|
||||
contentWidth: layout.width
|
||||
contentHeight: layout.height
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
spacing: 8
|
||||
StatusChatInputImageArea {
|
||||
id: imageArea
|
||||
Layout.preferredHeight: 64
|
||||
spacing: layout.spacing
|
||||
imageSource: imagePreviewModel
|
||||
onImageClicked: root.imageClicked(chatImage)
|
||||
onImageRemoved: root.imageRemoved(index)
|
||||
visible: !!imagePreviewModel && imagePreviewModel.length > 0
|
||||
}
|
||||
Repeater {
|
||||
model: d.filteredModel
|
||||
delegate: LinkPreviewMiniCard {
|
||||
// Model properties
|
||||
required property string title
|
||||
required property string url
|
||||
required property bool unfurled
|
||||
required property bool immutable
|
||||
required property string hostname
|
||||
required property string description
|
||||
required property int linkType
|
||||
required property int thumbnailWidth
|
||||
required property int thumbnailHeight
|
||||
required property string thumbnailUrl
|
||||
required property string thumbnailDataUri
|
||||
|
||||
required property int index
|
||||
|
||||
Layout.preferredHeight: 64
|
||||
|
||||
titleStr: title
|
||||
domain: hostname //TODO: use domain when available
|
||||
favIconUrl: thumbnailImageUrl //TODO: use favicon when available
|
||||
communityName: "" //TODO: add community info when available
|
||||
channelName: "" //TODO: add community info when available
|
||||
|
||||
thumbnailImageUrl: thumbnailDataUri.length > 0 ? thumbnailDataUri : thumbnailUrl
|
||||
type: linkType === 0 ? LinkPreviewMiniCard.Type.Link : LinkPreviewMiniCard.Type.Image
|
||||
previewState: unfurled && hostname != "" ? LinkPreviewMiniCard.State.Loaded :
|
||||
unfurled && hostname === "" ? LinkPreviewMiniCard.State.LoadingFailed :
|
||||
!unfurled ? LinkPreviewMiniCard.State.Loading : LinkPreviewMiniCard.State.Invalid
|
||||
|
||||
onClose: root.linkRemoved(url)
|
||||
onRetry: root.linkReload(url)
|
||||
onClicked: root.linkClicked(url)
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
d.hoveredUrl = url
|
||||
} else if (d.hoveredUrl === url) {
|
||||
d.hoveredUrl = ""
|
||||
}
|
||||
}
|
||||
Component.onDestruction: {
|
||||
if(d.hoveredUrl === url) {
|
||||
d.hoveredUrl = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LinearGradient {
|
||||
id: horizontalClipMask
|
||||
anchors.fill: opacityMaskWrapper
|
||||
visible: false
|
||||
start: Qt.point(0 , 0)
|
||||
end: Qt.point(horizontalClipMask.width, 0)
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "transparent" }
|
||||
GradientStop { position: root.horizontalPadding / horizontalClipMask.width; color: "white" }
|
||||
GradientStop { position: 1 - root.horizontalPadding / horizontalClipMask.width; color: "white" }
|
||||
GradientStop { position: 1; color: "transparent" }
|
||||
}
|
||||
}
|
||||
OpacityMask {
|
||||
anchors.fill: opacityMaskWrapper
|
||||
source: opacityMaskWrapper
|
||||
maskSource: horizontalClipMask
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property string hoveredUrl: ""
|
||||
property SortFilterProxyModel filteredModel: SortFilterProxyModel {
|
||||
id: filteredModel
|
||||
sourceModel: root.linkPreviewModel
|
||||
filters: [
|
||||
ExpressionFilter {
|
||||
expression: { return !model.immutable || model.unfurled } // Filter out immutable links that haven't been unfurled yet
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
CalloutCard 1.0 CalloutCard.qml
|
||||
CalloutOpacityMask 1.0 CalloutOpacityMask.qml
|
||||
ChatInputLinksPreviewArea 1.0 ChatInputLinksPreviewArea.qml
|
||||
FetchMoreMessagesButton 1.0 FetchMoreMessagesButton.qml
|
||||
GapComponent 1.0 GapComponent.qml
|
||||
LinkPreviewCard 1.0 LinkPreviewCard.qml
|
||||
|
@ -6,6 +6,7 @@ import QtQuick.Dialogs 1.3
|
||||
import utils 1.0
|
||||
|
||||
import shared 1.0
|
||||
import shared.controls.chat 1.0
|
||||
import shared.panels 1.0
|
||||
import shared.popups 1.0
|
||||
import shared.stores 1.0
|
||||
@ -27,6 +28,8 @@ Rectangle {
|
||||
signal stickerSelected(string hashId, string packId, string url)
|
||||
signal sendMessage(var event)
|
||||
signal keyUpPress()
|
||||
signal linkPreviewRemoved(string link)
|
||||
signal linkPreviewReloaded(string link)
|
||||
|
||||
property var usersStore
|
||||
property var store
|
||||
@ -56,6 +59,8 @@ Rectangle {
|
||||
|
||||
property var fileUrlsAndSources: []
|
||||
|
||||
property var linkPreviewModel: null
|
||||
|
||||
property var imageErrorMessageLocation: StatusChatInput.ImageErrorMessageLocation.Top // TODO: Remove this property?
|
||||
|
||||
property alias suggestions: suggestionsBox
|
||||
@ -845,7 +850,6 @@ Rectangle {
|
||||
function resetImageArea() {
|
||||
isImage = false;
|
||||
control.fileUrlsAndSources = []
|
||||
imageArea.imageSource = [];
|
||||
for (let i=0; i<validators.children.length; i++) {
|
||||
const validator = validators.children[i]
|
||||
validator.images = []
|
||||
@ -866,8 +870,8 @@ Rectangle {
|
||||
if (!imagePaths || !imagePaths.length) {
|
||||
return []
|
||||
}
|
||||
// needed because imageArea.imageSource is not a normal js array
|
||||
const existing = (imageArea.imageSource || []).map(x => x.toString())
|
||||
// needed because control.fileUrlsAndSources is not a normal js array
|
||||
const existing = (control.fileUrlsAndSources || []).map(x => x.toString())
|
||||
let validImages = Utils.deduplicate(existing.concat(imagePaths))
|
||||
for (let i=0; i<validators.children.length; i++) {
|
||||
const validator = validators.children[i]
|
||||
@ -879,8 +883,7 @@ Rectangle {
|
||||
|
||||
function showImageArea(imagePathsOrData) {
|
||||
isImage = imagePathsOrData.length > 0
|
||||
imageArea.imageSource = imagePathsOrData
|
||||
control.fileUrlsAndSources = imageArea.imageSource
|
||||
control.fileUrlsAndSources = imagePathsOrData
|
||||
}
|
||||
|
||||
// Use this to validate and show the images. The concatenation of previous selected images is done automatically
|
||||
@ -1162,21 +1165,26 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
StatusChatInputImageArea {
|
||||
id: imageArea
|
||||
ChatInputLinksPreviewArea {
|
||||
id: linkPreviewArea
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.current.halfPadding
|
||||
Layout.rightMargin: Style.current.halfPadding
|
||||
visible: isImage
|
||||
onImageClicked: {
|
||||
Global.openImagePopup(chatImage)
|
||||
}
|
||||
onImageRemoved: {
|
||||
if (control.fileUrlsAndSources.length > index && control.fileUrlsAndSources[index]) {
|
||||
control.fileUrlsAndSources.splice(index, 1)
|
||||
visible: contentItemsCount > 0
|
||||
horizontalPadding: 12
|
||||
topPadding: 12
|
||||
imagePreviewModel: control.fileUrlsAndSources
|
||||
linkPreviewModel: control.linkPreviewModel
|
||||
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
|
||||
if (urls.length > index && urls[index]) {
|
||||
urls.splice(index, 1)
|
||||
}
|
||||
showImageArea(control.fileUrlsAndSources)
|
||||
control.fileUrlsAndSources = urls
|
||||
}
|
||||
onImageClicked: (chatImage) => Global.openImagePopup(chatImage)
|
||||
onLinkReload: (link) => control.linkPreviewReloaded(link)
|
||||
onLinkRemoved: (link) => control.linkPreviewRemoved(link)
|
||||
onLinkClicked: (link) => Global.openLink(link)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
@ -4,27 +4,31 @@ import QtQuick.Controls 2.13
|
||||
|
||||
import utils 1.0
|
||||
import shared 1.0
|
||||
import shared.controls.chat 1.0
|
||||
import shared.panels 1.0
|
||||
|
||||
Row {
|
||||
id: imageArea
|
||||
spacing: 0
|
||||
spacing: Style.current.halfPadding
|
||||
|
||||
signal imageRemoved(int index)
|
||||
signal imageClicked(var chatImage)
|
||||
|
||||
property bool leftTail: true
|
||||
|
||||
property alias imageSource: rptImages.model
|
||||
|
||||
Repeater {
|
||||
id: rptImages
|
||||
|
||||
|
||||
Item {
|
||||
height: Style.current.halfPadding * 2 + chatImage.height + closeBtn.height / 3
|
||||
width: chatImage.width + closeBtn.width / 3
|
||||
height: chatImage.height
|
||||
width: chatImage.width
|
||||
|
||||
Image {
|
||||
id: chatImage
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
width: 64
|
||||
height: 64
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
@ -34,26 +38,10 @@ Row {
|
||||
cache: false
|
||||
source: modelData
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Item {
|
||||
width: chatImage.width
|
||||
height: chatImage.height
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: chatImage.width
|
||||
height: chatImage.height
|
||||
radius: 16
|
||||
}
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 4
|
||||
}
|
||||
}
|
||||
layer.effect: CalloutOpacityMask {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
leftTail: imageArea.leftTail
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user