feat(@desktop/wallet): Link out from collectible details view to opensea
fixes #13918
This commit is contained in:
parent
1851838e64
commit
f45a39bfcf
|
@ -1,4 +1,4 @@
|
||||||
import NimQml, strutils, uri, stew/shims/strformat, strutils, stint, re
|
import NimQml, strutils, uri, stew/shims/strformat, strutils, stint, re, httpclient
|
||||||
import stew/byteutils
|
import stew/byteutils
|
||||||
import ./utils/qrcodegen
|
import ./utils/qrcodegen
|
||||||
import ./utils/time_utils
|
import ./utils/time_utils
|
||||||
|
@ -9,6 +9,8 @@ import ../../app_service/service/visual_identity/service as procs_from_visual_id
|
||||||
|
|
||||||
include ../../app_service/service/accounts/utils
|
include ../../app_service/service/accounts/utils
|
||||||
|
|
||||||
|
const URL_STATUS_OK* = "200 OK"
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type Utils* = ref object of QObject
|
type Utils* = ref object of QObject
|
||||||
|
|
||||||
|
@ -181,4 +183,12 @@ QtObject:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
proc addTimestampToURL*(self: Utils, url: string): string {.slot.} =
|
proc addTimestampToURL*(self: Utils, url: string): string {.slot.} =
|
||||||
return time_utils.addTimestampToURL(url)
|
return time_utils.addTimestampToURL(url)
|
||||||
|
|
||||||
|
proc isValidURL*(self: Utils, url: string): bool {.slot.} =
|
||||||
|
var client = newHttpClient()
|
||||||
|
defer: client.close()
|
||||||
|
try:
|
||||||
|
return client.head(url).status == URL_STATUS_OK
|
||||||
|
except:
|
||||||
|
return false
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import
|
import
|
||||||
json, tables, sequtils, httpclient, net
|
json, tables, sequtils, net
|
||||||
import json, strutils, stew/shims/strformat, tables, chronicles, unicode, times
|
import json, strutils, stew/shims/strformat, tables, chronicles, unicode, times
|
||||||
import
|
import
|
||||||
json_serialization, chronicles, libp2p/[multihash, multicodec, cid], stint, nimcrypto
|
json_serialization, chronicles, libp2p/[multihash, multicodec, cid], stint, nimcrypto
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import NimQml, Tables, json, sequtils, chronicles, strutils, sets, stint
|
import NimQml, Tables, json, sequtils, chronicles, strutils, sets, stint
|
||||||
|
|
||||||
import httpclient
|
|
||||||
|
|
||||||
import app/core/[main]
|
import app/core/[main]
|
||||||
import app/core/tasks/[qt, threadpool]
|
import app/core/tasks/[qt, threadpool]
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import NimQml, Tables, json, sequtils, sugar, chronicles, stew/shims/strformat, stint, httpclient
|
import NimQml, Tables, json, sequtils, sugar, chronicles, stew/shims/strformat, stint
|
||||||
import net, strutils, os, times, algorithm, options
|
import net, strutils, os, times, algorithm, options
|
||||||
import web3/ethtypes
|
import web3/ethtypes
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,10 @@ ColumnLayout {
|
||||||
property string networkIconURL
|
property string networkIconURL
|
||||||
property string networkExplorerName
|
property string networkExplorerName
|
||||||
|
|
||||||
|
property bool collectibleLinkEnabled
|
||||||
|
property bool collectionLinkEnabled
|
||||||
|
property bool explorerLinkEnabled
|
||||||
|
|
||||||
signal collectionTagClicked()
|
signal collectionTagClicked()
|
||||||
signal openCollectibleExternally()
|
signal openCollectibleExternally()
|
||||||
signal openCollectibleOnExplorer()
|
signal openCollectibleOnExplorer()
|
||||||
|
@ -58,15 +62,15 @@ ColumnLayout {
|
||||||
size: StatusBaseButton.Size.Small
|
size: StatusBaseButton.Size.Small
|
||||||
text: root.networkExplorerName
|
text: root.networkExplorerName
|
||||||
icon.name: "external"
|
icon.name: "external"
|
||||||
asset.emoji: d.effectiveEmoji
|
|
||||||
onClicked: root.openCollectibleOnExplorer()
|
onClicked: root.openCollectibleOnExplorer()
|
||||||
|
visible: root.explorerLinkEnabled
|
||||||
}
|
}
|
||||||
StatusButton {
|
StatusButton {
|
||||||
size: StatusBaseButton.Size.Small
|
size: StatusBaseButton.Size.Small
|
||||||
text: "OpenSea"
|
text: "OpenSea"
|
||||||
icon.name: "external"
|
icon.name: "external"
|
||||||
asset.emoji: d.effectiveEmoji
|
|
||||||
onClicked: root.openCollectibleExternally()
|
onClicked: root.openCollectibleExternally()
|
||||||
|
visible: root.collectibleLinkEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,11 +82,13 @@ ColumnLayout {
|
||||||
id: collectionTag
|
id: collectionTag
|
||||||
asset.name: !!root.communityImage ? root.communityImage: !sensor.containsMouse ? root.isCollection ? "tiny/folder" : "tiny/profile" : "tiny/external"
|
asset.name: !!root.communityImage ? root.communityImage: !sensor.containsMouse ? root.isCollection ? "tiny/folder" : "tiny/profile" : "tiny/external"
|
||||||
asset.isImage: !!root.communityImage
|
asset.isImage: !!root.communityImage
|
||||||
|
enabled: root.collectionLinkEnabled
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: sensor
|
id: sensor
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: root.collectionLinkEnabled
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: root.collectionLinkEnabled ? Qt.PointingHandCursor: undefined
|
||||||
|
enabled: root.collectionLinkEnabled
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.collectionTagClicked()
|
root.collectionTagClicked()
|
||||||
}
|
}
|
||||||
|
|
|
@ -493,7 +493,7 @@ QtObject {
|
||||||
return root.mainModuleInst.addressWasShown(address)
|
return root.mainModuleInst.addressWasShown(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExplorerUrl(networkShortName, contractAddress, tokenId) {
|
function getExplorerDomain(networkShortName) {
|
||||||
let link = Constants.networkExplorerLinks.etherscan
|
let link = Constants.networkExplorerLinks.etherscan
|
||||||
if (networkShortName === Constants.networkShortChainNames.mainnet) {
|
if (networkShortName === Constants.networkShortChainNames.mainnet) {
|
||||||
if (root.areTestNetworksEnabled) {
|
if (root.areTestNetworksEnabled) {
|
||||||
|
@ -503,7 +503,6 @@ QtObject {
|
||||||
link = Constants.networkExplorerLinks.goerliEtherscan
|
link = Constants.networkExplorerLinks.goerliEtherscan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "%1/nft/%2/%3".arg(link).arg(contractAddress).arg(tokenId)
|
|
||||||
}
|
}
|
||||||
if (networkShortName === Constants.networkShortChainNames.arbitrum) {
|
if (networkShortName === Constants.networkShortChainNames.arbitrum) {
|
||||||
link = Constants.networkExplorerLinks.arbiscan
|
link = Constants.networkExplorerLinks.arbiscan
|
||||||
|
@ -514,7 +513,6 @@ QtObject {
|
||||||
link = Constants.networkExplorerLinks.goerliArbiscan
|
link = Constants.networkExplorerLinks.goerliArbiscan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "%1/token/%2?a=%3".arg(link).arg(contractAddress).arg(tokenId)
|
|
||||||
} else if (networkShortName === Constants.networkShortChainNames.optimism) {
|
} else if (networkShortName === Constants.networkShortChainNames.optimism) {
|
||||||
link = Constants.networkExplorerLinks.optimism
|
link = Constants.networkExplorerLinks.optimism
|
||||||
if (root.areTestNetworksEnabled) {
|
if (root.areTestNetworksEnabled) {
|
||||||
|
@ -524,6 +522,16 @@ QtObject {
|
||||||
link = Constants.networkExplorerLinks.goerliOptimism
|
link = Constants.networkExplorerLinks.goerliOptimism
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExplorerUrl(networkShortName, contractAddress, tokenId) {
|
||||||
|
let link = getExplorerDomain(networkShortName)
|
||||||
|
if (networkShortName === Constants.networkShortChainNames.mainnet) {
|
||||||
|
return "%1/nft/%2/%3".arg(link).arg(contractAddress).arg(tokenId)
|
||||||
|
}
|
||||||
|
else {
|
||||||
return "%1/token/%2?a=%3".arg(link).arg(contractAddress).arg(tokenId)
|
return "%1/token/%2?a=%3".arg(link).arg(contractAddress).arg(tokenId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -537,4 +545,53 @@ QtObject {
|
||||||
}
|
}
|
||||||
return qsTr("Etherscan Explorer")
|
return qsTr("Etherscan Explorer")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOpenSeaNetworkName(networkShortName) {
|
||||||
|
let networkName = Constants.openseaExplorerLinks.ethereum
|
||||||
|
if (networkShortName === Constants.networkShortChainNames.mainnet) {
|
||||||
|
if (root.areTestNetworksEnabled) {
|
||||||
|
if (!root.isGoerliEnabled) {
|
||||||
|
networkName = Constants.openseaExplorerLinks.sepoliaEthereum
|
||||||
|
} else {
|
||||||
|
networkName = Constants.openseaExplorerLinks.goerliEthereum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (networkShortName === Constants.networkShortChainNames.arbitrum) {
|
||||||
|
networkName = Constants.openseaExplorerLinks.arbitrum
|
||||||
|
if (root.areTestNetworksEnabled) {
|
||||||
|
if (!root.isGoerliEnabled) {
|
||||||
|
networkName = Constants.openseaExplorerLinks.sepoliaArbitrum
|
||||||
|
} else {
|
||||||
|
networkName = Constants.openseaExplorerLinks.goerliArbitrum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (networkShortName === Constants.networkShortChainNames.optimism) {
|
||||||
|
networkName = Constants.openseaExplorerLinks.optimism
|
||||||
|
if (root.areTestNetworksEnabled) {
|
||||||
|
if (!root.isGoerliEnabled) {
|
||||||
|
networkName = Constants.openseaExplorerLinks.sepoliaOptimism
|
||||||
|
} else {
|
||||||
|
networkName = Constants.openseaExplorerLinks.goerliOptimism
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return networkName
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOpenseaDomainName() {
|
||||||
|
return root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOpenSeaCollectionUrl(networkShortName, contractAddress) {
|
||||||
|
let networkName = getOpenSeaNetworkName(networkShortName)
|
||||||
|
let baseLink = root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink
|
||||||
|
return "%1/assets/%2/%3".arg(baseLink).arg(networkName).arg(contractAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOpenSeaCollectibleUrl(networkShortName, contractAddress, tokenId) {
|
||||||
|
let networkName = getOpenSeaNetworkName(networkShortName)
|
||||||
|
let baseLink = root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink
|
||||||
|
return "%1/assets/%2/%3/%4".arg(baseLink).arg(networkName).arg(contractAddress).arg(tokenId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,13 @@ Item {
|
||||||
|
|
||||||
readonly property var communityDetails: isCommunityCollectible ? root.communitiesStore.getCommunityDetailsAsJson(collectible.communityId) : null
|
readonly property var communityDetails: isCommunityCollectible ? root.communitiesStore.getCommunityDetailsAsJson(collectible.communityId) : null
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
readonly property string collectibleLink: root.walletRootStore.getOpenSeaCollectibleUrl(collectible.networkShortName, collectible.contractAddress, collectible.tokenId)
|
||||||
|
readonly property string collectionLink: root.walletRootStore.getOpenSeaCollectionUrl(collectible.networkShortName, collectible.contractAddress)
|
||||||
|
readonly property string blockExplorerLink: root.walletRootStore.getExplorerUrl(collectible.networkShortName, collectible.contractAddress, collectible.tokenId)
|
||||||
|
}
|
||||||
|
|
||||||
CollectibleDetailsHeader {
|
CollectibleDetailsHeader {
|
||||||
id: collectibleHeader
|
id: collectibleHeader
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
@ -50,22 +57,22 @@ Item {
|
||||||
networkColor: collectible.networkColor
|
networkColor: collectible.networkColor
|
||||||
networkIconURL: collectible.networkIconUrl
|
networkIconURL: collectible.networkIconUrl
|
||||||
networkExplorerName: root.walletRootStore.getExplorerNameForNetwork(collectible.networkShortName)
|
networkExplorerName: root.walletRootStore.getExplorerNameForNetwork(collectible.networkShortName)
|
||||||
|
collectibleLinkEnabled: Utils.getUrlStatus(d.collectibleLink)
|
||||||
|
collectionLinkEnabled: (!!communityDetails && communityDetails.name) || Utils.getUrlStatus(d.collectionLink)
|
||||||
|
explorerLinkEnabled: Utils.getUrlStatus(d.blockExplorerLink)
|
||||||
onCollectionTagClicked: {
|
onCollectionTagClicked: {
|
||||||
if (root.isCommunityCollectible) {
|
if (root.isCommunityCollectible) {
|
||||||
Global.switchToCommunity(collectible.communityId)
|
Global.switchToCommunity(collectible.communityId)
|
||||||
}
|
}
|
||||||
/* TODO for non community token link out to collection on opensea
|
else {
|
||||||
https://github.com/status-im/status-desktop/issues/13918 */
|
Global.openLinkWithConfirmation(d.collectionLink, root.walletRootStore.getOpenseaDomainName())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onOpenCollectibleExternally: {
|
onOpenCollectibleExternally: Global.openLinkWithConfirmation(d.collectibleLink, root.walletRootStore.getOpenseaDomainName())
|
||||||
/* TODO add link out to opensea
|
onOpenCollectibleOnExplorer: Global.openLinkWithConfirmation(d.blockExplorerLink, root.walletRootStore.getExplorerDomain(networkShortName))
|
||||||
https://github.com/status-im/status-desktop/issues/13918 */
|
|
||||||
}
|
|
||||||
onOpenCollectibleOnExplorer: Global.openLink(root.walletRootStore.getExplorerUrl(collectible.networkShortName, collectible.contractAddress, collectible.tokenId))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
Column {
|
||||||
id: collectibleBody
|
id: collectibleBody
|
||||||
anchors.top: collectibleHeader.bottom
|
anchors.top: collectibleHeader.bottom
|
||||||
anchors.topMargin: 25
|
anchors.topMargin: 25
|
||||||
|
@ -82,8 +89,8 @@ Item {
|
||||||
readonly property real visibleImageHeight: (collectibleimage.visible ? collectibleimage.height : privilegedCollectibleImage.height)
|
readonly property real visibleImageHeight: (collectibleimage.visible ? collectibleimage.height : privilegedCollectibleImage.height)
|
||||||
readonly property real visibleImageWidth: (collectibleimage.visible ? collectibleimage.width : privilegedCollectibleImage.width)
|
readonly property real visibleImageWidth: (collectibleimage.visible ? collectibleimage.width : privilegedCollectibleImage.width)
|
||||||
|
|
||||||
Layout.preferredHeight: collectibleImageDetails.visibleImageHeight
|
height: collectibleImageDetails.visibleImageHeight
|
||||||
Layout.preferredWidth: parent.width
|
width: parent.width
|
||||||
spacing: 24
|
spacing: 24
|
||||||
|
|
||||||
// Special artwork representation for community `Owner and Master Token` token types:
|
// Special artwork representation for community `Owner and Master Token` token types:
|
||||||
|
@ -132,36 +139,28 @@ Item {
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusScrollView {
|
StatusBaseText {
|
||||||
id: descriptionScrollView
|
id: descriptionText
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: collectibleImageDetails.height - collectibleName.height - parent.spacing
|
height: collectibleImageDetails.height - collectibleName.height - parent.spacing
|
||||||
|
|
||||||
contentWidth: availableWidth
|
clip: true
|
||||||
|
text: collectible.description
|
||||||
padding: 0
|
textFormat: Text.MarkdownText
|
||||||
|
color: Theme.palette.directColor4
|
||||||
StatusBaseText {
|
font.pixelSize: 15
|
||||||
id: descriptionText
|
lineHeight: 22
|
||||||
width: descriptionScrollView.availableWidth
|
lineHeightMode: Text.FixedHeight
|
||||||
|
elide: Text.ElideRight
|
||||||
text: collectible.description
|
wrapMode: Text.Wrap
|
||||||
textFormat: Text.MarkdownText
|
|
||||||
color: Theme.palette.directColor4
|
|
||||||
font.pixelSize: 15
|
|
||||||
lineHeight: 22
|
|
||||||
lineHeightMode: Text.FixedHeight
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusTabBar {
|
StatusTabBar {
|
||||||
id: collectiblesDetailsTab
|
id: collectiblesDetailsTab
|
||||||
Layout.fillWidth: true
|
width: parent.width
|
||||||
Layout.topMargin: Style.current.xlPadding
|
topPadding: Style.current.xlPadding
|
||||||
visible: collectible.traits.count > 0
|
visible: collectible.traits.count > 0
|
||||||
|
|
||||||
StatusTabButton {
|
StatusTabButton {
|
||||||
|
@ -178,14 +177,15 @@ Item {
|
||||||
|
|
||||||
StatusScrollView {
|
StatusScrollView {
|
||||||
id: scrollView
|
id: scrollView
|
||||||
Layout.fillWidth: true
|
width: parent.width
|
||||||
Layout.fillHeight: true
|
height: parent.height
|
||||||
contentWidth: availableWidth
|
contentWidth: availableWidth
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: tabLoader
|
id: tabLoader
|
||||||
Layout.fillWidth: true
|
width: parent.width
|
||||||
Layout.fillHeight: true
|
height: parent.height
|
||||||
|
|
||||||
sourceComponent: {
|
sourceComponent: {
|
||||||
switch (collectiblesDetailsTab.currentIndex) {
|
switch (collectiblesDetailsTab.currentIndex) {
|
||||||
case 0: return traitsView
|
case 0: return traitsView
|
||||||
|
|
|
@ -916,6 +916,23 @@ QtObject {
|
||||||
readonly property string txPath: "tx"
|
readonly property string txPath: "tx"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property QtObject openseaExplorerLinks: QtObject {
|
||||||
|
readonly property string mainnetLink: "https://opensea.io"
|
||||||
|
readonly property string testnetLink: "https://testnets.opensea.io"
|
||||||
|
|
||||||
|
readonly property string ethereum: "ethereum"
|
||||||
|
readonly property string arbitrum: "arbitrum"
|
||||||
|
readonly property string optimism: "optimism"
|
||||||
|
|
||||||
|
readonly property string goerliEthereum: "goerli"
|
||||||
|
readonly property string goerliArbitrum: "arbitrum-goerli"
|
||||||
|
readonly property string goerliOptimism: "optimism-goerli"
|
||||||
|
|
||||||
|
readonly property string sepoliaEthereum: "sepolia"
|
||||||
|
readonly property string sepoliaArbitrum: "arbitrum-sepolia"
|
||||||
|
readonly property string sepoliaOptimism: "optimism-sepolia"
|
||||||
|
}
|
||||||
|
|
||||||
readonly property string api_request: "api-request"
|
readonly property string api_request: "api-request"
|
||||||
readonly property string web3SendAsyncReadOnly: "web3-send-async-read-only"
|
readonly property string web3SendAsyncReadOnly: "web3-send-async-read-only"
|
||||||
readonly property string web3DisconnectAccount: "web3-disconnect-account"
|
readonly property string web3DisconnectAccount: "web3-disconnect-account"
|
||||||
|
|
|
@ -991,4 +991,8 @@ QtObject {
|
||||||
|
|
||||||
return communitiesModuleInst.isDisplayNameDupeOfCommunityMember(displayName)
|
return communitiesModuleInst.isDisplayNameDupeOfCommunityMember(displayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUrlStatus(url) {
|
||||||
|
return globalUtilsInst.isValidURL(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue