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]
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 =
result = CommunitySignal()
result.signalType = SignalType.CommunityFound
@ -46,6 +58,30 @@ proc fromEvent*(T: type DiscordCategoriesAndChannelsExtractedSignal, event: Json
result.errors[key] = err
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 =
result = HistoryArchivesSignal()
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 =
result = HistoryArchivesSignal.createFromEvent(event)
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"
HistoryArchivesUnseeded = "community.historyArchivesUnseeded"
HistoryArchiveDownloaded = "community.historyArchiveDownloaded"
DownloadingHistoryArchivesFinished = "community.downloadingHistoryArchivesFinished"
UpdateAvailable = "update.available"
DiscordCategoriesAndChannelsExtracted = "community.discordCategoriesAndChannelsExtracted"
StatusUpdatesTimedout = "status.updates.timedout"
DiscordCommunityImportFinished = "community.discordCommunityImportFinished"
DiscordCommunityImportProgress = "community.discordCommunityImportProgress"
Unknown
proc event*(self:SignalType):string =

View File

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

View File

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

View File

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

View File

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

View File

@ -70,6 +70,10 @@ proc init*(self: Controller) =
let args = DiscordCategoriesAndChannelsArgs(e)
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 =
result = self.communityService.getCommunityTags()
@ -113,6 +117,36 @@ proc createCommunity*(
pinMessageAllMembersEnabled,
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*(
self: Controller,
communityId: string,
@ -167,3 +201,6 @@ proc getStatusForContactWithId*(self: Controller, publicKey: string): StatusUpda
proc requestExtractDiscordChannelsAndCategories*(self: Controller, filesToImport: seq[string]) =
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.} =
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.} =
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.} =
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_channels_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/[member_item, member_model, section_model]
import ../../../global/global_singleton
@ -288,3 +292,44 @@ method onImportCommunityErrorOccured*(self: Module, error: string) =
method requestExtractDiscordChannelsAndCategories*(self: Module, filesToImport: seq[string]) =
self.view.setDiscordDataExtractionInProgress(true)
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_channels_model
import ./models/discord_channel_item
import ./models/discord_import_tasks_model
import ./models/discord_import_errors_model
QtObject:
type
@ -32,7 +34,15 @@ QtObject:
discordOldestMessageTimestamp: int
discordImportErrorsCount: int
discordImportWarningsCount: int
discordImportProgress: int
discordImportInProgress: bool
discordImportCancelled: bool
discordImportProgressStopped: bool
discordImportTasksModel: DiscordImportTasksModel
discordImportTasksModelVariant: QVariant
discordDataExtractionInProgress: bool
discordImportCommunityId: string
discordImportCommunityName: string
proc delete*(self: View) =
self.model.delete
@ -46,6 +56,8 @@ QtObject:
self.discordCategoriesModelVariant.delete
self.discordChannelsModel.delete
self.discordChannelsModelVariant.delete
self.discordImportTasksModel.delete
self.discordImportTasksModelVariant.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
@ -67,6 +79,12 @@ QtObject:
result.discordDataExtractionInProgress = false
result.discordImportWarningsCount = 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()
proc load*(self: View) =
@ -119,6 +137,62 @@ QtObject:
read = getDiscordImportErrorsCount
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) =
self.model.addItem(item)
self.communityAdded(item.id)
@ -174,6 +248,15 @@ QtObject:
QtProperty[QVariant] discordChannels:
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 getObservedItem(self: View): QVariant {.slot.} =
@ -204,6 +287,34 @@ QtObject:
read = getDiscordDataExtractionInProgress
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.} =
# Users always have to request to join a community but might
# get automatically accepted.
@ -219,6 +330,48 @@ QtObject:
self.delegate.createCommunity(name, description, introMessage, outroMessage, access, color, tags,
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.} =
self.delegate.deleteCommunityCategory(communityId, categoryId)
@ -288,15 +441,14 @@ QtObject:
self.setDiscordImportErrorsCount(0)
self.setDiscordImportWarningsCount(0)
proc clearFileList*(self: View) {.slot.} =
self.discordFileListModel.clearItems()
self.setDiscordImportErrorsCount(0)
self.setDiscordImportWarningsCount(0)
proc requestExtractDiscordChannelsAndCategories*(self: View) {.slot.} =
let filePaths = self.discordFileListModel.getSelectedFilePaths()
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.} =
if selected:
self.discordCategoriesModel.selectItem(id)
@ -316,6 +468,3 @@ QtObject:
if self.discordChannelsModel.allChannelsByCategoryUnselected(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/service/contacts/dto/contacts
import ../../../app_service/service/message/dto/message
export types.ContentType
import message_reaction_model, message_reaction_item, message_transaction_parameters_item
@ -64,6 +65,7 @@ type
mentionedUsersPks: seq[string]
senderTrustStatus: TrustStatus
senderEnsVerified: bool
messageAttachments: seq[string]
proc initItem*(
id,
@ -90,7 +92,8 @@ proc initItem*(
transactionParameters: TransactionParametersItem,
mentionedUsersPks: seq[string],
senderTrustStatus: TrustStatus,
senderEnsVerified: bool
senderEnsVerified: bool,
discordMessage: DiscordMessage
): Item =
result = Item()
result.id = id
@ -124,6 +127,23 @@ proc initItem*(
result.gapTo = 0
result.senderTrustStatus = senderTrustStatus
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 =
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) =
self.reactionsModel.removeReaction(emojiId, reactionId, didIRemoveThisReaction)
proc messageAttachments*(self: Item): seq[string] {.inline.} =
self.messageAttachments
proc links*(self: Item): seq[string] {.inline.} =
self.links

View File

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

View File

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

View File

@ -96,6 +96,15 @@ type DiscordImportError* = object
code*: int
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 =
result = CommunityAdminSettingsDto()
discard jsonObj.getProp("pinMessageAllMembersEnabled", result.pinMessageAllMembersEnabled)
@ -118,6 +127,21 @@ proc toDiscordImportError*(jsonObj: JsonNode): DiscordImportError =
discard jsonObj.getProp("code", result.code)
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 =
result = CommunityDto()
discard jsonObj.getProp("id", result.id)

View File

@ -80,6 +80,15 @@ type
errors*: Table[string, DiscordImportError]
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:
const SIGNAL_COMMUNITY_JOINED* = "communityJoined"
const SIGNAL_COMMUNITY_MY_REQUEST_ADDED* = "communityMyRequestAdded"
@ -107,6 +116,8 @@ const SIGNAL_COMMUNITY_MUTED* = "communityMuted"
const SIGNAL_CATEGORY_MUTED* = "categoryMuted"
const SIGNAL_CATEGORY_UNMUTED* = "categoryUnmuted"
const SIGNAL_DISCORD_CATEGORIES_AND_CHANNELS_EXTRACTED* = "discordCategoriesAndChannelsExtracted"
const SIGNAL_DISCORD_COMMUNITY_IMPORT_FINISHED* = "discordCommunityImportFinished"
const SIGNAL_DISCORD_COMMUNITY_IMPORT_PROGRESS* = "discordCommunityImportProgress"
QtObject:
type
@ -198,6 +209,22 @@ QtObject:
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) =
# This proc sets fields of `chatDto` which are available only for community channels.
chatDto.position = chat.position
@ -648,6 +675,49 @@ QtObject:
except Exception as e:
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*(
self: Service,
name: string,
@ -1226,3 +1296,9 @@ QtObject:
except Exception as e:
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
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
hash*: string
url*: string
@ -60,6 +83,7 @@ type MessageDto* = object
seen*: bool
outgoingStatus*: string
quotedMessage*: QuotedMessage
discordMessage*: DiscordMessage
rtl*: bool
parsedText*: seq[ParsedText]
lineCount*: int
@ -91,6 +115,41 @@ proc toParsedText*(jsonObj: JsonNode): ParsedText =
for childObj in childrenArr:
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 =
result = QuotedMessage()
discard jsonObj.getProp("from", result.from)
@ -151,6 +210,10 @@ proc toMessageDto*(jsonObj: JsonNode): MessageDto =
if(jsonObj.getProp("quotedMessage", quotedMessageObj)):
result.quotedMessage = toQuotedMessage(quotedMessageObj)
var discordMessageObj: JsonNode
if(jsonObj.getProp("discordMessage", discordMessageObj)):
result.discordMessage = toDiscordMessage(discordMessageObj)
var stickerObj: JsonNode
if(jsonObj.getProp("sticker", stickerObj)):
result.sticker = toSticker(stickerObj)

View File

@ -296,11 +296,13 @@ QtObject:
if (receivedData.emojiReactions.len > 0):
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)
if now().toTime().toUnix()-receivedData.begin <= WEEK_AS_MILLISECONDS:
# we don't need to reload the messages for archives older than 7 days
self.handleMessagesReload(receivedData.communityId)
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 =
return self.msgCursor.hasKey(chatId)

View File

@ -123,6 +123,45 @@ proc editCommunity*(
"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*(
communityId: string,
name: string,

View File

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

View File

@ -29,6 +29,7 @@ StatusSectionLayout {
property var importCommunitiesPopup: importCommunitiesPopupComponent
property var createCommunitiesPopup: createCommunitiesPopupComponent
property int contentPrefferedWidth: 100
property var discordImportProgressPopup: discordImportProgressDialog
notificationCount: root.communitiesStore.unreadNotificationsCount
onNotificationButtonClicked: Global.openActivityCenterPopup()
@ -219,9 +220,14 @@ StatusSectionLayout {
}
}
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")
icon.name: "download"
buttonTooltipText: qsTr("Your current import must finished or be cancelled before a new import can be started.")
buttonLoading: importInProgress
onButtonClicked: {
chooseCommunityCreationTypePopup.close()
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 {
type: root.store.discordImportErrorsCount ? IssuePill.Type.Error : IssuePill.Type.Warning
count: root.store.discordImportErrorsCount ? root.store.discordImportErrorsCount :
root.store.discordImportWarningsCount ? root.store.discordImportWarningsCount : 0
visible: count
count: {
if (root.store.discordImportErrorsCount > 0) {
return root.store.discordImportErrorsCount
}
if (root.store.discordImportWarningsCount > 0) {
return root.store.discordImportWarningsCount
}
return 0
}
visible: !!count
}
Item { Layout.fillWidth: true }
StatusButton {
@ -243,6 +250,20 @@ StatusStackModal {
readonly property bool canGoNext: root.store.discordChannelsModel.hasSelectedItems
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 {
@ -456,34 +477,46 @@ StatusStackModal {
QtObject {
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() {
const error = store.createCommunity({
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})
})
const error = root.store.createCommunity(_getCommunityConfig())
if (error) {
errorDialog.text = error.error
errorDialog.open()
}
root.close()
}
function requestImportDiscordCommunity() {
const error = root.store.requestImportDiscordCommunity(_getCommunityConfig(), datePicker.selectedDate.valueOf()/1000)
if (error) {
errorDialog.text = error.error
errorDialog.open()
}
}
}
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 int discordImportErrorsCount: root.communitiesModuleInst.discordImportErrorsCount
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 var advancedModule: profileSectionModule.advancedModule
property bool isCommunityHistoryArchiveSupportEnabled: advancedModule? advancedModule.isCommunityHistoryArchiveSupportEnabled : false
@ -105,5 +112,40 @@ QtObject {
function 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 {
id: connectedBannerComponent

View File

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

View File

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

View File

@ -18,6 +18,8 @@ Rectangle {
property alias text: bannerText.text
property alias buttonText: bannerButton.text
property alias icon: bannerIcon.asset
property string buttonTooltipText: ""
property bool buttonLoading: false
implicitWidth: 272
implicitHeight: 168
@ -74,7 +76,17 @@ Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: 16
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.Controls 2.13
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
@ -7,8 +7,9 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
import "../"
import "./"
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
Item {
id: root
@ -20,8 +21,10 @@ Item {
property bool active: false
property int type: ModuleWarning.Danger
property int progressValue: -1 // 0..100, -1 not visible
property string text: ""
property alias buttonText: button.text
property alias closeBtnVisible: closeImg.visible
signal clicked()
signal closeClicked()
@ -49,6 +52,8 @@ Item {
closeButtonMouseArea.clicked(null)
}
signal linkActivated(string link)
implicitHeight: root.active ? content.implicitHeight : 0
visible: implicitHeight > 0
@ -123,14 +128,24 @@ Item {
id: layout
spacing: 12
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.centerIn: parent
StatusBaseText {
text: root.text
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 13
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
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 {
@ -140,9 +155,9 @@ Item {
onClicked: {
root.clicked()
}
contentItem: Text {
contentItem: StatusBaseText {
text: button.text
font.pixelSize: 12
font.pixelSize: 13
font.weight: Font.Medium
font.family: Style.current.baseFont.name
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 {
id: closeImg
anchors.verticalCenter: parent.verticalCenter
@ -185,5 +236,4 @@ Item {
}
}
}
}

View File

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

View File

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

View File

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