feat(CreateCommunityPopup): add discord import progress panel and discord message handling

This adds the UI plus all necessary models and signal handling to render
discord import progress in the desktop application.

It also introduces message handling for discord chat message types.

Requires status-im/status-go#2826 to function

Co-authored with @caybro
This commit is contained in:
Pascal Precht 2022-09-15 09:31:38 +02:00 committed by r4bbit.eth
parent 90ce349675
commit bf14b06d55
38 changed files with 1634 additions and 74 deletions

View File

@ -19,6 +19,18 @@ type DiscordCategoriesAndChannelsExtractedSignal* = ref object of Signal
errors*: Table[string, DiscordImportError] errors*: Table[string, DiscordImportError]
errorsCount*: int errorsCount*: int
type DiscordCommunityImportProgressSignal* = ref object of Signal
communityId*: string
communityName*: string
tasks*: seq[DiscordImportTaskProgress]
progress*: float
errorsCount*: int
warningsCount*: int
stopped*: bool
type DiscordCommunityImportFinishedSignal* = ref object of Signal
communityId*: string
proc fromEvent*(T: type CommunitySignal, event: JsonNode): CommunitySignal = proc fromEvent*(T: type CommunitySignal, event: JsonNode): CommunitySignal =
result = CommunitySignal() result = CommunitySignal()
result.signalType = SignalType.CommunityFound result.signalType = SignalType.CommunityFound
@ -46,6 +58,30 @@ proc fromEvent*(T: type DiscordCategoriesAndChannelsExtractedSignal, event: Json
result.errors[key] = err result.errors[key] = err
result.errorsCount = result.errorsCount+1 result.errorsCount = result.errorsCount+1
proc fromEvent*(T: type DiscordCommunityImportProgressSignal, event: JsonNode): DiscordCommunityImportProgressSignal =
result = DiscordCommunityImportProgressSignal()
result.signalType = SignalType.DiscordCommunityImportProgress
result.tasks = @[]
if event["event"]["importProgress"].kind == JObject:
let importProgressObj = event["event"]["importProgress"]
result.communityId = importProgressObj{"communityId"}.getStr()
result.communityName = importProgressObj{"communityName"}.getStr()
result.progress = importProgressObj{"progress"}.getFloat()
result.errorsCount = importProgressObj{"errorsCount"}.getInt()
result.warningsCount = importProgressObj{"warningsCount"}.getInt()
result.stopped = importProgressObj{"stopped"}.getBool()
if importProgressObj["tasks"].kind == JArray:
for task in importProgressObj["tasks"]:
result.tasks.add(task.toDiscordImportTaskProgress())
proc fromEvent*(T: type DiscordCommunityImportFinishedSignal, event: JsonNode): DiscordCommunityImportFinishedSignal =
result = DiscordCommunityImportFinishedSignal()
result.signalType = SignalType.DiscordCommunityImportFinished
result.communityId = event["event"]{"communityId"}.getStr()
proc createFromEvent*(T: type HistoryArchivesSignal, event: JsonNode): HistoryArchivesSignal = proc createFromEvent*(T: type HistoryArchivesSignal, event: JsonNode): HistoryArchivesSignal =
result = HistoryArchivesSignal() result = HistoryArchivesSignal()
result.communityId = event["event"]{"communityId"}.getStr() result.communityId = event["event"]{"communityId"}.getStr()
@ -83,3 +119,8 @@ proc historyArchivesUnseededFromEvent*(T: type HistoryArchivesSignal, event: Jso
proc historyArchiveDownloadedFromEvent*(T: type HistoryArchivesSignal, event: JsonNode): HistoryArchivesSignal = proc historyArchiveDownloadedFromEvent*(T: type HistoryArchivesSignal, event: JsonNode): HistoryArchivesSignal =
result = HistoryArchivesSignal.createFromEvent(event) result = HistoryArchivesSignal.createFromEvent(event)
result.signalType = SignalType.HistoryArchiveDownloaded result.signalType = SignalType.HistoryArchiveDownloaded
proc downloadingHistoryArchivesFinishedFromEvent*(T: type HistoryArchivesSignal, event: JsonNode): HistoryArchivesSignal =
result = HistoryArchivesSignal()
result.communityId = event["event"]{"communityId"}.getStr()
result.signalType = SignalType.DownloadingHistoryArchivesFinished

View File

@ -39,9 +39,12 @@ type SignalType* {.pure.} = enum
HistoryArchivesSeeding = "community.historyArchivesSeeding" HistoryArchivesSeeding = "community.historyArchivesSeeding"
HistoryArchivesUnseeded = "community.historyArchivesUnseeded" HistoryArchivesUnseeded = "community.historyArchivesUnseeded"
HistoryArchiveDownloaded = "community.historyArchiveDownloaded" HistoryArchiveDownloaded = "community.historyArchiveDownloaded"
DownloadingHistoryArchivesFinished = "community.downloadingHistoryArchivesFinished"
UpdateAvailable = "update.available" UpdateAvailable = "update.available"
DiscordCategoriesAndChannelsExtracted = "community.discordCategoriesAndChannelsExtracted" DiscordCategoriesAndChannelsExtracted = "community.discordCategoriesAndChannelsExtracted"
StatusUpdatesTimedout = "status.updates.timedout" StatusUpdatesTimedout = "status.updates.timedout"
DiscordCommunityImportFinished = "community.discordCommunityImportFinished"
DiscordCommunityImportProgress = "community.discordCommunityImportProgress"
Unknown Unknown
proc event*(self:SignalType):string = proc event*(self:SignalType):string =

View File

@ -93,9 +93,12 @@ QtObject:
of SignalType.HistoryArchivesSeeding: HistoryArchivesSignal.historyArchivesSeedingFromEvent(jsonSignal) of SignalType.HistoryArchivesSeeding: HistoryArchivesSignal.historyArchivesSeedingFromEvent(jsonSignal)
of SignalType.HistoryArchivesUnseeded: HistoryArchivesSignal.historyArchivesUnseededFromEvent(jsonSignal) of SignalType.HistoryArchivesUnseeded: HistoryArchivesSignal.historyArchivesUnseededFromEvent(jsonSignal)
of SignalType.HistoryArchiveDownloaded: HistoryArchivesSignal.historyArchiveDownloadedFromEvent(jsonSignal) of SignalType.HistoryArchiveDownloaded: HistoryArchivesSignal.historyArchiveDownloadedFromEvent(jsonSignal)
of SignalType.DownloadingHistoryArchivesFinished: HistoryArchivesSignal.downloadingHistoryArchivesFinishedFromEvent(jsonSignal)
of SignalType.UpdateAvailable: UpdateAvailableSignal.fromEvent(jsonSignal) of SignalType.UpdateAvailable: UpdateAvailableSignal.fromEvent(jsonSignal)
of SignalType.DiscordCategoriesAndChannelsExtracted: DiscordCategoriesAndChannelsExtractedSignal.fromEvent(jsonSignal) of SignalType.DiscordCategoriesAndChannelsExtracted: DiscordCategoriesAndChannelsExtractedSignal.fromEvent(jsonSignal)
of SignalType.StatusUpdatesTimedout: StatusUpdatesTimedoutSignal.fromEvent(jsonSignal) of SignalType.StatusUpdatesTimedout: StatusUpdatesTimedoutSignal.fromEvent(jsonSignal)
of SignalType.DiscordCommunityImportFinished: DiscordCommunityImportFinishedSignal.fromEvent(jsonSignal)
of SignalType.DiscordCommunityImportProgress: DiscordCommunityImportProgressSignal.fromEvent(jsonSignal)
else: Signal() else: Signal()
result.signalType = signalType result.signalType = signalType

View File

@ -96,7 +96,8 @@ proc createMessageItemFromDto(self: Module, message: MessageDto, chatDetails: Ch
newTransactionParametersItem("","","","","","",-1,""), newTransactionParametersItem("","","","","","",-1,""),
message.mentionedUsersPks, message.mentionedUsersPks,
contactDetails.details.trustStatus, contactDetails.details.trustStatus,
contactDetails.details.ensVerified contactDetails.details.ensVerified,
message.discordMessage
)) ))
method convertToItems*( method convertToItems*(
@ -243,4 +244,4 @@ method getDetails*(self: Module, sectionId: string, chatId: string): string =
jsonObject["cImage"] = %* chatImage jsonObject["cImage"] = %* chatImage
jsonObject["cColor"] = %* c.color jsonObject["cColor"] = %* c.color
jsonObject["cEmoji"] = %* c.emoji jsonObject["cEmoji"] = %* c.emoji
return $jsonObject return $jsonObject

View File

@ -97,7 +97,8 @@ proc createFetchMoreMessagesItem(self: Module): Item =
transactionParameters = newTransactionParametersItem("","","","","","",-1,""), transactionParameters = newTransactionParametersItem("","","","","","",-1,""),
mentionedUsersPks = @[], mentionedUsersPks = @[],
senderTrustStatus = TrustStatus.Unknown, senderTrustStatus = TrustStatus.Unknown,
senderEnsVerified = false senderEnsVerified = false,
DiscordMessage()
) )
proc createChatIdentifierItem(self: Module): Item = proc createChatIdentifierItem(self: Module): Item =
@ -136,7 +137,8 @@ proc createChatIdentifierItem(self: Module): Item =
transactionParameters = newTransactionParametersItem("","","","","","",-1,""), transactionParameters = newTransactionParametersItem("","","","","","",-1,""),
mentionedUsersPks = @[], mentionedUsersPks = @[],
senderTrustStatus = TrustStatus.Unknown, senderTrustStatus = TrustStatus.Unknown,
senderEnsVerified = false senderEnsVerified = false,
DiscordMessage()
) )
proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool = proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool =
@ -185,7 +187,7 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
sender.defaultDisplayName, sender.defaultDisplayName,
sender.optionalName, sender.optionalName,
sender.icon, sender.icon,
isCurrentUser, (isCurrentUser and m.contentType.ContentType != ContentType.DiscordMessage),
sender.details.added, sender.details.added,
m.outgoingStatus, m.outgoingStatus,
renderedMessageText, renderedMessageText,
@ -209,7 +211,8 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
m.transactionParameters.signature), m.transactionParameters.signature),
m.mentionedUsersPks(), m.mentionedUsersPks(),
sender.details.trustStatus, sender.details.trustStatus,
sender.details.ensVerified sender.details.ensVerified,
m.discordMessage
) )
for r in reactions: for r in reactions:
@ -277,7 +280,7 @@ method messageAdded*(self: Module, message: MessageDto) =
sender.defaultDisplayName, sender.defaultDisplayName,
sender.optionalName, sender.optionalName,
sender.icon, sender.icon,
isCurrentUser, (isCurrentUser and message.contentType.ContentType != ContentType.DiscordMessage),
sender.details.added, sender.details.added,
message.outgoingStatus, message.outgoingStatus,
renderedMessageText, renderedMessageText,
@ -301,7 +304,8 @@ method messageAdded*(self: Module, message: MessageDto) =
message.transactionParameters.signature), message.transactionParameters.signature),
message.mentionedUsersPks, message.mentionedUsersPks,
sender.details.trustStatus, sender.details.trustStatus,
sender.details.ensVerified sender.details.ensVerified,
message.discordMessage
) )
self.view.model().insertItemBasedOnTimestamp(item) self.view.model().insertItemBasedOnTimestamp(item)

View File

@ -194,7 +194,8 @@ proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy:
m.transactionParameters.signature), m.transactionParameters.signature),
m.mentionedUsersPks, m.mentionedUsersPks,
contactDetails.details.trustStatus, contactDetails.details.trustStatus,
contactDetails.details.ensVerified contactDetails.details.ensVerified,
m.discordMessage
) )
item.pinned = true item.pinned = true
item.pinnedBy = actionInitiatedBy item.pinnedBy = actionInitiatedBy

View File

@ -70,6 +70,10 @@ proc init*(self: Controller) =
let args = DiscordCategoriesAndChannelsArgs(e) let args = DiscordCategoriesAndChannelsArgs(e)
self.delegate.discordCategoriesAndChannelsExtracted(args.categories, args.channels, args.oldestMessageTimestamp, args.errors, args.errorsCount) self.delegate.discordCategoriesAndChannelsExtracted(args.categories, args.channels, args.oldestMessageTimestamp, args.errors, args.errorsCount)
self.events.on(SIGNAL_DISCORD_COMMUNITY_IMPORT_PROGRESS) do(e:Args):
let args = DiscordImportProgressArgs(e)
self.delegate.discordImportProgressUpdated(args.communityId, args.communityName, args.tasks, args.progress, args.errorsCount, args.warningsCount, args.stopped)
proc getCommunityTags*(self: Controller): string = proc getCommunityTags*(self: Controller): string =
result = self.communityService.getCommunityTags() result = self.communityService.getCommunityTags()
@ -113,6 +117,36 @@ proc createCommunity*(
pinMessageAllMembersEnabled, pinMessageAllMembersEnabled,
bannerJsonStr) bannerJsonStr)
proc requestImportDiscordCommunity*(
self: Controller,
name: string,
description: string,
introMessage: string,
outroMessage: string,
access: int,
color: string,
tags: string,
imageUrl: string,
aX: int, aY: int, bX: int, bY: int,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool,
filesToImport: seq[string],
fromTimestamp: int) =
self.communityService.requestImportDiscordCommunity(
name,
description,
introMessage,
outroMessage,
access,
color,
tags,
imageUrl,
aX, aY, bX, bY,
historyArchiveSupportEnabled,
pinMessageAllMembersEnabled,
filesToImport,
fromTimestamp)
proc reorderCommunityChat*( proc reorderCommunityChat*(
self: Controller, self: Controller,
communityId: string, communityId: string,
@ -167,3 +201,6 @@ proc getStatusForContactWithId*(self: Controller, publicKey: string): StatusUpda
proc requestExtractDiscordChannelsAndCategories*(self: Controller, filesToImport: seq[string]) = proc requestExtractDiscordChannelsAndCategories*(self: Controller, filesToImport: seq[string]) =
self.communityService.requestExtractDiscordChannelsAndCategories(filesToImport) self.communityService.requestExtractDiscordChannelsAndCategories(filesToImport)
proc requestCancelDiscordCommunityImport*(self: Controller, id: string) =
self.communityService.requestCancelDiscordCommunityImport(id)

View File

@ -34,6 +34,11 @@ method createCommunity*(self: AccessInterface, name: string, description, introM
historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool, bannerJsonStr: string) {.base.} = historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool, bannerJsonStr: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method requestImportDiscordCommunity*(self: AccessInterface, name: string, description, introMessage, outroMessage: string, access: int,
color: string, tags: string, imagePath: string, aX: int, aY: int, bX: int, bY: int,
historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool, filesToImport: seq[string], fromTimestamp: int) {.base.} =
raise newException(ValueError, "No implementation available")
method deleteCommunityCategory*(self: AccessInterface, communityId: string, categoryId: string) {.base.} = method deleteCommunityCategory*(self: AccessInterface, communityId: string, categoryId: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -117,3 +122,9 @@ method requestExtractDiscordChannelsAndCategories*(self: AccessInterface, filesT
method discordCategoriesAndChannelsExtracted*(self: AccessInterface, categories: seq[DiscordCategoryDto], channels: seq[DiscordChannelDto], oldestMessageTimestamp: int, errors: Table[string, DiscordImportError], errorsCount: int) {.base.} = method discordCategoriesAndChannelsExtracted*(self: AccessInterface, categories: seq[DiscordCategoryDto], channels: seq[DiscordChannelDto], oldestMessageTimestamp: int, errors: Table[string, DiscordImportError], errorsCount: int) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method discordImportProgressUpdated*(self: AccessInterface, communityId: string, communityName: string, tasks: seq[DiscordImportTaskProgress], progress: float, errorsCount: int, warningsCount: int, stopped: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method requestCancelDiscordCommunityImport*(self: AccessInterface, id: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,31 @@
import strformat
type
DiscordImportErrorItem* = object
taskId*: string
code*: int
message*: string
proc initDiscordImportErrorItem*(
taskId: string,
code: int,
message: string,
): DiscordImportErrorItem =
result.taskId = taskId
result.code = code
result.message = message
proc `$`*(self: DiscordImportErrorItem): string =
result = fmt"""DiscordImportErrorItem(
taskId: {self.taskId},
code: {self.code},
message: {self.message}
]"""
proc getTaskId*(self: DiscordImportErrorItem): string =
return self.taskId
proc getCode*(self: DiscordImportErrorItem): int =
return self.code
proc getMessage*(self: DiscordImportErrorItem): string =
return self.message

View File

@ -0,0 +1,60 @@
import NimQml, Tables
import discord_import_error_item
type
ModelRole {.pure.} = enum
TaskId = UserRole + 1
Code
Message
QtObject:
type DiscordImportErrorsModel* = ref object of QAbstractListModel
items*: seq[DiscordImportErrorItem]
proc setup(self: DiscordImportErrorsModel) =
self.QAbstractListModel.setup
proc delete(self: DiscordImportErrorsModel) =
self.items = @[]
self.QAbstractListModel.delete
proc newDiscordDiscordImportErrorsModel*(): DiscordImportErrorsModel =
new(result, delete)
result.setup
method roleNames(self: DiscordImportErrorsModel): Table[int, string] =
{
ModelRole.TaskId.int:"taskId",
ModelRole.Code.int:"code",
ModelRole.Message.int:"message",
}.toTable
method rowCount(self: DiscordImportErrorsModel, index: QModelIndex = nil): int =
return self.items.len
method data(self: DiscordImportErrorsModel, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.items.len:
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.TaskId:
result = newQVariant(item.getTaskId())
of ModelRole.Code:
result = newQVariant(item.getCode())
of ModelRole.Message:
result = newQVariant(item.getMessage())
proc setItems*(self: DiscordImportErrorsModel, items: seq[DiscordImportErrorItem]) =
self.beginResetModel()
self.items = items
self.endResetModel()
proc addItem*(self: DiscordImportErrorsModel, item: DiscordImportErrorItem) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len)
self.items.add(item)
self.endInsertRows()

View File

@ -0,0 +1,45 @@
import strformat
type
DiscordImportProgressItem* = object
communityId*: string
progress*: float
errorsCount*: int
warningsCount*: int
stopped*: bool
proc initDiscordImportProgressItem*(
communityId: string,
progress: float,
errorsCount: int,
warningsCount: int,
stopped: bool,
): DiscordImportProgressItem =
result.communityId = communityId
result.progress = progress
result.errorsCount = errorsCount
result.warningsCount = warningsCount
result.stopped = stopped
proc `$`*(self: DiscordImportProgressItem): string =
result = fmt"""DiscordImportProgressItem(
communityId: {self.communityId},
progress: {self.progress},
errorsCount: {self.errorsCount},
warningsCount: {self.warningsCount},
stopped: {self.stopped}
]"""
proc getCommunitId*(self: DiscordImportProgressItem): string =
return self.communityId
proc getProgress*(self: DiscordImportProgressItem): float =
return self.progress
proc getErrorsCount*(self: DiscordImportProgressItem): int =
return self.errorsCount
proc getWarningsCount*(self: DiscordImportProgressItem): int =
return self.warningsCount
proc getStopped*(self: DiscordImportProgressItem): bool =
return self.stopped

View File

@ -0,0 +1,68 @@
import strformat
import discord_import_errors_model, discord_import_error_item
import ../../../../../app_service/service/community/dto/community
const MAX_VISIBLE_ERROR_ITEMS* = 3
type
DiscordImportTaskItem* = object
`type`*: string
progress*: float
state*: string
errors*: DiscordImportErrorsModel
stopped*: bool
errorsCount*: int
warningsCount*: int
proc `$`*(self: DiscordImportTaskItem): string =
result = fmt"""DiscordImportTaskItem(
type: {self.type},
state: {self.state},
progress: {self.progress},
stopped: {self.stopped},
]"""
proc initDiscordImportTaskItem*(
`type`: string,
progress: float,
state: string,
errors: seq[DiscordImportError],
stopped: bool,
errorsCount: int,
warningsCount: int
): DiscordImportTaskItem =
result.type = type
result.progress = progress
result.state = state
result.errors = newDiscordDiscordImportErrorsModel()
result.stopped = stopped
result.errorsCount = errorsCount
result.warningsCount = warningsCount
# We only show the first 3 errors per task, then we add another
# "#n more issues" item in the UI
for i, error in errors:
if i < MAX_VISIBLE_ERROR_ITEMS:
result.errors.addItem(initDiscordImportErrorItem(`type`, error.code, error.message))
proc getType*(self: DiscordImportTaskItem): string =
return self.type
proc getProgress*(self: DiscordImportTaskItem): float =
return self.progress
proc getState*(self: DiscordImportTaskItem): string =
return self.state
proc getErrors*(self: DiscordImportTaskItem): DiscordImportErrorsModel =
return self.errors
proc getStopped*(self: DiscordImportTaskItem): bool =
return self.stopped
proc getErrorsCount*(self: DiscordImportTaskItem): int =
return self.errorsCount
proc getWarningsCount*(self: DiscordImportTaskItem): int =
return self.warningsCount

View File

@ -0,0 +1,125 @@
import NimQml, Tables
import discord_import_error_item, discord_import_errors_model
import discord_import_task_item as taskItem
import ../../../../../app_service/service/community/dto/community
type
ModelRole {.pure.} = enum
Type = UserRole + 1
Progress
State
Errors
Stopped
ErrorsCount
WarningsCount
QtObject:
type DiscordImportTasksModel* = ref object of QAbstractListModel
items*: seq[DiscordImportTaskItem]
proc setup(self: DiscordImportTasksModel) =
self.QAbstractListModel.setup
proc delete(self: DiscordImportTasksModel) =
self.items = @[]
self.QAbstractListModel.delete
proc newDiscordDiscordImportTasksModel*(): DiscordImportTasksmodel =
new(result, delete)
result.setup
method roleNames(self: DiscordImportTasksModel): Table[int, string] =
{
ModelRole.Type.int:"type",
ModelRole.Progress.int:"progress",
ModelRole.State.int:"state",
ModelRole.Errors.int:"errors",
ModelRole.Stopped.int:"stopped",
ModelRole.ErrorsCount.int:"errorsCount",
ModelRole.WarningsCount.int:"warningsCount",
}.toTable
method data(self: DiscordImportTasksModel, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.items.len:
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Type:
result = newQVariant(item.getType())
of ModelRole.Progress:
result = newQVariant(item.getProgress())
of ModelRole.State:
result = newQVariant(item.getState())
of ModelRole.Errors:
result = newQVariant(item.getErrors())
of ModelRole.Stopped:
result = newQVariant(item.getStopped())
of ModelRole.ErrorsCount:
result = newQVariant(item.getErrorsCount())
of ModelRole.WarningsCount:
result = newQVariant(item.getWarningsCount())
method rowCount(self: DiscordImportTasksModel, index: QModelIndex = nil): int =
return self.items.len
proc setItems*(self: DiscordImportTasksModel, items: seq[DiscordImportTaskItem]) =
self.beginResetModel()
self.items = items
self.endResetModel()
proc hasItemByType*(self: DiscordImportTasksModel, `type`: string): bool =
for i, item in self.items:
if self.items[i].`type` == `type`:
return true
return false
proc addItem*(self: DiscordImportTasksModel, item: DiscordImportTaskItem) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len)
self.items.add(item)
self.endInsertRows()
proc findIndexByType(self: DiscordImportTasksModel, `type`: string): int =
for i in 0 ..< self.items.len:
if(self.items[i].`type` == `type`):
return i
return -1
proc updateItem*(self: DiscordImportTasksModel, item: DiscordImportTaskProgress) =
let idx = self.findIndexByType(item.`type`)
if idx > -1:
let index = self.createIndex(idx, 0, nil)
let errorsAndWarningsCount = self.items[idx].warningsCount + self.items[idx].errorsCount
self.items[idx].progress = item.progress
self.items[idx].state = item.state
self.items[idx].stopped = item.stopped
self.items[idx].errorsCount = item.errorsCount
self.items[idx].warningsCount = item.warningsCount
let errorItemsCount = self.items[idx].errors.items.len
# We only show the first 3 errors per task, then we add another
# "#n more issues" item in the UI
for i, error in item.errors:
if errorItemsCount + i < taskItem.MAX_VISIBLE_ERROR_ITEMS:
let errorItem = initDiscordImportErrorItem(item.`type`, error.code, error.message)
self.items[idx].errors.addItem(errorItem)
self.dataChanged(index, index, @[
ModelRole.Progress.int,
ModelRole.State.int,
ModelRole.Errors.int,
ModelRole.Stopped.int,
ModelRole.ErrorsCount.int,
ModelRole.WarningsCount.int
])
proc clearItems*(self: DiscordImportTasksModel) =
self.beginResetModel()
self.items = @[]
self.endResetModel()

View File

@ -10,6 +10,10 @@ import ./models/discord_categories_model
import ./models/discord_channel_item import ./models/discord_channel_item
import ./models/discord_channels_model import ./models/discord_channels_model
import ./models/discord_file_list_model import ./models/discord_file_list_model
import ./models/discord_import_task_item
import ./models/discord_import_tasks_model
import ./models/discord_import_error_item
import ./models/discord_import_errors_model
import ../../shared_models/section_item import ../../shared_models/section_item
import ../../shared_models/[member_item, member_model, section_model] import ../../shared_models/[member_item, member_model, section_model]
import ../../../global/global_singleton import ../../../global/global_singleton
@ -288,3 +292,44 @@ method onImportCommunityErrorOccured*(self: Module, error: string) =
method requestExtractDiscordChannelsAndCategories*(self: Module, filesToImport: seq[string]) = method requestExtractDiscordChannelsAndCategories*(self: Module, filesToImport: seq[string]) =
self.view.setDiscordDataExtractionInProgress(true) self.view.setDiscordDataExtractionInProgress(true)
self.controller.requestExtractDiscordChannelsAndCategories(filesToImport) self.controller.requestExtractDiscordChannelsAndCategories(filesToImport)
method requestImportDiscordCommunity*(self: Module, name: string, description, introMessage, outroMessage: string, access: int,
color: string, tags: string, imagePath: string, aX: int, aY: int, bX: int, bY: int,
historyArchiveSupportEnabled: bool, pinMessageAllMembersEnabled: bool, filesToImport: seq[string], fromTimestamp: int) =
self.controller.requestImportDiscordCommunity(name, description, introMessage, outroMessage, access, color, tags, imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, filesToImport, fromTimestamp)
method getDiscordImportTaskItem(self: Module, t: DiscordImportTaskProgress): DiscordImportTaskItem =
return initDiscordImportTaskItem(
t.`type`,
t.progress,
t.state,
t.errors,
t.stopped,
t.errorsCount,
t.warningsCount)
method discordImportProgressUpdated*(self: Module, communityId: string, communityName: string, tasks: seq[DiscordImportTaskProgress], progress: float, errorsCount: int, warningsCount: int, stopped: bool) =
var taskItems: seq[DiscordImportTaskItem] = @[]
for task in tasks:
if not self.view.discordImportTasksModel().hasItemByType(task.`type`):
self.view.discordImportTasksModel().addItem(self.getDiscordImportTaskItem(task))
else:
self.view.discordImportTasksModel().updateItem(task)
self.view.setDiscordImportCommunityId(communityId)
self.view.setDiscordImportCommunityName(communityName)
self.view.setDiscordImportErrorsCount(errorsCount)
self.view.setDiscordImportWarningsCount(warningsCount)
# For some reason, exposing the global `progress` as QtProperty[float]`
# doesn't translate well into QML.
# That's why we pass it as integer instead.
self.view.setDiscordImportProgress((progress*100).int)
self.view.setDiscordImportProgressStopped(stopped)
if stopped or progress.int >= 1:
self.view.setDiscordImportInProgress(false)
method requestCancelDiscordCommunityImport*(self: Module, id: string) =
self.controller.requestCancelDiscordCommunityImport(id)

View File

@ -12,6 +12,8 @@ import ./models/discord_categories_model
import ./models/discord_category_item import ./models/discord_category_item
import ./models/discord_channels_model import ./models/discord_channels_model
import ./models/discord_channel_item import ./models/discord_channel_item
import ./models/discord_import_tasks_model
import ./models/discord_import_errors_model
QtObject: QtObject:
type type
@ -32,7 +34,15 @@ QtObject:
discordOldestMessageTimestamp: int discordOldestMessageTimestamp: int
discordImportErrorsCount: int discordImportErrorsCount: int
discordImportWarningsCount: int discordImportWarningsCount: int
discordImportProgress: int
discordImportInProgress: bool
discordImportCancelled: bool
discordImportProgressStopped: bool
discordImportTasksModel: DiscordImportTasksModel
discordImportTasksModelVariant: QVariant
discordDataExtractionInProgress: bool discordDataExtractionInProgress: bool
discordImportCommunityId: string
discordImportCommunityName: string
proc delete*(self: View) = proc delete*(self: View) =
self.model.delete self.model.delete
@ -46,6 +56,8 @@ QtObject:
self.discordCategoriesModelVariant.delete self.discordCategoriesModelVariant.delete
self.discordChannelsModel.delete self.discordChannelsModel.delete
self.discordChannelsModelVariant.delete self.discordChannelsModelVariant.delete
self.discordImportTasksModel.delete
self.discordImportTasksModelVariant.delete
self.QObject.delete self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View = proc newView*(delegate: io_interface.AccessInterface): View =
@ -67,6 +79,12 @@ QtObject:
result.discordDataExtractionInProgress = false result.discordDataExtractionInProgress = false
result.discordImportWarningsCount = 0 result.discordImportWarningsCount = 0
result.discordImportErrorsCount = 0 result.discordImportErrorsCount = 0
result.discordImportProgress = 0
result.discordImportInProgress = false
result.discordImportCancelled = false
result.discordImportProgressStopped = false
result.discordImportTasksModel = newDiscordDiscordImportTasksModel()
result.discordImportTasksModelVariant = newQVariant(result.discordImportTasksModel)
result.observedItem = newActiveSection() result.observedItem = newActiveSection()
proc load*(self: View) = proc load*(self: View) =
@ -119,6 +137,62 @@ QtObject:
read = getDiscordImportErrorsCount read = getDiscordImportErrorsCount
notify = discordImportErrorsCountChanged notify = discordImportErrorsCountChanged
proc discordImportProgressChanged*(self: View) {.signal.}
proc setDiscordImportProgress*(self: View, value: int) {.slot.} =
if (self.discordImportProgress == value): return
self.discordImportProgress = value
self.discordImportProgressChanged()
proc getDiscordImportProgress*(self: View): int {.slot.} =
return self.discordImportProgress
QtProperty[int] discordImportProgress:
read = getDiscordImportProgress
notify = discordImportProgressChanged
proc discordImportInProgressChanged*(self: View) {.signal.}
proc setDiscordImportInProgress*(self: View, value: bool) {.slot.} =
if (self.discordImportInProgress == value): return
self.discordImportInProgress = value
self.discordImportInProgressChanged()
proc getDiscordImportInProgress*(self: View): bool {.slot.} =
return self.discordImportInProgress
QtProperty[bool] discordImportInProgress:
read = getDiscordImportInProgress
notify = discordImportInProgressChanged
proc discordImportCancelledChanged*(self: View) {.signal.}
proc setDiscordImportCancelled*(self: View, value: bool) {.slot.} =
if (self.discordImportCancelled == value): return
self.discordImportCancelled = value
self.discordImportCancelledChanged()
proc getDiscordImportCancelled*(self: View): bool {.slot.} =
return self.discordImportCancelled
QtProperty[bool] discordImportCancelled:
read = getDiscordImportCancelled
notify = discordImportCancelledChanged
proc discordImportProgressStoppedChanged*(self: View) {.signal.}
proc setDiscordImportProgressStopped*(self: View, stopped: bool) {.slot.} =
if (self.discordImportProgressStopped == stopped): return
self.discordImportProgressStopped = stopped
self.discordImportProgressStoppedChanged()
proc getDiscordImportProgressStopped*(self: View): bool {.slot.} =
return self.discordImportProgressStopped
QtProperty[int] discordImportProgressStopped:
read = getDiscordImportProgressStopped
notify = discordImportProgressStoppedChanged
proc addItem*(self: View, item: SectionItem) = proc addItem*(self: View, item: SectionItem) =
self.model.addItem(item) self.model.addItem(item)
self.communityAdded(item.id) self.communityAdded(item.id)
@ -174,6 +248,15 @@ QtObject:
QtProperty[QVariant] discordChannels: QtProperty[QVariant] discordChannels:
read = getDiscordChannelsModel read = getDiscordChannelsModel
proc discordImportTasksModel*(self: View): DiscordImportTasksModel =
result = self.discordImportTasksModel
proc getDiscordImportTasksModel(self: View): QVariant {.slot.} =
return self.discordImportTasksModelVariant
QtProperty[QVariant] discordImportTasks:
read = getDiscordImportTasksModel
proc observedItemChanged*(self:View) {.signal.} proc observedItemChanged*(self:View) {.signal.}
proc getObservedItem(self: View): QVariant {.slot.} = proc getObservedItem(self: View): QVariant {.slot.} =
@ -204,6 +287,34 @@ QtObject:
read = getDiscordDataExtractionInProgress read = getDiscordDataExtractionInProgress
notify = discordDataExtractionInProgressChanged notify = discordDataExtractionInProgressChanged
proc discordImportCommunityIdChanged*(self: View) {.signal.}
proc getDiscordImportCommunityId(self: View): string {.slot.} =
return self.discordImportCommunityId
proc setDiscordImportCommunityId*(self: View, id: string) {.slot.} =
if (self.discordImportCommunityId == id): return
self.discordImportCommunityId = id
self.discordImportCommunityIdChanged()
QtProperty[string] discordImportCommunityId:
read = getDiscordImportCommunityId
notify = discordImportCommunityIdChanged
proc discordImportCommunityNameChanged*(self: View) {.signal.}
proc getDiscordImportCommunityName(self: View): string {.slot.} =
return self.discordImportCommunityName
proc setDiscordImportCommunityName*(self: View, name: string) {.slot.} =
if (self.discordImportCommunityName == name): return
self.discordImportCommunityName = name
self.discordImportCommunityNameChanged()
QtProperty[string] discordImportCommunityName:
read = getDiscordImportCommunityName
notify = discordImportCommunityNameChanged
proc joinCommunity*(self: View, communityId: string, ensName: string) {.slot.} = proc joinCommunity*(self: View, communityId: string, ensName: string) {.slot.} =
# Users always have to request to join a community but might # Users always have to request to join a community but might
# get automatically accepted. # get automatically accepted.
@ -219,6 +330,48 @@ QtObject:
self.delegate.createCommunity(name, description, introMessage, outroMessage, access, color, tags, self.delegate.createCommunity(name, description, introMessage, outroMessage, access, color, tags,
imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, bannerJsonStr) imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, bannerJsonStr)
proc clearFileList*(self: View) {.slot.} =
self.discordFileListModel.clearItems()
self.setDiscordImportErrorsCount(0)
self.setDiscordImportWarningsCount(0)
proc clearDiscordCategoriesAndChannels*(self: View) {.slot.} =
self.discordCategoriesModel.clearItems()
self.discordChannelsModel.clearItems()
proc resetDiscordImport*(self: View, cancelled: bool) {.slot.} =
self.clearFileList()
self.clearDiscordCategoriesAndChannels()
self.discordImportTasksModel.clearItems()
self.setDiscordImportProgress(0)
self.setDiscordImportProgressStopped(false)
self.setDiscordImportErrorsCount(0)
self.setDiscordImportWarningsCount(0)
self.setDiscordImportCommunityId("")
self.setDiscordImportCommunityName("")
self.setDiscordImportInProgress(false)
self.setDiscordImportCancelled(cancelled)
proc requestImportDiscordCommunity*(self: View, name: string,
description: string, introMessage: string, outroMessage: string,
access: int, color: string, tags: string,
imagePath: string,
aX: int, aY: int, bX: int, bY: int,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool,
fromTimestamp: int) {.slot.} =
let selectedItems = self.discordChannelsModel.getSelectedItems()
var filesToImport: seq[string] = @[]
for i in 0 ..< selectedItems.len:
filesToImport.add(selectedItems[i].getFilePath())
self.resetDiscordImport(false)
self.setDiscordImportInProgress(true)
self.delegate.requestImportDiscordCommunity(name, description, introMessage, outroMessage, access, color, tags,
imagePath, aX, aY, bX, bY, historyArchiveSupportEnabled, pinMessageAllMembersEnabled, filesToImport, fromTimestamp)
proc deleteCommunityCategory*(self: View, communityId: string, categoryId: string): string {.slot.} = proc deleteCommunityCategory*(self: View, communityId: string, categoryId: string): string {.slot.} =
self.delegate.deleteCommunityCategory(communityId, categoryId) self.delegate.deleteCommunityCategory(communityId, categoryId)
@ -288,15 +441,14 @@ QtObject:
self.setDiscordImportErrorsCount(0) self.setDiscordImportErrorsCount(0)
self.setDiscordImportWarningsCount(0) self.setDiscordImportWarningsCount(0)
proc clearFileList*(self: View) {.slot.} =
self.discordFileListModel.clearItems()
self.setDiscordImportErrorsCount(0)
self.setDiscordImportWarningsCount(0)
proc requestExtractDiscordChannelsAndCategories*(self: View) {.slot.} = proc requestExtractDiscordChannelsAndCategories*(self: View) {.slot.} =
let filePaths = self.discordFileListModel.getSelectedFilePaths() let filePaths = self.discordFileListModel.getSelectedFilePaths()
self.delegate.requestExtractDiscordChannelsAndCategories(filePaths) self.delegate.requestExtractDiscordChannelsAndCategories(filePaths)
proc requestCancelDiscordCommunityImport*(self: View, id: string) {.slot.} =
self.delegate.requestCancelDiscordCommunityImport(id)
self.resetDiscordImport(true)
proc toggleDiscordCategory*(self: View, id: string, selected: bool) {.slot.} = proc toggleDiscordCategory*(self: View, id: string, selected: bool) {.slot.} =
if selected: if selected:
self.discordCategoriesModel.selectItem(id) self.discordCategoriesModel.selectItem(id)
@ -316,6 +468,3 @@ QtObject:
if self.discordChannelsModel.allChannelsByCategoryUnselected(item.getCategoryId()): if self.discordChannelsModel.allChannelsByCategoryUnselected(item.getCategoryId()):
self.discordCategoriesModel.unselectItem(item.getCategoryId()) self.discordCategoriesModel.unselectItem(item.getCategoryId())
proc clearDiscordCategoriesAndChannels*(self: View) {.slot.} =
self.discordCategoriesModel.clearItems()
self.discordChannelsModel.clearItems()

View File

@ -0,0 +1,68 @@
import Nimqml, json, strformat
import ../../../app_service/service/message/dto/message
QtObject:
type
DiscordMessageItem* = ref object of QObject
id: string
timestamp: string
timestampEdited: string
content: string
author: DiscordMessageAuthor
proc setup(self: DiscordMessageItem) =
self.QObject.setup
proc delete*(self: DiscordMessageItem) =
self.QObject.delete
proc newDiscordMessageItem*(
id: string,
timestamp: string,
timestampEdited: string,
content: string,
author: DiscordMessageAuthor
): DiscordMessageItem =
new(result, delete)
result.setup
result.id = id
result.timestamp = timestamp
result.timestampEdited = timestampEdited
result.content = content
result.author = author
proc `$`*(self: DiscordMessageItem): string =
result = fmt"""DiscordMessageItem(
id: {$self.id},
timestamp: {$self.timestamp},
timestampEdited: {$self.timestampEdited},
content: {$self.content},
)"""
proc id*(self: DiscordMessageItem): string {.inline.} =
self.id
QtProperty[string] id:
read = id
proc timestamp*(self: DiscordMessageItem): string {.inline.} =
self.timestamp
QtProperty[string] timestamp:
read = timestamp
proc timestampEdited*(self: DiscordMessageItem): string {.inline.} =
self.timestampEdited
QtProperty[string] timestampEdited:
read = timestampEdited
proc content*(self: DiscordMessageItem): string {.inline.} =
self.content
QtProperty[string] content:
read = content
proc author*(self: DiscordMessageItem): DiscordMessageAuthor {.inline.} =
self.author

View File

@ -1,6 +1,7 @@
import json, strformat import json, strformat, strutils
import ../../../app_service/common/types import ../../../app_service/common/types
import ../../../app_service/service/contacts/dto/contacts import ../../../app_service/service/contacts/dto/contacts
import ../../../app_service/service/message/dto/message
export types.ContentType export types.ContentType
import message_reaction_model, message_reaction_item, message_transaction_parameters_item import message_reaction_model, message_reaction_item, message_transaction_parameters_item
@ -64,6 +65,7 @@ type
mentionedUsersPks: seq[string] mentionedUsersPks: seq[string]
senderTrustStatus: TrustStatus senderTrustStatus: TrustStatus
senderEnsVerified: bool senderEnsVerified: bool
messageAttachments: seq[string]
proc initItem*( proc initItem*(
id, id,
@ -90,7 +92,8 @@ proc initItem*(
transactionParameters: TransactionParametersItem, transactionParameters: TransactionParametersItem,
mentionedUsersPks: seq[string], mentionedUsersPks: seq[string],
senderTrustStatus: TrustStatus, senderTrustStatus: TrustStatus,
senderEnsVerified: bool senderEnsVerified: bool,
discordMessage: DiscordMessage
): Item = ): Item =
result = Item() result = Item()
result.id = id result.id = id
@ -124,6 +127,23 @@ proc initItem*(
result.gapTo = 0 result.gapTo = 0
result.senderTrustStatus = senderTrustStatus result.senderTrustStatus = senderTrustStatus
result.senderEnsVerified = senderEnsVerified result.senderEnsVerified = senderEnsVerified
result.messageAttachments = @[]
if ContentType.DiscordMessage == contentType:
result.messageText = discordMessage.content
result.senderDisplayName = discordMessage.author.name
result.senderIcon = discordMessage.author.localUrl
result.timestamp = parseInt(discordMessage.timestamp)*1000
if result.senderIcon == "":
result.senderIcon = discordMessage.author.avatarUrl
if discordMessage.timestampEdited != "":
result.timestamp = parseInt(discordMessage.timestampEdited)*1000
for attachment in discordMessage.attachments:
if attachment.contentType.contains("image"):
result.messageAttachments.add(attachment.localUrl)
proc `$`*(self: Item): string = proc `$`*(self: Item): string =
result = fmt"""Item( result = fmt"""Item(
@ -276,6 +296,9 @@ proc addReaction*(self: Item, emojiId: EmojiId, didIReactWithThisEmoji: bool, us
proc removeReaction*(self: Item, emojiId: EmojiId, reactionId: string, didIRemoveThisReaction: bool) = proc removeReaction*(self: Item, emojiId: EmojiId, reactionId: string, didIRemoveThisReaction: bool) =
self.reactionsModel.removeReaction(emojiId, reactionId, didIRemoveThisReaction) self.reactionsModel.removeReaction(emojiId, reactionId, didIRemoveThisReaction)
proc messageAttachments*(self: Item): seq[string] {.inline.} =
self.messageAttachments
proc links*(self: Item): seq[string] {.inline.} = proc links*(self: Item): seq[string] {.inline.} =
self.links self.links

View File

@ -41,6 +41,7 @@ type
MentionedUsersPks MentionedUsersPks
SenderTrustStatus SenderTrustStatus
SenderEnsVerified SenderEnsVerified
MessageAttachments
QtObject: QtObject:
type type
@ -116,7 +117,8 @@ QtObject:
ModelRole.TransactionParameters.int: "transactionParameters", ModelRole.TransactionParameters.int: "transactionParameters",
ModelRole.MentionedUsersPks.int: "mentionedUsersPks", ModelRole.MentionedUsersPks.int: "mentionedUsersPks",
ModelRole.SenderTrustStatus.int: "senderTrustStatus", ModelRole.SenderTrustStatus.int: "senderTrustStatus",
ModelRole.SenderEnsVerified.int: "senderEnsVerified" ModelRole.SenderEnsVerified.int: "senderEnsVerified",
ModelRole.MessageAttachments.int: "messageAttachments"
}.toTable }.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant = method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -211,6 +213,8 @@ QtObject:
result = newQVariant(item.mentionedUsersPks.join(" ")) result = newQVariant(item.mentionedUsersPks.join(" "))
of ModelRole.SenderEnsVerified: of ModelRole.SenderEnsVerified:
result = newQVariant(item.senderEnsVerified) result = newQVariant(item.senderEnsVerified)
of ModelRole.MessageAttachments:
result = newQVariant(item.messageAttachments.join(" "))
proc updateItemAtIndex(self: Model, index: int) = proc updateItemAtIndex(self: Model, index: int) =
let ind = self.createIndex(index, 0, nil) let ind = self.createIndex(index, 0, nil)

View File

@ -14,6 +14,7 @@ type
Community = 9 Community = 9
Gap = 10 Gap = 10
Edit = 11 Edit = 11
DiscordMessage = 12
type type
StatusType* {.pure.} = enum StatusType* {.pure.} = enum

View File

@ -96,6 +96,15 @@ type DiscordImportError* = object
code*: int code*: int
message*: string message*: string
type DiscordImportTaskProgress* = object
`type`*: string
progress*: float
errors*: seq[DiscordImportError]
errorsCount*: int
warningsCount*: int
stopped*: bool
state*: string
proc toCommunityAdminSettingsDto*(jsonObj: JsonNode): CommunityAdminSettingsDto = proc toCommunityAdminSettingsDto*(jsonObj: JsonNode): CommunityAdminSettingsDto =
result = CommunityAdminSettingsDto() result = CommunityAdminSettingsDto()
discard jsonObj.getProp("pinMessageAllMembersEnabled", result.pinMessageAllMembersEnabled) discard jsonObj.getProp("pinMessageAllMembersEnabled", result.pinMessageAllMembersEnabled)
@ -118,6 +127,21 @@ proc toDiscordImportError*(jsonObj: JsonNode): DiscordImportError =
discard jsonObj.getProp("code", result.code) discard jsonObj.getProp("code", result.code)
discard jsonObj.getProp("message", result.message) discard jsonObj.getProp("message", result.message)
proc toDiscordImportTaskProgress*(jsonObj: JsonNode): DiscordImportTaskProgress =
result = DiscordImportTaskProgress()
result.`type` = jsonObj{"type"}.getStr()
result.progress = jsonObj{"progress"}.getFloat()
result.stopped = jsonObj{"stopped"}.getBool()
result.errorsCount = jsonObj{"errorsCount"}.getInt()
result.warningsCount = jsonObj{"warningsCount"}.getInt()
result.state = jsonObj{"state"}.getStr()
var importErrorsObj: JsonNode
if(jsonObj.getProp("errors", importErrorsObj) and importErrorsObj.kind == JArray):
for error in importErrorsObj:
let importError = error.toDiscordImportError()
result.errors.add(importError)
proc toCommunityDto*(jsonObj: JsonNode): CommunityDto = proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
result = CommunityDto() result = CommunityDto()
discard jsonObj.getProp("id", result.id) discard jsonObj.getProp("id", result.id)

View File

@ -80,6 +80,15 @@ type
errors*: Table[string, DiscordImportError] errors*: Table[string, DiscordImportError]
errorsCount*: int errorsCount*: int
DiscordImportProgressArgs* = ref object of Args
communityId*: string
communityName*: string
tasks*: seq[DiscordImportTaskProgress]
progress*: float
errorsCount*: int
warningsCount*: int
stopped*: bool
# Signals which may be emitted by this service: # Signals which may be emitted by this service:
const SIGNAL_COMMUNITY_JOINED* = "communityJoined" const SIGNAL_COMMUNITY_JOINED* = "communityJoined"
const SIGNAL_COMMUNITY_MY_REQUEST_ADDED* = "communityMyRequestAdded" const SIGNAL_COMMUNITY_MY_REQUEST_ADDED* = "communityMyRequestAdded"
@ -107,6 +116,8 @@ const SIGNAL_COMMUNITY_MUTED* = "communityMuted"
const SIGNAL_CATEGORY_MUTED* = "categoryMuted" const SIGNAL_CATEGORY_MUTED* = "categoryMuted"
const SIGNAL_CATEGORY_UNMUTED* = "categoryUnmuted" const SIGNAL_CATEGORY_UNMUTED* = "categoryUnmuted"
const SIGNAL_DISCORD_CATEGORIES_AND_CHANNELS_EXTRACTED* = "discordCategoriesAndChannelsExtracted" const SIGNAL_DISCORD_CATEGORIES_AND_CHANNELS_EXTRACTED* = "discordCategoriesAndChannelsExtracted"
const SIGNAL_DISCORD_COMMUNITY_IMPORT_FINISHED* = "discordCommunityImportFinished"
const SIGNAL_DISCORD_COMMUNITY_IMPORT_PROGRESS* = "discordCommunityImportProgress"
QtObject: QtObject:
type type
@ -198,6 +209,22 @@ QtObject:
errorsCount: receivedData.errorsCount errorsCount: receivedData.errorsCount
)) ))
self.events.on(SignalType.DiscordCommunityImportFinished.event) do(e: Args):
var receivedData = DiscordCommunityImportFinishedSignal(e)
self.events.emit(SIGNAL_DISCORD_COMMUNITY_IMPORT_FINISHED, CommunityIdArgs(communityId: receivedData.communityId))
self.events.on(SignalType.DiscordCommunityImportProgress.event) do(e: Args):
var receivedData = DiscordCommunityImportProgressSignal(e)
self.events.emit(SIGNAL_DISCORD_COMMUNITY_IMPORT_PROGRESS, DiscordImportProgressArgs(
communityId: receivedData.communityId,
communityName: receivedData.communityName,
tasks: receivedData.tasks,
progress: receivedData.progress,
errorsCount: receivedData.errorsCount,
warningsCount: receivedData.warningsCount,
stopped: receivedData.stopped
))
proc updateMissingFields(chatDto: var ChatDto, chat: ChatDto) = proc updateMissingFields(chatDto: var ChatDto, chat: ChatDto) =
# This proc sets fields of `chatDto` which are available only for community channels. # This proc sets fields of `chatDto` which are available only for community channels.
chatDto.position = chat.position chatDto.position = chat.position
@ -648,6 +675,49 @@ QtObject:
except Exception as e: except Exception as e:
error "Error leaving community", msg = e.msg, communityId error "Error leaving community", msg = e.msg, communityId
proc requestImportDiscordCommunity*(
self: Service,
name: string,
description: string,
introMessage: string,
outroMessage: string,
access: int,
color: string,
tags: string,
imageUrl: string,
aX: int, aY: int, bX: int, bY: int,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool,
filesToImport: seq[string],
fromTimestamp: int) =
try:
var image = singletonInstance.utils.formatImagePath(imageUrl)
var tagsString = tags
if len(tagsString) == 0:
tagsString = "[]"
let response = status_go.requestImportDiscordCommunity(
name,
description,
introMessage,
outroMessage,
access,
color,
tagsString,
image,
aX, aY, bX, bY,
historyArchiveSupportEnabled,
pinMessageAllMembersEnabled,
filesToImport,
fromTimestamp)
if response.error != nil:
let error = Json.decode($response.error, RpcError)
raise newException(RpcException, "Error creating community: " & error.message)
except Exception as e:
error "Error creating community", msg = e.msg
proc createCommunity*( proc createCommunity*(
self: Service, self: Service,
name: string, name: string,
@ -1226,3 +1296,9 @@ QtObject:
except Exception as e: except Exception as e:
error "Error extracting discord channels and categories", msg = e.msg error "Error extracting discord channels and categories", msg = e.msg
proc requestCancelDiscordCommunityImport*(self: Service, communityId: string) =
try:
discard status_go.requestCancelDiscordCommunityImport(communityId)
except Exception as e:
error "Error extracting discord channels and categories", msg = e.msg

View File

@ -32,6 +32,29 @@ type QuotedMessage* = object
text*: string text*: string
parsedText*: seq[ParsedText] parsedText*: seq[ParsedText]
type DiscordMessageAttachment* = object
id*: string
fileUrl*: string
fileName*: string
localUrl*: string
contentType*: string
type DiscordMessageAuthor* = object
id*: string
name*: string
nickname*: string
avatarUrl*: string
localUrl*: string
type DiscordMessage* = object
id*: string
`type`*: string
timestamp*: string
timestampEdited*: string
content*: string
author*: DiscordMessageAuthor
attachments*: seq[DiscordMessageAttachment]
type Sticker* = object type Sticker* = object
hash*: string hash*: string
url*: string url*: string
@ -60,6 +83,7 @@ type MessageDto* = object
seen*: bool seen*: bool
outgoingStatus*: string outgoingStatus*: string
quotedMessage*: QuotedMessage quotedMessage*: QuotedMessage
discordMessage*: DiscordMessage
rtl*: bool rtl*: bool
parsedText*: seq[ParsedText] parsedText*: seq[ParsedText]
lineCount*: int lineCount*: int
@ -91,6 +115,41 @@ proc toParsedText*(jsonObj: JsonNode): ParsedText =
for childObj in childrenArr: for childObj in childrenArr:
result.children.add(toParsedText(childObj)) result.children.add(toParsedText(childObj))
proc toDiscordMessageAuthor*(jsonObj: JsonNode): DiscordMessageAuthor =
result = DiscordMessageAuthor()
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("nickname", result.nickname)
discard jsonObj.getProp("avatarUrl", result.avatarUrl)
discard jsonObj.getProp("localUrl", result.localUrl)
proc toDiscordMessageAttachment*(jsonObj: JsonNOde): DiscordMessageAttachment =
result = DiscordMessageAttachment()
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("url", result.fileUrl)
discard jsonObj.getProp("localUrl", result.localUrl)
discard jsonObj.getProp("fileName", result.fileName)
discard jsonObj.getProp("contentType", result.contentType)
proc toDiscordMessage*(jsonObj: JsonNode): DiscordMessage =
result = DiscordMessage()
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("type", result.type)
discard jsonObj.getProp("timestamp", result.timestamp)
discard jsonObj.getProp("timestampEdited", result.timestampEdited)
discard jsonObj.getProp("content", result.content)
var discordMessageAuthorObj: JsonNode
if(jsonObj.getProp("author", discordMessageAuthorObj)):
result.author = toDiscordMessageAuthor(discordMessageAuthorObj)
result.attachments = @[]
var attachmentsArr: JsonNode
if(jsonObj.getProp("attachments", attachmentsArr) and attachmentsArr.kind == JArray):
for attachment in attachmentsArr:
result.attachments.add(toDiscordMessageAttachment(attachment))
proc toQuotedMessage*(jsonObj: JsonNode): QuotedMessage = proc toQuotedMessage*(jsonObj: JsonNode): QuotedMessage =
result = QuotedMessage() result = QuotedMessage()
discard jsonObj.getProp("from", result.from) discard jsonObj.getProp("from", result.from)
@ -151,6 +210,10 @@ proc toMessageDto*(jsonObj: JsonNode): MessageDto =
if(jsonObj.getProp("quotedMessage", quotedMessageObj)): if(jsonObj.getProp("quotedMessage", quotedMessageObj)):
result.quotedMessage = toQuotedMessage(quotedMessageObj) result.quotedMessage = toQuotedMessage(quotedMessageObj)
var discordMessageObj: JsonNode
if(jsonObj.getProp("discordMessage", discordMessageObj)):
result.discordMessage = toDiscordMessage(discordMessageObj)
var stickerObj: JsonNode var stickerObj: JsonNode
if(jsonObj.getProp("sticker", stickerObj)): if(jsonObj.getProp("sticker", stickerObj)):
result.sticker = toSticker(stickerObj) result.sticker = toSticker(stickerObj)

View File

@ -296,11 +296,13 @@ QtObject:
if (receivedData.emojiReactions.len > 0): if (receivedData.emojiReactions.len > 0):
self.handleEmojiReactionsUpdate(receivedData.emojiReactions) self.handleEmojiReactionsUpdate(receivedData.emojiReactions)
self.events.on(SignalType.HistoryArchiveDownloaded.event) do(e: Args): self.events.on(SignalType.DownloadingHistoryArchivesFinished.event) do(e: Args):
var receivedData = HistoryArchivesSignal(e) var receivedData = HistoryArchivesSignal(e)
if now().toTime().toUnix()-receivedData.begin <= WEEK_AS_MILLISECONDS: self.handleMessagesReload(receivedData.communityId)
# we don't need to reload the messages for archives older than 7 days
self.handleMessagesReload(receivedData.communityId) self.events.on(SignalType.DiscordCommunityImportFinished.event) do(e: Args):
var receivedData = DiscordCommunityImportFinishedSignal(e)
self.handleMessagesReload(receivedData.communityId)
proc initialMessagesFetched(self: Service, chatId: string): bool = proc initialMessagesFetched(self: Service, chatId: string): bool =
return self.msgCursor.hasKey(chatId) return self.msgCursor.hasKey(chatId)

View File

@ -123,6 +123,45 @@ proc editCommunity*(
"pinMessageAllMembersEnabled": pinMessageAllMembersEnabled "pinMessageAllMembersEnabled": pinMessageAllMembersEnabled
}]) }])
proc requestImportDiscordCommunity*(
name: string,
description: string,
introMessage: string,
outroMessage: string,
access: int,
color: string,
tags: string,
imageUrl: string,
aX: int, aY: int, bX: int, bY: int,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool,
filesToImport: seq[string],
fromTimestamp: int
): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("requestImportDiscordCommunity".prefix, %*[{
# TODO this will need to be renamed membership (small m)
"Membership": access,
"name": name,
"description": description,
"introMessage": introMessage,
"outroMessage": outroMessage,
"ensOnly": false, # TODO ensOnly is no longer supported. Remove this when we remove it in status-go
"color": color,
"tags": parseJson(tags),
"image": imageUrl,
"imageAx": aX,
"imageAy": aY,
"imageBx": bX,
"imageBy": bY,
"historyArchiveSupportEnabled": historyArchiveSupportEnabled,
"pinMessageAllMembersEnabled": pinMessageAllMembersEnabled,
"from": fromTimestamp,
"filesToImport": filesToImport
}])
proc requestCancelDiscordCommunityImport*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("requestCancelDiscordCommunityImport".prefix, %*[communityId])
proc createCommunityChannel*( proc createCommunityChannel*(
communityId: string, communityId: string,
name: string, name: string,

View File

@ -284,6 +284,7 @@ Item {
editModeOn: model.editMode editModeOn: model.editMode
isEdited: model.isEdited isEdited: model.isEdited
linkUrls: model.links linkUrls: model.links
messageAttachments: model.messageAttachments
transactionParams: model.transactionParameters transactionParams: model.transactionParameters
hasMention: model.mentionedUsersPks.split(" ").includes(root.rootStore.userProfileInst.pubKey) hasMention: model.mentionedUsersPks.split(" ").includes(root.rootStore.userProfileInst.pubKey)

View File

@ -29,6 +29,7 @@ StatusSectionLayout {
property var importCommunitiesPopup: importCommunitiesPopupComponent property var importCommunitiesPopup: importCommunitiesPopupComponent
property var createCommunitiesPopup: createCommunitiesPopupComponent property var createCommunitiesPopup: createCommunitiesPopupComponent
property int contentPrefferedWidth: 100 property int contentPrefferedWidth: 100
property var discordImportProgressPopup: discordImportProgressDialog
notificationCount: root.communitiesStore.unreadNotificationsCount notificationCount: root.communitiesStore.unreadNotificationsCount
onNotificationButtonClicked: Global.openActivityCenterPopup() onNotificationButtonClicked: Global.openActivityCenterPopup()
@ -219,9 +220,14 @@ StatusSectionLayout {
} }
} }
CommunityBanner { CommunityBanner {
text: qsTr("Import existing Discord community into Status") property bool importInProgress: root.communitiesStore.discordImportInProgress && !root.communitiesStore.discordImportCancelled
text: importInProgress ?
qsTr("'%1' import in progress...").arg(root.communitiesStore.discordImportCommunityName) :
qsTr("Import existing Discord community into Status")
buttonText: qsTr("Import existing") buttonText: qsTr("Import existing")
icon.name: "download" icon.name: "download"
buttonTooltipText: qsTr("Your current import must finished or be cancelled before a new import can be started.")
buttonLoading: importInProgress
onButtonClicked: { onButtonClicked: {
chooseCommunityCreationTypePopup.close() chooseCommunityCreationTypePopup.close()
Global.openPopup(createCommunitiesPopupComponent, {isDiscordImport: true}) Global.openPopup(createCommunitiesPopupComponent, {isDiscordImport: true})
@ -230,4 +236,11 @@ StatusSectionLayout {
} }
} }
} }
Component {
id: discordImportProgressDialog
DiscordImportProgressDialog {
store: root.communitiesStore
}
}
} }

View File

@ -125,9 +125,16 @@ StatusStackModal {
} }
IssuePill { IssuePill {
type: root.store.discordImportErrorsCount ? IssuePill.Type.Error : IssuePill.Type.Warning type: root.store.discordImportErrorsCount ? IssuePill.Type.Error : IssuePill.Type.Warning
count: root.store.discordImportErrorsCount ? root.store.discordImportErrorsCount : count: {
root.store.discordImportWarningsCount ? root.store.discordImportWarningsCount : 0 if (root.store.discordImportErrorsCount > 0) {
visible: count return root.store.discordImportErrorsCount
}
if (root.store.discordImportWarningsCount > 0) {
return root.store.discordImportWarningsCount
}
return 0
}
visible: !!count
} }
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
StatusButton { StatusButton {
@ -243,6 +250,20 @@ StatusStackModal {
readonly property bool canGoNext: root.store.discordChannelsModel.hasSelectedItems readonly property bool canGoNext: root.store.discordChannelsModel.hasSelectedItems
readonly property var nextAction: function () { readonly property var nextAction: function () {
d.requestImportDiscordCommunity()
// replace ourselves with the progress dialog, no way back
root.leftButtons[0].visible = false
root.backgroundColor = Theme.palette.baseColor4
root.replace(progressComponent)
}
Component {
id: progressComponent
DiscordImportProgressContents {
width: root.availableWidth
store: root.store
onClose: root.close()
}
} }
Item { Item {
@ -456,34 +477,46 @@ StatusStackModal {
QtObject { QtObject {
id: d id: d
function _getCommunityConfig() {
return {
name: StatusQUtils.Utils.filterXSS(nameInput.input.text),
description: StatusQUtils.Utils.filterXSS(descriptionTextInput.input.text),
introMessage: StatusQUtils.Utils.filterXSS(introMessageInput.input.text),
outroMessage: StatusQUtils.Utils.filterXSS(outroMessageInput.input.text),
color: colorPicker.color.toString().toUpperCase(),
tags: communityTagsPicker.selectedTags,
image: {
src: logoPicker.source,
AX: logoPicker.cropRect.x,
AY: logoPicker.cropRect.y,
BX: logoPicker.cropRect.x + logoPicker.cropRect.width,
BY: logoPicker.cropRect.y + logoPicker.cropRect.height,
},
options: {
historyArchiveSupportEnabled: options.archiveSupportEnabled,
checkedMembership: options.requestToJoinEnabled ? Constants.communityChatOnRequestAccess : Constants.communityChatPublicAccess,
pinMessagesAllowedForMembers: options.pinMessagesEnabled
},
bannerJsonStr: JSON.stringify({imagePath: String(bannerPicker.source).replace("file://", ""), cropRect: bannerPicker.cropRect})
}
}
function createCommunity() { function createCommunity() {
const error = store.createCommunity({ const error = root.store.createCommunity(_getCommunityConfig())
name: StatusQUtils.Utils.filterXSS(nameInput.input.text),
description: StatusQUtils.Utils.filterXSS(descriptionTextInput.input.text),
introMessage: StatusQUtils.Utils.filterXSS(introMessageInput.input.text),
outroMessage: StatusQUtils.Utils.filterXSS(outroMessageInput.input.text),
color: colorPicker.color.toString().toUpperCase(),
tags: communityTagsPicker.selectedTags,
image: {
src: logoPicker.source,
AX: logoPicker.cropRect.x,
AY: logoPicker.cropRect.y,
BX: logoPicker.cropRect.x + logoPicker.cropRect.width,
BY: logoPicker.cropRect.y + logoPicker.cropRect.height,
},
options: {
historyArchiveSupportEnabled: options.archiveSupportEnabled,
checkedMembership: options.requestToJoinEnabled ? Constants.communityChatOnRequestAccess : Constants.communityChatPublicAccess,
pinMessagesAllowedForMembers: options.pinMessagesEnabled
},
bannerJsonStr: JSON.stringify({imagePath: String(bannerPicker.source).replace("file://", ""), cropRect: bannerPicker.cropRect})
})
if (error) { if (error) {
errorDialog.text = error.error errorDialog.text = error.error
errorDialog.open() errorDialog.open()
} }
root.close() root.close()
} }
function requestImportDiscordCommunity() {
const error = root.store.requestImportDiscordCommunity(_getCommunityConfig(), datePicker.selectedDate.valueOf()/1000)
if (error) {
errorDialog.text = error.error
errorDialog.open()
}
}
} }
MessageDialog { MessageDialog {

View File

@ -0,0 +1,356 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import utils 1.0
import shared.popups 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups.Dialog 0.1
import SortFilterProxyModel 0.2
import "../controls"
StatusScrollView {
id: root
property var store
signal close()
implicitWidth: childrenRect.width
padding: 0
enum ImportStatus {
Unknown,
InProgress,
Stopped,
StoppedWithErrors,
CompletedWithWarnings,
CompletedSuccessfully
}
readonly property list<StatusBaseButton> rightButtons: [
StatusButton {
visible: d.status === DiscordImportProgressContents.ImportStatus.CompletedWithWarnings ||
d.status === DiscordImportProgressContents.ImportStatus.StoppedWithErrors
type: StatusButton.Danger
font.weight: Font.Medium
text: qsTr("Delete community & restart import")
onClicked: {
// TODO display a confirmation and open CreateCommunityPopup again
root.close()
}
},
StatusButton {
visible: d.status === DiscordImportProgressContents.ImportStatus.InProgress
type: StatusButton.Danger
font.weight: Font.Medium
text: qsTr("Cancel import")
onClicked: {
Global.openPopup(cancelConfirmationPopupCmp)
}
},
StatusButton {
visible: d.status === DiscordImportProgressContents.ImportStatus.Stopped // TODO find out exactly when to display this button
type: StatusButton.Danger
font.weight: Font.Medium
text: qsTr("Restart import")
onClicked: {
// TODO open CreateCommunityPopup again
root.store.resetDiscordImport()
root.close()
}
},
StatusButton {
visible: d.status === DiscordImportProgressContents.ImportStatus.InProgress
font.weight: Font.Medium
text: qsTr("Hide window")
onClicked: root.close()
},
StatusButton {
visible: d.status === DiscordImportProgressContents.ImportStatus.CompletedSuccessfully ||
d.status === DiscordImportProgressContents.ImportStatus.CompletedWithWarnings
font.weight: Font.Medium
text: qsTr("Visit your new community")
onClicked: {
root.close()
root.store.setActiveCommunity(root.store.discordImportCommunityId)
}
}
]
QtObject {
id: d
readonly property var helperInfo: {
"import.communityCreation": {
icon: "network",
text: qsTr("Setting up your community")
},
"import.channelsCreation": {
icon: "channel",
text: qsTr("Importing channels")
},
"import.importMessages": {
icon: "receive",
text: qsTr("Importing messages")
},
"import.downloadAssets": {
icon: "image",
text: qsTr("Downloading assets")
},
"import.initializeCommunity": {
icon: "communities",
text: qsTr("Initializing community")
}
}
readonly property int importProgress: root.store.discordImportProgress // FIXME for now it is 0..100
readonly property bool importStopped: root.store.discordImportProgressStopped
readonly property bool hasErrors: root.store.discordImportErrorsCount
readonly property bool hasWarnings: root.store.discordImportWarningsCount
readonly property int status: {
if (importStopped) {
if (hasErrors)
return DiscordImportProgressContents.ImportStatus.StoppedWithErrors
return DiscordImportProgressContents.ImportStatus.Stopped
}
if (importProgress >= 100) {
if (hasWarnings)
return DiscordImportProgressContents.ImportStatus.CompletedWithWarnings
return DiscordImportProgressContents.ImportStatus.CompletedSuccessfully
}
if (importProgress > 0 && importProgress < 100)
return DiscordImportProgressContents.ImportStatus.InProgress
return DiscordImportProgressContents.ImportStatus.Unknown
}
function getSubtaskDescription(progress, stopped, state) {
if (progress >= 1.0)
return qsTr("✓ Complete")
if (progress > 0 && stopped)
return qsTr("Import stopped...")
if (importStopped)
return ""
if (progress <= 0.0)
return qsTr("Pending...")
return state == "import.taskState.saving" ?
qsTr("Saving... This can take a moment, almost done!") :
qsTr("Working...")
}
}
Component {
id: subtaskComponent
ColumnLayout {
id: subtaskDelegate
spacing: 40
width: parent.width
readonly property int errorsAndWarningsCount: model.errorsCount + model.warningsCount
readonly property string type: model.type
readonly property var errors: model.errors
RowLayout {
id: subtaskRow
spacing: 12
Layout.fillWidth: true
Layout.preferredHeight: 42
StatusRoundIcon {
id: subtaskIcon
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 40
Layout.preferredHeight: 40
asset.name: d.helperInfo[model.type].icon
}
ColumnLayout {
spacing: 4
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter
StatusBaseText {
font.pixelSize: 15
text: d.helperInfo[model.type].text
}
StatusBaseText {
font.pixelSize: 12
color: {
if (model.progress >= 1)
return Theme.palette.successColor1
if (model.progress > 0 && d.hasErrors)
return Theme.palette.dangerColor1
return Theme.palette.baseColor1
}
text: d.getSubtaskDescription(model.progress, model.stopped, model.state)
}
}
Item { Layout.fillWidth: true }
StatusBaseText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 13
font.weight: Font.Medium
visible: subtaskProgressBar.visible
text: qsTr("%1%").arg(Math.round(model.progress*100))
}
StatusProgressBar {
id: subtaskProgressBar
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 130
Layout.preferredHeight: 10
visible: value > 0 && value <= 1 && d.status !== DiscordImportProgressContents.ImportStatus.StoppedWithErrors
fillColor: Theme.palette.primaryColor1
backgroundColor: Theme.palette.directColor8
value: model.progress
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.leftMargin: subtaskIcon.width + subtaskRow.spacing
spacing: 12
Repeater {
model: SortFilterProxyModel {
sourceModel: subtaskDelegate.errors
sorters: RoleSorter { roleName: "code"; sortOrder: Qt.DescendingOrder } // errors first
}
delegate: IssuePill {
Layout.fillWidth: true
horizontalPadding: 12
verticalPadding: 8
bgCornerRadius: 8
visible: text
type: model.code === Constants.DiscordImportErrorCode.Error ? IssuePill.Type.Error : IssuePill.Type.Warning
text: model.message
}
}
Loader {
active: subtaskDelegate.errorsAndWarningsCount > 3
Layout.fillWidth: true
sourceComponent: IssuePill {
width: parent.width
horizontalPadding: 12
verticalPadding: 8
bgCornerRadius: 8
visible: text
type: IssuePill.Type.Warning
text: qsTr("%1 more issue(s) downloading assets").arg(errorsAndWarningsCount - 3)
}
}
}
StatusDialogDivider {
Layout.fillWidth: true
Layout.leftMargin: -24 // compensate for Control.horizontalPadding -> full width
Layout.rightMargin: -24 // compensate for Control.horizontalPadding -> full width
visible: !parent.Positioner.isLastItem
}
}
}
ColumnLayout {
width: parent.width
spacing: 20
RowLayout {
Layout.fillWidth: true
spacing: 12
Image {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
sourceSize: Qt.size(36, 36)
source: Style.svg("contact") // TODO community icon
}
StatusBaseText {
Layout.fillWidth: true
font.pixelSize: 15
text: {
switch (d.status) {
case DiscordImportProgressContents.ImportStatus.InProgress:
return qsTr("Importing %1 from Discord...").arg(root.store.discordImportCommunityName)
case DiscordImportProgressContents.ImportStatus.Stopped:
return qsTr("Importing %1 from Discord stopped...").arg(root.store.discordImportCommunityName)
case DiscordImportProgressContents.ImportStatus.StoppedWithErrors:
return qsTr("Importing %1 stopped due to a critical issue...").arg(root.store.discordImportCommunityName)
case DiscordImportProgressContents.ImportStatus.CompletedWithWarnings:
return qsTr("%1 was imported with %n issue(s).", "", root.store.discordImportWarningsCount).arg(root.store.discordImportCommunityName)
case DiscordImportProgressContents.ImportStatus.CompletedSuccessfully:
return qsTr("%1 was successfully imported from Discord.").arg(root.store.discordImportCommunityName)
default:
return qsTr("Your Discord community import is in progress...")
}
}
}
Item { Layout.fillWidth: true }
IssuePill {
type: d.hasErrors ? IssuePill.Type.Error : IssuePill.Type.Warning
count: d.hasErrors ? root.store.discordImportErrorsCount :
d.hasWarnings ? root.store.discordImportWarningsCount : 0
visible: count
}
}
Control {
Layout.fillWidth: true
horizontalPadding: 24
verticalPadding: 40
background: Rectangle {
radius: 16
color: Theme.palette.indirectColor1
border.width: 1
border.color: Theme.palette.directColor8
}
contentItem: Column {
spacing: 40
Repeater {
model: root.store.discordImportTasks
delegate: subtaskComponent
}
}
}
StatusBaseText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
font.pixelSize: 13
text: d.status === DiscordImportProgressContents.ImportStatus.InProgress ?
qsTr("This process can take a while. Feel free to hide this window and use Status normally in the meantime. Well notify you when the Community is ready for you.") :
qsTr("If there were any issues with your import you can upload new JSON files via the community page at any time.")
}
}
Component {
id: cancelConfirmationPopupCmp
ConfirmationDialog {
id: cancelConfirmationPopup
header.title: qsTr("Are you sure you want to cancel the import?")
confirmationText: qsTr("Your new Status community will be deleted and all information entered will be lost.")
showCancelButton: true
cancelBtnType: "default"
confirmButtonLabel: qsTr("Delete community")
cancelButtonLabel: qsTr("Continue importing")
onConfirmButtonClicked: {
root.store.requestCancelDiscordCommunityImport(root.store.discordImportCommunityId)
cancelConfirmationPopup.close()
root.close()
}
onCancelButtonClicked: {
cancelConfirmationPopup.close()
}
onClosed: {
destroy()
}
}
}
}

View File

@ -0,0 +1,51 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQml.Models 2.14
import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups.Dialog 0.1
import "../controls"
StatusDialog {
id: root
property var store
title: qsTr("Import a community from Discord into Status")
horizontalPadding: 16
verticalPadding: 20
width: 640
onClosed: destroy()
Component.onCompleted: {
const buttons = contents.rightButtons
for (let i = 0; i < buttons.length; i++) {
footer.rightButtons.append(buttons[i])
}
}
footer: StatusDialogFooter {
id: footer
rightButtons: ObjectModel {}
}
background: StatusDialogBackground {
color: Theme.palette.baseColor4
}
contentItem: DiscordImportProgressContents {
id: contents
width: root.availableWidth
store: root.store
onClose: root.close()
}
}

View File

@ -17,6 +17,13 @@ QtObject {
property bool discordDataExtractionInProgress: root.communitiesModuleInst.discordDataExtractionInProgress property bool discordDataExtractionInProgress: root.communitiesModuleInst.discordDataExtractionInProgress
property int discordImportErrorsCount: root.communitiesModuleInst.discordImportErrorsCount property int discordImportErrorsCount: root.communitiesModuleInst.discordImportErrorsCount
property int discordImportWarningsCount: root.communitiesModuleInst.discordImportWarningsCount property int discordImportWarningsCount: root.communitiesModuleInst.discordImportWarningsCount
property int discordImportProgress: root.communitiesModuleInst.discordImportProgress
property bool discordImportInProgress: root.communitiesModuleInst.discordImportInProgress
property bool discordImportCancelled: root.communitiesModuleInst.discordImportCancelled
property bool discordImportProgressStopped: root.communitiesModuleInst.discordImportProgressStopped
property string discordImportCommunityId: root.communitiesModuleInst.discordImportCommunityId
property string discordImportCommunityName: root.communitiesModuleInst.discordImportCommunityName
property var discordImportTasks: root.communitiesModuleInst.discordImportTasks
property string locale: localAppSettings.language property string locale: localAppSettings.language
property var advancedModule: profileSectionModule.advancedModule property var advancedModule: profileSectionModule.advancedModule
property bool isCommunityHistoryArchiveSupportEnabled: advancedModule? advancedModule.isCommunityHistoryArchiveSupportEnabled : false property bool isCommunityHistoryArchiveSupportEnabled: advancedModule? advancedModule.isCommunityHistoryArchiveSupportEnabled : false
@ -105,5 +112,40 @@ QtObject {
function toggleDiscordChannel(id, selected) { function toggleDiscordChannel(id, selected) {
root.communitiesModuleInst.toggleDiscordChannel(id, selected) root.communitiesModuleInst.toggleDiscordChannel(id, selected)
}
function requestCancelDiscordCommunityImport(id) {
root.communitiesModuleInst.requestCancelDiscordCommunityImport(id)
}
function resetDiscordImport() {
root.communitiesModuleInst.resetDiscordImport(false)
}
function requestImportDiscordCommunity(args = {
name: "",
description: "",
introMessage: "",
outroMessage: "",
color: "",
tags: "",
image: {
src: "",
AX: 0,
AY: 0,
BX: 0,
BY: 0,
},
options: {
historyArchiveSupportEnabled: false,
checkedMembership: false,
pinMessagesAllowedForMembers: false
}
}, from = 0) {
return communitiesModuleInst.requestImportDiscordCommunity(
args.name, args.description, args.introMessage, args.outroMessage, args.options.checkedMembership,
args.color, args.tags,
args.image.src, args.image.AX, args.image.AY, args.image.BX, args.image.BY,
args.options.historyArchiveSupportEnabled, args.options.pinMessagesAllowedForMembers, from);
} }
} }

View File

@ -498,6 +498,57 @@ Item {
} }
} }
ModuleWarning {
Layout.fillWidth: true
readonly property int progress: communitiesPortalLayoutContainer.communitiesStore.discordImportProgress
readonly property bool inProgress: progress > 0 && progress < 100
readonly property bool finished: progress >= 100
readonly property bool cancelled: communitiesPortalLayoutContainer.communitiesStore.discordImportCancelled
readonly property bool stopped: communitiesPortalLayoutContainer.communitiesStore.discordImportProgressStopped
readonly property int errors: communitiesPortalLayoutContainer.communitiesStore.discordImportErrorsCount
readonly property int warnings: communitiesPortalLayoutContainer.communitiesStore.discordImportWarningsCount
readonly property string communityId: communitiesPortalLayoutContainer.communitiesStore.discordImportCommunityId
readonly property string communityName: communitiesPortalLayoutContainer.communitiesStore.discordImportCommunityName
active: !cancelled && (inProgress || finished || stopped)
type: errors ? ModuleWarning.Type.Danger : ModuleWarning.Type.Success
text: {
if (finished || stopped) {
if (errors)
return qsTr("The import of %1 from Discord to Status was stopped: <a href='#'>Critical issues found</a>").arg(communityName)
let result = qsTr("%1 was successfully imported from Discord to Status").arg(communityName) + " <a href='#'>"
if (warnings)
result += qsTr("Details (%1)").arg(qsTr("%n issue(s)", "", warnings))
else
result += qsTr("Details")
result += "</a>"
return result
}
if (inProgress) {
let result = qsTr("Importing %1 from Discord to Status").arg(communityName) + " <a href='#'>"
if (warnings)
result += qsTr("Check progress (%1)").arg(qsTr("%n issue(s)", "", warnings))
else
result += qsTr("Check progress")
result += "</a>"
return result
}
}
onLinkActivated: Global.openPopup(communitiesPortalLayoutContainer.discordImportProgressPopup)
progressValue: progress
closeBtnVisible: finished || stopped
buttonText: finished && !errors ? qsTr("Visit your Community") : ""
onClicked: function() {
communitiesPortalLayoutContainer.communitiesStore.setActiveCommunity(communityId)
}
onCloseClicked: {
hide();
}
}
Component { Component {
id: connectedBannerComponent id: connectedBannerComponent

View File

@ -18,6 +18,7 @@ Loader {
property string image property string image
property bool showRing: true property bool showRing: true
property bool interactive: true property bool interactive: true
property bool disabled: false
property int colorId: Utils.colorIdForPubkey(pubkey) property int colorId: Utils.colorIdForPubkey(pubkey)
property var colorHash: Utils.getColorHashAsJson(pubkey) property var colorHash: Utils.getColorHashAsJson(pubkey)
@ -44,10 +45,12 @@ Loader {
active: root.interactive active: root.interactive
sourceComponent: MouseArea { sourceComponent: MouseArea {
cursorShape: Qt.PointingHandCursor cursorShape: hoverEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
hoverEnabled: true hoverEnabled: !root.disabled
onClicked: { onClicked: {
root.clicked() if (!root.disabled) {
root.clicked()
}
} }
} }
} }

View File

@ -15,6 +15,7 @@ Item {
property string displayName property string displayName
property string localName property string localName
property bool amISender property bool amISender
property bool disabled
signal clickMessage(bool isProfileClick) signal clickMessage(bool isProfileClick)
@ -24,15 +25,15 @@ Item {
color: text.startsWith("@") || root.amISender || localName !== "" ? Style.current.blue : Style.current.secondaryText color: text.startsWith("@") || root.amISender || localName !== "" ? Style.current.blue : Style.current.secondaryText
font.weight: Font.Medium font.weight: Font.Medium
font.pixelSize: Style.current.secondaryTextFontSize font.pixelSize: Style.current.secondaryTextFontSize
font.underline: root.isHovered font.underline: root.isHovered && !root.disabled
readOnly: true readOnly: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
selectByMouse: true selectByMouse: true
MouseArea { MouseArea {
cursorShape: Qt.PointingHandCursor cursorShape: hoverEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: !root.disabled
onEntered: { onEntered: {
root.isHovered = true root.isHovered = true
} }
@ -40,7 +41,9 @@ Item {
root.isHovered = false root.isHovered = false
} }
onClicked: { onClicked: {
root.clickMessage(true); if (!root.disabled) {
root.clickMessage(true);
}
} }
} }
} }

View File

@ -18,6 +18,8 @@ Rectangle {
property alias text: bannerText.text property alias text: bannerText.text
property alias buttonText: bannerButton.text property alias buttonText: bannerButton.text
property alias icon: bannerIcon.asset property alias icon: bannerIcon.asset
property string buttonTooltipText: ""
property bool buttonLoading: false
implicitWidth: 272 implicitWidth: 272
implicitHeight: 168 implicitHeight: 168
@ -74,7 +76,17 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 16 anchors.bottomMargin: 16
font.weight: Font.Medium font.weight: Font.Medium
onClicked: root.buttonClicked() onClicked: {
if (!root.buttonLoading) {
root.buttonClicked()
}
}
loading: root.buttonLoading
StatusQControls.StatusToolTip {
text: root.buttonTooltipText
visible: !!root.buttonTooltipText && bannerButton.loading && bannerButton.hovered
}
} }
} }

View File

@ -1,5 +1,5 @@
import QtQuick 2.13 import QtQuick 2.14
import QtQuick.Controls 2.13 import QtQuick.Controls 2.14
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13 import QtGraphicalEffects 1.13
@ -7,8 +7,9 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import utils 1.0 import utils 1.0
import "../"
import "./" import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
Item { Item {
id: root id: root
@ -20,8 +21,10 @@ Item {
property bool active: false property bool active: false
property int type: ModuleWarning.Danger property int type: ModuleWarning.Danger
property int progressValue: -1 // 0..100, -1 not visible
property string text: "" property string text: ""
property alias buttonText: button.text property alias buttonText: button.text
property alias closeBtnVisible: closeImg.visible
signal clicked() signal clicked()
signal closeClicked() signal closeClicked()
@ -49,6 +52,8 @@ Item {
closeButtonMouseArea.clicked(null) closeButtonMouseArea.clicked(null)
} }
signal linkActivated(string link)
implicitHeight: root.active ? content.implicitHeight : 0 implicitHeight: root.active ? content.implicitHeight : 0
visible: implicitHeight > 0 visible: implicitHeight > 0
@ -123,14 +128,24 @@ Item {
id: layout id: layout
spacing: 12 spacing: 12
anchors.horizontalCenter: parent.horizontalCenter anchors.centerIn: parent
anchors.verticalCenter: parent.verticalCenter
StatusBaseText { StatusBaseText {
text: root.text text: root.text
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
color: Theme.palette.indirectColor1 color: Theme.palette.indirectColor1
linkColor: color
onLinkActivated: root.linkActivated(link)
HoverHandler {
id: handler1
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: handler1.hovered && parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
} }
Button { Button {
@ -140,9 +155,9 @@ Item {
onClicked: { onClicked: {
root.clicked() root.clicked()
} }
contentItem: Text { contentItem: StatusBaseText {
text: button.text text: button.text
font.pixelSize: 12 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
font.family: Style.current.baseFont.name font.family: Style.current.baseFont.name
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@ -163,6 +178,42 @@ Item {
} }
} }
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
anchors.right: progressBar.left
anchors.rightMargin: Style.current.halfPadding
text: qsTr("%1%").arg(progressBar.value)
visible: progressBar.visible
font.pixelSize: 12
verticalAlignment: Text.AlignVCenter
color: Theme.palette.white
}
ProgressBar {
id: progressBar
anchors.verticalCenter: parent.verticalCenter
anchors.right: closeImg.left
anchors.rightMargin: Style.current.bigPadding
from: 0
to: 100
visible: root.progressValue > -1
value: root.progressValue
background: Rectangle {
implicitWidth: 64
implicitHeight: 8
radius: 8
color: "transparent"
border.width: 1
border.color: Theme.palette.white
}
contentItem: Rectangle {
width: progressBar.width*progressBar.position
implicitHeight: 8
radius: 8
color: Theme.palette.white
}
}
StatusIcon { StatusIcon {
id: closeImg id: closeImg
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -185,5 +236,4 @@ Item {
} }
} }
} }
} }

View File

@ -18,6 +18,7 @@ StatusModal {
property var executeCancel property var executeCancel
property string confirmButtonObjectName: "" property string confirmButtonObjectName: ""
property string btnType: "warn" property string btnType: "warn"
property string cancelBtnType: "warn"
property string confirmButtonLabel: qsTr("Confirm") property string confirmButtonLabel: qsTr("Confirm")
property string rejectButtonLabel: qsTr("Reject") property string rejectButtonLabel: qsTr("Reject")
property string cancelButtonLabel: qsTr("Cancel") property string cancelButtonLabel: qsTr("Cancel")
@ -81,6 +82,14 @@ StatusModal {
id: cancelButton id: cancelButton
visible: showCancelButton visible: showCancelButton
text: confirmationDialog.cancelButtonLabel text: confirmationDialog.cancelButtonLabel
type: {
switch (confirmationDialog.cancelBtnType) {
case "warn":
return StatusBaseButton.Type.Danger
default:
return StatusBaseButton.Type.Normal
}
}
onClicked: { onClicked: {
if (executeCancel && typeof executeCancel === "function") { if (executeCancel && typeof executeCancel === "function") {
executeCancel() executeCancel()
@ -106,7 +115,7 @@ StatusModal {
case "warn": case "warn":
return StatusBaseButton.Type.Danger return StatusBaseButton.Type.Danger
default: default:
return StatusBaseButton.Type.Primary return StatusBaseButton.Type.Normal
} }
} }
text: confirmationDialog.confirmButtonLabel text: confirmationDialog.confirmButtonLabel

View File

@ -62,6 +62,7 @@ Loader {
property string messagePinnedBy: "" property string messagePinnedBy: ""
property var reactionsModel: [] property var reactionsModel: []
property string linkUrls: "" property string linkUrls: ""
property string messageAttachments: ""
property var transactionParams property var transactionParams
// External behavior changers // External behavior changers
@ -101,7 +102,7 @@ Loader {
property double prevMsgTimestamp: prevMessageAsJsonObj ? prevMessageAsJsonObj.timestamp : 0 property double prevMsgTimestamp: prevMessageAsJsonObj ? prevMessageAsJsonObj.timestamp : 0
property double nextMsgTimestamp: nextMessageAsJsonObj ? nextMessageAsJsonObj.timestamp : 0 property double nextMsgTimestamp: nextMessageAsJsonObj ? nextMessageAsJsonObj.timestamp : 0
property bool shouldRepeatHeader: ((messageTimestamp - prevMsgTimestamp) / 60 / 1000) > Constants.repeatHeaderInterval property bool shouldRepeatHeader: ((messageTimestamp - prevMsgTimestamp) / 60 / 1000) > Constants.repeatHeaderInterval || isDiscordMessage
property bool hasMention: false property bool hasMention: false
property bool stickersLoaded: false property bool stickersLoaded: false
@ -111,11 +112,12 @@ Loader {
property int stickerPack: -1 property int stickerPack: -1
property bool isEmoji: messageContentType === Constants.messageContentType.emojiType property bool isEmoji: messageContentType === Constants.messageContentType.emojiType
property bool isImage: messageContentType === Constants.messageContentType.imageType property bool isImage: messageContentType === Constants.messageContentType.imageType || (isDiscordMessage && messageImage != "")
property bool isAudio: messageContentType === Constants.messageContentType.audioType property bool isAudio: messageContentType === Constants.messageContentType.audioType
property bool isStatusMessage: messageContentType === Constants.messageContentType.systemMessagePrivateGroupType property bool isStatusMessage: messageContentType === Constants.messageContentType.systemMessagePrivateGroupType
property bool isSticker: messageContentType === Constants.messageContentType.stickerType property bool isSticker: messageContentType === Constants.messageContentType.stickerType
property bool isText: messageContentType === Constants.messageContentType.messageType || messageContentType === Constants.messageContentType.editType property bool isDiscordMessage: messageContentType === Constants.messageContentType.discordMessageType
property bool isText: messageContentType === Constants.messageContentType.messageType || messageContentType === Constants.messageContentType.editType || isDiscordMessage
property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio property bool isMessage: isEmoji || isImage || isSticker || isText || isAudio
|| messageContentType === Constants.messageContentType.communityInviteType || messageContentType === Constants.messageContentType.transactionType || messageContentType === Constants.messageContentType.communityInviteType || messageContentType === Constants.messageContentType.transactionType
@ -411,7 +413,7 @@ Loader {
loadingImageText: qsTr("Loading image...") loadingImageText: qsTr("Loading image...")
errorLoadingImageText: qsTr("Error loading the image") errorLoadingImageText: qsTr("Error loading the image")
resendText: qsTr("Resend") resendText: qsTr("Resend")
pinnedMsgInfoText: qsTr("Pinned by") pinnedMsgInfoText: root.isDiscordMessage ? qsTr("Pinned") : qsTr("Pinned by")
reactionIcons: [ reactionIcons: [
Style.svg("emojiReactions/heart"), Style.svg("emojiReactions/heart"),
Style.svg("emojiReactions/thumbsUp"), Style.svg("emojiReactions/thumbsUp"),
@ -427,7 +429,7 @@ Loader {
isEdited: root.isEdited isEdited: root.isEdited
hasMention: root.hasMention hasMention: root.hasMention
isPinned: root.pinnedMessage isPinned: root.pinnedMessage
pinnedBy: root.pinnedMessage ? Utils.getContactDetailsAsJson(root.messagePinnedBy).displayName : "" pinnedBy: root.pinnedMessage && !root.isDiscordMessage ? Utils.getContactDetailsAsJson(root.messagePinnedBy).displayName : ""
hasExpired: root.isExpired hasExpired: root.isExpired
reactionsModel: root.reactionsModel reactionsModel: root.reactionsModel
@ -457,7 +459,8 @@ Loader {
return Utils.setColorAlpha(Style.current.blue, 0.1); return Utils.setColorAlpha(Style.current.blue, 0.1);
return "transparent"; return "transparent";
} }
profileClickable: !root.isDiscordMessage
messageAttachments: root.messageAttachments
timestampString: Utils.formatShortTime(timestamp, timestampString: Utils.formatShortTime(timestamp,
localAccountSensitiveSettings.is24hTimeFormat) localAccountSensitiveSettings.is24hTimeFormat)
@ -548,6 +551,7 @@ Loader {
messageDetails: StatusMessageDetails { messageDetails: StatusMessageDetails {
contentType: delegate.contentType contentType: delegate.contentType
messageOriginInfo: isDiscordMessage ? qsTr("Imported from discord") : ""
messageText: root.messageText messageText: root.messageText
messageContent: { messageContent: {
switch (delegate.contentType) switch (delegate.contentType)
@ -557,6 +561,9 @@ Loader {
case StatusMessage.ContentType.Image: case StatusMessage.ContentType.Image:
return root.messageImage; return root.messageImage;
} }
if (root.isDiscordMessage && root.messageImage != "") {
return root.messageImage
}
return ""; return "";
} }
@ -571,9 +578,11 @@ Loader {
width: 40 width: 40
height: 40 height: 40
name: root.senderIcon || "" name: root.senderIcon || ""
assetSettings.isImage: root.isDiscordMessage
pubkey: root.senderId pubkey: root.senderId
colorId: Utils.colorIdForPubkey(root.senderId) colorId: Utils.colorIdForPubkey(root.senderId)
colorHash: Utils.getColorHashAsJson(root.senderId) colorHash: Utils.getColorHashAsJson(root.senderId)
showRing: !root.isDiscordMessage
} }
} }
@ -602,6 +611,8 @@ Loader {
width: 20 width: 20
height: 20 height: 20
name: delegate.replyMessage ? delegate.replyMessage.senderIcon : "" name: delegate.replyMessage ? delegate.replyMessage.senderIcon : ""
assetSettings.isImage: delegate.replyMessage && delegate.replyMessage.messageContentType == Constants.discordMessageType
showRing: delegate.replyMessage && delegate.replyMessage.messageContentType != Constants.discordMessageType
pubkey: delegate.replySenderId pubkey: delegate.replySenderId
colorId: Utils.colorIdForPubkey(delegate.replySenderId) colorId: Utils.colorIdForPubkey(delegate.replySenderId)
colorHash: Utils.getColorHashAsJson(delegate.replySenderId) colorHash: Utils.getColorHashAsJson(delegate.replySenderId)

View File

@ -232,6 +232,7 @@ QtObject {
readonly property int communityInviteType: 9 readonly property int communityInviteType: 9
readonly property int gapType: 10 readonly property int gapType: 10
readonly property int editType: 11 readonly property int editType: 11
readonly property int discordMessageType: 12
} }
readonly property QtObject profilePicturesVisibility: QtObject { readonly property QtObject profilePicturesVisibility: QtObject {