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:
parent
90ce349675
commit
bf14b06d55
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -14,6 +14,7 @@ type
|
|||
Community = 9
|
||||
Gap = 10
|
||||
Edit = 11
|
||||
DiscordMessage = 12
|
||||
|
||||
type
|
||||
StatusType* {.pure.} = enum
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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. We’ll 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue