feat: create community categories

This commit is contained in:
Richard Ramos 2021-05-16 11:16:42 -04:00 committed by Iuri Matias
parent a79400e023
commit 0ded790f0d
22 changed files with 689 additions and 9 deletions

View File

@ -0,0 +1,70 @@
import NimQml, Tables
import ../../../status/chat/[chat, message]
import ../../../status/status
import ../../../status/ens
import ../../../status/accounts
import strutils
type
CategoryRoles {.pure.} = enum
Id = UserRole + 1
Name = UserRole + 2
Position = UserRole + 3
QtObject:
type
CategoryList* = ref object of QAbstractListModel
categories*: seq[CommunityCategory]
status: Status
proc setup(self: CategoryList) = self.QAbstractListModel.setup
proc delete(self: CategoryList) =
self.categories = @[]
self.QAbstractListModel.delete
proc newCategoryList*(status: Status): CategoryList =
new(result, delete)
result.categories = @[]
result.status = status
result.setup()
method rowCount*(self: CategoryList, index: QModelIndex = nil): int = self.categories.len
method data(self: CategoryList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.categories.len:
return
let catItem = self.categories[index.row]
let catItemRole = role.CategoryRoles
case catItemRole:
of CategoryRoles.Id: result = newQVariant(catItem.id)
of CategoryRoles.Name: result = newQVariant(catItem.name)
of CategoryRoles.Position: result = newQVariant(catItem.position)
method roleNames(self: CategoryList): Table[int, string] =
{
CategoryRoles.Name.int:"name",
CategoryRoles.Position.int:"position",
CategoryRoles.Id.int: "categoryId"
}.toTable
proc setCategories*(self: CategoryList, categories: seq[CommunityCategory]) =
self.beginResetModel()
self.categories = categories
self.endResetModel()
proc addCategoryToList*(self: CategoryList, category: CommunityCategory): int =
self.beginInsertRows(newQModelIndex(), 0, 0)
self.categories.insert(category, 0)
self.endInsertRows()
result = self.categories.len
proc removeCategoryFromList*(self: CategoryList, categoryId: string): int =
let idx = self.categories.findIndexById(categoryId)
if idx == -1: return
self.beginRemoveRows(newQModelIndex(), idx, idx)
self.categories.delete(idx)
self.endRemoveRows()
result = self.categories.len

View File

@ -19,6 +19,7 @@ type
Muted = UserRole + 10 Muted = UserRole + 10
Id = UserRole + 11 Id = UserRole + 11
Description = UserRole + 12 Description = UserRole + 12
CategoryId = UserRole + 13
QtObject: QtObject:
type type
@ -76,6 +77,7 @@ QtObject:
of ChannelsRoles.HasMentions: result = newQVariant(chatItem.hasMentions) of ChannelsRoles.HasMentions: result = newQVariant(chatItem.hasMentions)
of ChannelsRoles.Muted: result = newQVariant(chatItem.muted.bool) of ChannelsRoles.Muted: result = newQVariant(chatItem.muted.bool)
of ChannelsRoles.Id: result = newQVariant($chatItem.id) of ChannelsRoles.Id: result = newQVariant($chatItem.id)
of ChannelsRoles.CategoryId: result = newQVariant(chatItem.categoryId)
of ChannelsRoles.Description: result = newQVariant(chatItem.description) of ChannelsRoles.Description: result = newQVariant(chatItem.description)
method roleNames(self: ChannelsList): Table[int, string] = method roleNames(self: ChannelsList): Table[int, string] =
@ -91,7 +93,8 @@ QtObject:
ChannelsRoles.ContentType.int: "contentType", ChannelsRoles.ContentType.int: "contentType",
ChannelsRoles.Muted.int: "muted", ChannelsRoles.Muted.int: "muted",
ChannelsRoles.Id.int: "id", ChannelsRoles.Id.int: "id",
ChannelsRoles.Description.int: "description" ChannelsRoles.Description.int: "description",
ChannelsRoles.CategoryId.int: "categoryId"
}.toTable }.toTable
proc setChats*(self: ChannelsList, chats: seq[Chat]) = proc setChats*(self: ChannelsList, chats: seq[Chat]) =

View File

@ -54,8 +54,9 @@ QtObject:
var found = false var found = false
for chat in community.chats: for chat in community.chats:
if (chat.id == newChat.id): if (chat.id == newChat.id):
# canPost is not available in the newChat so we need to check what we had before # canPost and categoryId are not available in the newChat so we need to check what we had before
newChat.canPost = community.chats[i].canPost newChat.canPost = community.chats[i].canPost
newChat.categoryId = community.chats[i].categoryId
community.chats[i] = newChat community.chats[i] = newChat
found = true found = true
i = i + 1 i = i + 1
@ -225,7 +226,7 @@ QtObject:
error "Error creating the community", msg = e.msg error "Error creating the community", msg = e.msg
result = fmt"Error creating the community: {e.msg}" result = fmt"Error creating the community: {e.msg}"
proc createCommunityChannel*(self: CommunitiesView, communityId: string, name: string, description: string): string {.slot.} = proc createCommunityChannel*(self: CommunitiesView, communityId: string, name: string, description: string, categoryId: string): string {.slot.} =
result = "" result = ""
try: try:
let chat = self.status.chat.createCommunityChannel(communityId, name, description) let chat = self.status.chat.createCommunityChannel(communityId, name, description)
@ -233,12 +234,38 @@ QtObject:
if (chat.id == ""): if (chat.id == ""):
return "Chat was not created. Please try again later" return "Chat was not created. Please try again later"
if categoryId != "":
self.status.chat.reorderCommunityChannel(communityId, categoryId, chat.id.replace(communityId, ""), 0)
chat.categoryId = categoryId
self.joinedCommunityList.addChannelToCommunity(communityId, chat) self.joinedCommunityList.addChannelToCommunity(communityId, chat)
self.activeCommunity.addChatItemToList(chat) self.activeCommunity.addChatItemToList(chat)
except Exception as e: except Exception as e:
error "Error creating the channel", msg = e.msg error "Error creating the channel", msg = e.msg
result = fmt"Error creating the channel: {e.msg}" result = fmt"Error creating the channel: {e.msg}"
proc createCommunityCategory*(self: CommunitiesView, communityId: string, name: string, channels: string): string {.slot.} =
result = ""
try:
let channelSeq = map(parseJson(channels).getElems(), proc(x:JsonNode):string = x.getStr().replace(communityId, ""))
let category = self.status.chat.createCommunityCategory(communityId, name, channelSeq)
self.joinedCommunityList.addCategoryToCommunity(communityId, category)
self.activeCommunity.addCategoryToList(category)
except Exception as e:
error "Error creating the category", msg = e.msg
result = fmt"Error creating the category: {e.msg}"
proc deleteCommunityCategory*(self: CommunitiesView, communityId: string, categoryId: string): string {.slot.} =
result = ""
try:
self.status.chat.deleteCommunityCategory(communityId, categoryId)
self.joinedCommunityList.removeCategoryFromCommunity(communityId, categoryId)
self.activeCommunity.removeCategoryFromList(categoryId)
except Exception as e:
error "Error creating the category", msg = e.msg
result = fmt"Error creating the category: {e.msg}"
proc observedCommunityChanged*(self: CommunitiesView) {.signal.} proc observedCommunityChanged*(self: CommunitiesView) {.signal.}
proc setObservedCommunity*(self: CommunitiesView, communityId: string) {.slot.} = proc setObservedCommunity*(self: CommunitiesView, communityId: string) {.slot.} =

View File

@ -3,6 +3,7 @@ import ../../../status/[chat/chat, status]
import channels_list import channels_list
import ../../../eventemitter import ../../../eventemitter
import community_members_list import community_members_list
import category_list
import community_membership_request_list import community_membership_request_list
QtObject: QtObject:
@ -10,6 +11,7 @@ QtObject:
communityItem*: Community communityItem*: Community
communityMembershipRequestList*: CommunityMembershipRequestList communityMembershipRequestList*: CommunityMembershipRequestList
chats*: ChannelsList chats*: ChannelsList
categories*: CategoryList
members*: CommunityMembersView members*: CommunityMembersView
status*: Status status*: Status
active*: bool active*: bool
@ -19,6 +21,7 @@ QtObject:
proc delete*(self: CommunityItemView) = proc delete*(self: CommunityItemView) =
if not self.chats.isNil: self.chats.delete if not self.chats.isNil: self.chats.delete
if not self.categories.isNil: self.categories.delete
self.QObject.delete self.QObject.delete
proc newCommunityItemView*(status: Status): CommunityItemView = proc newCommunityItemView*(status: Status): CommunityItemView =
@ -27,6 +30,7 @@ QtObject:
result.status = status result.status = status
result.active = false result.active = false
result.chats = newChannelsList(status) result.chats = newChannelsList(status)
result.categories = newCategoryList(status)
result.communityMembershipRequestList = newCommunityMembershipRequestList() result.communityMembershipRequestList = newCommunityMembershipRequestList()
result.members = newCommunityMembersView(status) result.members = newCommunityMembersView(status)
result.setup result.setup
@ -36,6 +40,7 @@ QtObject:
proc setCommunityItem*(self: CommunityItemView, communityItem: Community) = proc setCommunityItem*(self: CommunityItemView, communityItem: Community) =
self.communityItem = communityItem self.communityItem = communityItem
self.chats.setChats(communityItem.chats) self.chats.setChats(communityItem.chats)
self.categories.setCategories(communityItem.categories)
self.members.setMembers(communityItem.members) self.members.setMembers(communityItem.members)
self.nbMembersChanged() self.nbMembersChanged()
self.communityMembershipRequestList.setNewData(communityItem.membershipRequests) self.communityMembershipRequestList.setNewData(communityItem.membershipRequests)
@ -135,6 +140,9 @@ QtObject:
proc getChats*(self: CommunityItemView): QVariant {.slot.} = proc getChats*(self: CommunityItemView): QVariant {.slot.} =
result = newQVariant(self.chats) result = newQVariant(self.chats)
proc getCategories*(self: CommunityItemView): QVariant {.slot.} =
result = newQVariant(self.categories)
proc changeChats*(self: CommunityItemView, chats: seq[Chat]) = proc changeChats*(self: CommunityItemView, chats: seq[Chat]) =
self.communityItem.chats = chats self.communityItem.chats = chats
self.chats.setChats(chats) self.chats.setChats(chats)
@ -145,10 +153,26 @@ QtObject:
discard self.chats.addChatItemToList(chat) discard self.chats.addChatItemToList(chat)
self.chatsChanged() self.chatsChanged()
proc addCategoryToList*(self: CommunityItemView, category: CommunityCategory) =
self.communityItem.categories.add(category)
discard self.categories.addCategoryToList(category)
self.chatsChanged()
proc removeCategoryFromList*(self: CommunityItemView, categoryId: string) =
discard self.categories.removeCategoryFromList(categoryId)
let idx = self.communityItem.categories.findIndexById(categoryId)
self.chatsChanged()
if idx == -1: return
self.communityItem.categories.delete(idx)
QtProperty[QVariant] chats: QtProperty[QVariant] chats:
read = getChats read = getChats
notify = chatsChanged notify = chatsChanged
QtProperty[QVariant] categories:
read = getCategories
notify = chatsChanged
proc getMembers*(self: CommunityItemView): QVariant {.slot.} = proc getMembers*(self: CommunityItemView): QVariant {.slot.} =
result = newQVariant(self.members) result = newQVariant(self.members)

View File

@ -129,6 +129,13 @@ QtObject:
let index = self.communities.findIndexById(communityId) let index = self.communities.findIndexById(communityId)
self.communities[index] = community self.communities[index] = community
proc addCategoryToCommunity*(self: CommunityList, communityId: string, category: CommunityCategory) =
var community = self.getCommunityById(communityId)
community.categories.add(category)
let index = self.communities.findIndexById(communityId)
self.communities[index] = community
proc replaceCommunity*(self: CommunityList, community: Community) = proc replaceCommunity*(self: CommunityList, community: Community) =
let index = self.communities.findIndexById(community.id) let index = self.communities.findIndexById(community.id)
if (index == -1): if (index == -1):
@ -136,4 +143,12 @@ QtObject:
let topLeft = self.createIndex(index, index, nil) let topLeft = self.createIndex(index, index, nil)
let bottomRight = self.createIndex(index, index, nil) let bottomRight = self.createIndex(index, index, nil)
self.communities[index] = community self.communities[index] = community
self.dataChanged(topLeft, bottomRight, @[CommunityRoles.Name.int, CommunityRoles.Description.int, CommunityRoles.UnviewedMessagesCount.int]) self.dataChanged(topLeft, bottomRight, @[CommunityRoles.Name.int, CommunityRoles.Description.int, CommunityRoles.UnviewedMessagesCount.int])
proc removeCategoryFromCommunity*(self: CommunityList, communityId: string, categoryId:string) =
var community = self.getCommunityById(communityId)
let idx = community.categories.findIndexById(categoryId)
if idx == -1: return
community.categories.delete(idx)
let index = self.communities.findIndexById(communityId)
self.communities[index] = community

View File

@ -447,6 +447,15 @@ proc createCommunity*(self: ChatModel, name: string, description: string, access
proc createCommunityChannel*(self: ChatModel, communityId: string, name: string, description: string): Chat = proc createCommunityChannel*(self: ChatModel, communityId: string, name: string, description: string): Chat =
result = status_chat.createCommunityChannel(communityId, name, description) result = status_chat.createCommunityChannel(communityId, name, description)
proc createCommunityCategory*(self: ChatModel, communityId: string, name: string, channels: seq[string]): CommunityCategory =
result = status_chat.createCommunityCategory(communityId, name, channels)
proc deleteCommunityCategory*(self: ChatModel, communityId: string, categoryId: string) =
status_chat.deleteCommunityCategory(communityId, categoryId)
proc reorderCommunityChannel*(self: ChatModel, communityId: string, categoryId: string, chatId: string, position: int) =
status_chat.reorderCommunityChat(communityId, categoryId, chatId, position)
proc joinCommunity*(self: ChatModel, communityId: string) = proc joinCommunity*(self: ChatModel, communityId: string) =
status_chat.joinCommunity(communityId) status_chat.joinCommunity(communityId)

View File

@ -60,6 +60,7 @@ proc toJsonNode*(self: seq[ChatMembershipEvent]): seq[JsonNode] =
type Chat* = ref object type Chat* = ref object
id*: string # ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one is the hex encoded public key and for group chats is a random uuid appended with the hex encoded pk of the creator of the chat id*: string # ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one is the hex encoded public key and for group chats is a random uuid appended with the hex encoded pk of the creator of the chat
communityId*: string communityId*: string
categoryId*: string
name*: string name*: string
description*: string description*: string
color*: string color*: string
@ -93,12 +94,18 @@ type CommunityMembershipRequest* = object
state*: int state*: int
our*: string our*: string
type CommunityCategory* = object
id*: string
name*: string
position*: int
type Community* = object type Community* = object
id*: string id*: string
name*: string name*: string
lastChannelSeen*: string lastChannelSeen*: string
description*: string description*: string
chats*: seq[Chat] chats*: seq[Chat]
categories*: seq[CommunityCategory]
members*: seq[string] members*: seq[string]
access*: int access*: int
unviewedMessagesCount*: int unviewedMessagesCount*: int
@ -164,6 +171,15 @@ proc findIndexById*(self: seq[CommunityMembershipRequest], id: string): int =
result = idx result = idx
break break
proc findIndexById*(self: seq[CommunityCategory], id: string): int =
result = -1
var idx = -1
for item in self:
inc idx
if(item.id == id):
result = idx
break
proc isMember*(self: Chat, pubKey: string): bool = proc isMember*(self: Chat, pubKey: string): bool =
for member in self.members: for member in self.members:
if member.id == pubKey and member.joined: return true if member.id == pubKey and member.joined: return true

View File

@ -332,6 +332,42 @@ proc createCommunityChannel*(communityId: string, name: string, description: str
if rpcResult{"result"}.kind != JNull: if rpcResult{"result"}.kind != JNull:
result = rpcResult["result"]["chats"][0].toChat() result = rpcResult["result"]["chats"][0].toChat()
proc createCommunityCategory*(communityId: string, name: string, channels: seq[string]): CommunityCategory =
let rpcResult = callPrivateRPC("createCommunityCategory".prefix, %*[
{
"communityId": communityId,
"categoryName": name,
"chatIds": channels
}]).parseJSON()
if rpcResult.contains("error"):
raise newException(StatusGoException, rpcResult["error"]["message"].getStr())
else:
for k, v in rpcResult["result"]["communityChanges"].getElems()[0]["categoriesAdded"].pairs():
result.id = v["category_id"].getStr()
result.name = v["name"].getStr()
result.position = v{"position"}.getInt()
proc reorderCommunityChat*(communityId: string, categoryId: string, chatId: string, position: int) =
let rpcResult = callPrivateRPC("reorderCommunityChat".prefix, %*[
{
"communityId": communityId,
"categoryId": categoryId,
"chatId": chatId,
"position": position
}]).parseJSON()
if rpcResult.contains("error"):
raise newException(StatusGoException, rpcResult["error"]["message"].getStr())
proc deleteCommunityCategory*(communityId: string, categoryId: string) =
let rpcResult = callPrivateRPC("deleteCommunityCategory".prefix, %*[
{
"communityId": communityId,
"categoryId": categoryId
}]).parseJSON()
if rpcResult.contains("error"):
raise newException(StatusGoException, rpcResult["error"]["message"].getStr())
proc requestCommunityInfo*(communityId: string) = proc requestCommunityInfo*(communityId: string) =
discard callPrivateRPC("requestCommunityInfoFromMailserver".prefix, %*[communityId]) discard callPrivateRPC("requestCommunityInfoFromMailserver".prefix, %*[communityId])

View File

@ -201,6 +201,7 @@ proc toCommunity*(jsonCommunity: JsonNode): Community =
for chatId, chat in jsonCommunity{"chats"}: for chatId, chat in jsonCommunity{"chats"}:
result.chats.add(Chat( result.chats.add(Chat(
id: result.id & chatId, id: result.id & chatId,
categoryId: chat{"categoryID"}.getStr(),
communityId: result.id, communityId: result.id,
name: chat{"name"}.getStr, name: chat{"name"}.getStr,
canPost: chat{"canPost"}.getBool, canPost: chat{"canPost"}.getBool,
@ -209,6 +210,14 @@ proc toCommunity*(jsonCommunity: JsonNode): Community =
#chat{"permissions"}{"access"}.getInt, #chat{"permissions"}{"access"}.getInt,
)) ))
if jsonCommunity.hasKey("categories") and jsonCommunity["categories"].kind != JNull:
for catId, cat in jsonCommunity{"categories"}:
result.categories.add(CommunityCategory(
id: catId,
name: cat{"name"}.getStr,
position: cat{"position"}.getInt
))
if jsonCommunity.hasKey("members") and jsonCommunity["members"].kind != JNull: if jsonCommunity.hasKey("members") and jsonCommunity["members"].kind != JNull:
# memberInfo is empty for now # memberInfo is empty for now
for memberPubKey, memeberInfo in jsonCommunity{"members"}: for memberPubKey, memeberInfo in jsonCommunity{"members"}:

View File

@ -26,6 +26,15 @@ Rectangle {
} }
} }
Component {
id: createCategoryPopup
CreateCategoryPopup {
onClosed: {
destroy()
}
}
}
Component { Component {
id: transferOwnershipPopup id: transferOwnershipPopup
TransferOwnershipPopup {} TransferOwnershipPopup {}
@ -82,6 +91,17 @@ Rectangle {
onTriggered: openPopup(createChannelPopup, {communityId: chatsModel.communities.activeCommunity.id}) onTriggered: openPopup(createChannelPopup, {communityId: chatsModel.communities.activeCommunity.id})
} }
Action {
enabled: chatsModel.communities.activeCommunity.admin
text: qsTr("Create category")
icon.source: "../../img/create-category.svg"
icon.width: 20
icon.height: 20
onTriggered: openPopup(createCategoryPopup, {communityId: chatsModel.communities.activeCommunity.id})
}
Separator {}
Action { Action {
text: qsTr("Invite People") text: qsTr("Invite People")
enabled: chatsModel.communities.activeCommunity.canManageUsers enabled: chatsModel.communities.activeCommunity.canManageUsers
@ -152,21 +172,28 @@ Rectangle {
leftPadding: Style.current.halfPadding leftPadding: Style.current.halfPadding
rightPadding: Style.current.halfPadding rightPadding: Style.current.halfPadding
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
contentHeight: channelList.height + emptyViewAndSuggestionsLoader.height + backUpBannerLoader.height + 2 * Style.current.padding contentHeight: categoryList.height + channelList.height + emptyViewAndSuggestionsLoader.height + backUpBannerLoader.height + 2 * Style.current.padding
clip: true clip: true
ChannelList { ChannelList {
id: channelList id: channelList
searchStr: "" searchStr: ""
categoryId: ""
channelModel: chatsModel.communities.activeCommunity.chats channelModel: chatsModel.communities.activeCommunity.chats
} }
CategoryList {
id: categoryList
anchors.top: channelList.bottom
categoryModel: chatsModel.communities.activeCommunity.categories
}
Loader { Loader {
id: emptyViewAndSuggestionsLoader id: emptyViewAndSuggestionsLoader
active: chatsModel.communities.activeCommunity.admin && !appSettings.hiddenCommunityWelcomeBanners.includes(chatsModel.communities.activeCommunity.id) active: chatsModel.communities.activeCommunity.admin && !appSettings.hiddenCommunityWelcomeBanners.includes(chatsModel.communities.activeCommunity.id)
width: parent.width width: parent.width
height: active ? item.height : 0 height: active ? item.height : 0
anchors.top: channelList.bottom anchors.top: categoryList.bottom
anchors.topMargin: active ? Style.current.padding : 0 anchors.topMargin: active ? Style.current.padding : 0
sourceComponent: Component { sourceComponent: Component {
CommunityWelcomeBanner {} CommunityWelcomeBanner {}

View File

@ -0,0 +1,193 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../../../../shared"
import "../../../../shared/status"
import "../../../../imports"
import "../components"
import "./"
import "../ContactsColumn"
import "../CommunityComponents"
import QtQuick.Dialogs 1.2
Column {
id: categoryListContent
property var categoryModel
property string categoryToDelete: ""
height: childrenRect.height
width: parent.width
spacing: 2
Repeater {
model: categoryListContent.categoryModel
delegate: Item {
id: wrapper
property bool showCategory: true
property color color: {
if (hhandler.hovered) {
return Style.current.menuBackgroundHover
}
return Style.current.transparent
}
height: showCategory ? categoryHeader.height + channelList.height : categoryHeader.height
width: categoryListContent.width
Rectangle {
id: categoryHeader
color: wrapper.color
radius: 8
height: 40
width: categoryListContent.width
StyledText {
text: model.name
elide: Text.ElideRight
color: Style.current.textColor
font.weight: Font.Medium
font.pixelSize: 15
anchors.left: parent.left
anchors.leftMargin: Style.current.halfPadding
anchors.verticalCenter: parent.verticalCenter
}
StatusIconButton {
visible: hhandler.hovered && chatsModel.communities.activeCommunity.admin
id: addBtn
icon.name: "add-category"
width: 20
height: 20
anchors.right: moreBtn.left
anchors.rightMargin: Style.current.halfPadding
anchors.verticalCenter: parent.verticalCenter
iconColor: Style.current.textColor
highlightedIconColor: Style.current.textColor
hoveredIconColor: Style.current.textColor
highlightedBackgroundColor: Style.current.menuBackgroundHover
onClicked: {
openPopup(createChannelPopup, {communityId: chatsModel.communities.activeCommunity.id, categoryId: model.categoryId})
}
StatusToolTip {
visible: addBtn.hovered
text: qsTr("Add channel inside category")
}
}
StatusIconButton {
visible: hhandler.hovered
id: moreBtn
icon.name: "more"
width: 20
height: 20
anchors.right: showBtn.left
anchors.rightMargin: Style.current.halfPadding
anchors.verticalCenter: parent.verticalCenter
iconColor: Style.current.textColor
highlightedIconColor: Style.current.textColor
hoveredIconColor: Style.current.textColor
highlightedBackgroundColor: Style.current.menuBackgroundHover
onClicked: contextMenu.popup()
StatusToolTip {
visible: moreBtn.hovered
text: qsTr("More")
}
PopupMenu {
id: contextMenu
Action {
enabled: chatsModel.communities.activeCommunity.admin
text: qsTr("Edit category")
icon.source: "../../../img/edit.svg"
icon.width: 20
icon.height: 20
}
Separator {
visible: chatsModel.communities.activeCommunity.admin
}
Action {
text: qsTr("Delete category")
enabled: chatsModel.communities.activeCommunity.admin
icon.source: "../../../img/delete.svg"
icon.color: Style.current.red
icon.width: 20
icon.height: 20
onTriggered: {
categoryToDelete = model.categoryId
openPopup(deleteCategoryConfirmationDialogComponent, {
title: qsTr("Delete %1 category").arg(model.name),
confirmationText: qsTr("Are you sure you want to delete %1 category? Channels inside the category wont be deleted.").arg(model.name)
})
}
}
}
}
StatusIconButton {
visible: hhandler.hovered
id: showBtn
icon.name: showCategory ? "hide-category" : "show-category"
width: 20
height: 20
anchors.right: parent.right
anchors.rightMargin: Style.current.halfPadding
anchors.verticalCenter: parent.verticalCenter
iconColor: Style.current.textColor
highlightedIconColor: Style.current.textColor
hoveredIconColor: Style.current.textColor
highlightedBackgroundColor: Style.current.menuBackgroundHover
onClicked: {
showCategory = !showCategory
}
}
HoverHandler {
id: hhandler
}
}
ChannelList {
id: channelList
searchStr: ""
categoryId: model.categoryId
visible: showCategory
height: showCategory ? channelList.childrenRect.height : 0
anchors.top: categoryHeader.bottom
width: categoryListContent.width
channelModel: chatsModel.communities.activeCommunity.chats
}
MessageDialog {
id: deleteError
title: qsTr("Error deleting the category")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
Component {
id: deleteCategoryConfirmationDialogComponent
ConfirmationDialog {
btnType: "warn"
height: 216
showCancelButton: true
onClosed: {
destroy()
}
onCancelButtonClicked: {
close();
}
onConfirmButtonClicked: function(){
const error = chatsModel.communities.deleteCommunityCategory(chatsModel.communities.activeCommunity.id, categoryToDelete)
if (error) {
creatingError.text = error
return creatingError.open()
}
close();
}
}
}
}
}
}

View File

@ -0,0 +1,73 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
Rectangle {
property string name: "channel-name"
property string channelId: "channel-id"
property string categoryId: ""
property var onItemChecked
property bool isHovered: false
id: container
visible: categoryId == ""
height: visible ? 52 : 0
width: 425
anchors.right: parent.right
anchors.left: parent.left
anchors.leftMargin: 0
anchors.rightMargin: 0
border.width: 0
radius: Style.current.radius
color: isHovered ? Style.current.backgroundHover : Style.current.transparent
StatusIdenticon {
id: channelImage
height: 36
width: 36
chatId: name
chatName: name
chatType: Constants.chatTypePublic
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
id: channelName
text: "#" + name
elide: Text.ElideRight
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
font.pixelSize: 15
anchors.verticalCenter: parent.verticalCenter
anchors.left: channelImage.right
anchors.leftMargin: Style.current.halfPadding
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onEntered: container.isHovered = true
onExited: container.isHovered = false
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
chk.checked = !chk.checked
}
}
StatusCheckBox {
id: chk
anchors.top: channelImage.top
anchors.topMargin: 6
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
onClicked: {
onItemChecked(container.channelId, chk.checked)
}
}
}

View File

@ -0,0 +1,153 @@
import QtQuick 2.12
import QtQuick.Controls 2.3
import QtGraphicalEffects 1.13
import QtQuick.Dialogs 1.3
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
ModalPopup {
property string communityId
readonly property int maxDescChars: 140
property string nameValidationError: ""
property bool isValid: nameInput.isValid
property var channels: []
id: popup
height: 453
onOpened: {
nameInput.text = "";
nameInput.forceActiveFocus(Qt.MouseFocusReason)
}
onClosed: destroy()
function validate() {
nameInput.validate()
return isValid
}
title: qsTr("New category")
ScrollView {
property ScrollBar vScrollBar: ScrollBar.vertical
id: scrollView
anchors.fill: parent
rightPadding: Style.current.padding
anchors.rightMargin: - Style.current.halfPadding
contentHeight: content.height
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
clip: true
function scrollBackUp() {
vScrollBar.setPosition(0)
}
Item {
id: content
height: childrenRect.height
width: parent.width
Input {
id: nameInput
placeholderText: qsTr("Category title")
validationError: popup.nameValidationError
property bool isValid: false
onTextEdited: {
validate()
}
function validate() {
validationError = ""
if (nameInput.text === "") {
//% "You need to enter a name"
validationError = qsTrId("you-need-to-enter-a-name")
} else if (nameInput.text.length > 100) {
//% "Your name needs to be 100 characters or shorter"
validationError = qsTrId("your-name-needs-to-be-100-characters-or-shorter")
}
isValid = validationError === ""
return validationError
}
}
Separator {
id: sep
anchors.left: parent.left
anchors.right: parent.right
anchors.top: nameInput.bottom
anchors.topMargin: Style.current.padding
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
}
StatusSectionHeadline {
id: chatsTitle
text: qsTr("Chats")
anchors.top: sep.bottom
anchors.topMargin: Style.current.smallPadding
}
ListView {
height: childrenRect.height
model: chatsModel.communities.activeCommunity.chats
anchors.top: chatsTitle.bottom
anchors.topMargin: Style.current.smallPadding
anchors.left: parent.left
anchors.right: parent.right
delegate: CommunityChannel {
name: model.name
channelId: model.id
categoryId: model.categoryId
onItemChecked: function(channelId, itemChecked){
var idx = channels.indexOf(channelId)
if(itemChecked){
if(idx === -1){
channels.push(channelId)
}
} else {
if(idx > -1){
channels.splice(idx, 1);
}
}
}
}
}
}
}
footer: StatusButton {
enabled: popup.isValid
//% "Create"
text: qsTrId("create")
anchors.right: parent.right
onClicked: {
if (!validate()) {
scrollView.scrollBackUp()
return
}
const error = chatsModel.communities.createCommunityCategory(communityId, Utils.filterXSS(nameInput.text), JSON.stringify(channels))
if (error) {
creatingError.text = error
return creatingError.open()
}
popup.close()
}
MessageDialog {
id: creatingError
title: qsTr("Error creating the category")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
}
}

View File

@ -8,6 +8,7 @@ import "../../../../shared/status"
ModalPopup { ModalPopup {
property string communityId property string communityId
property string categoryId: ""
readonly property int maxDescChars: 140 readonly property int maxDescChars: 140
property string nameValidationError: "" property string nameValidationError: ""
property bool isValid: property bool isValid:
@ -177,7 +178,8 @@ ModalPopup {
} }
const error = chatsModel.communities.createCommunityChannel(communityId, const error = chatsModel.communities.createCommunityChannel(communityId,
Utils.filterXSS(nameInput.text), Utils.filterXSS(nameInput.text),
Utils.filterXSS(descriptionTextArea.text)) Utils.filterXSS(descriptionTextArea.text),
categoryId)
if (error) { if (error) {
creatingError.text = error creatingError.text = error

View File

@ -7,6 +7,7 @@ import "../components"
Item { Item {
property string chatId: "" property string chatId: ""
property string categoryId: ""
property string name: "channelName" property string name: "channelName"
property string lastMessage: "My latest message\n with a return" property string lastMessage: "My latest message\n with a return"
property string timestamp: "1605212622434" property string timestamp: "1605212622434"
@ -22,6 +23,7 @@ Item {
return chatType return chatType
} }
property string filterCategory: ""
property string searchStr: "" property string searchStr: ""
property bool isCompact: appSettings.useCompactMode property bool isCompact: appSettings.useCompactMode
property int contentType: 1 property int contentType: 1
@ -41,7 +43,7 @@ Item {
property string profileImage: realChatType === Constants.chatTypeOneToOne ? appMain.getProfileImage(chatId) || "" : "" property string profileImage: realChatType === Constants.chatTypeOneToOne ? appMain.getProfileImage(chatId) || "" : ""
// Hide the box if it is filtered out // Hide the box if it is filtered out
property bool isVisible: searchStr === "" || name.includes(searchStr) property bool isVisible: categoryId == filterCategory && (searchStr === "" || name.includes(searchStr))
id: wrapper id: wrapper
anchors.right: parent.right anchors.right: parent.right

View File

@ -10,6 +10,7 @@ Item {
property var channelModel property var channelModel
property alias channelListCount: chatGroupsListView.count property alias channelListCount: chatGroupsListView.count
property string searchStr: "" property string searchStr: ""
property string categoryId: ""
id: channelListContent id: channelListContent
width: parent.width width: parent.width
height: childrenRect.height height: childrenRect.height
@ -39,6 +40,8 @@ Item {
hasMentions: model.hasMentions hasMentions: model.hasMentions
contentType: model.contentType contentType: model.contentType
searchStr: channelListContent.searchStr searchStr: channelListContent.searchStr
categoryId: model.categoryId
filterCategory: channelListContent.categoryId
chatId: model.id chatId: model.id
} }
onCountChanged: { onCountChanged: {

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.9375 5C10.9375 4.48223 10.5178 4.0625 10 4.0625C9.48223 4.0625 9.0625 4.48223 9.0625 5V8.5625C9.0625 8.83864 8.83864 9.0625 8.5625 9.0625H5C4.48223 9.0625 4.0625 9.48223 4.0625 10C4.0625 10.5178 4.48223 10.9375 5 10.9375H8.5625C8.83864 10.9375 9.0625 11.1614 9.0625 11.4375V15C9.0625 15.5178 9.48223 15.9375 10 15.9375C10.5178 15.9375 10.9375 15.5178 10.9375 15V11.4375C10.9375 11.1614 11.1614 10.9375 11.4375 10.9375H15C15.5178 10.9375 15.9375 10.5178 15.9375 10C15.9375 9.48223 15.5178 9.0625 15 9.0625H11.4375C11.1614 9.0625 10.9375 8.83864 10.9375 8.5625V5Z" fill="black" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 753 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00001 13.6667C11.1296 13.6667 13.6667 11.1297 13.6667 8.00004C13.6667 4.87043 11.1296 2.33337 8.00001 2.33337C4.8704 2.33337 2.33334 4.87043 2.33334 8.00004C2.33334 11.1297 4.8704 13.6667 8.00001 13.6667ZM8.00001 14.6667C11.6819 14.6667 14.6667 11.6819 14.6667 8.00004C14.6667 4.31814 11.6819 1.33337 8.00001 1.33337C4.31811 1.33337 1.33334 4.31814 1.33334 8.00004C1.33334 11.6819 4.31811 14.6667 8.00001 14.6667Z" fill="#4360DF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.65987 5.08224C7.70527 4.80985 7.52126 4.55224 7.24887 4.50684C6.97649 4.46144 6.71887 4.64545 6.67348 4.91784L6.51175 5.88817C6.48497 6.0489 6.3459 6.16671 6.18296 6.16671H5.33334C5.0572 6.16671 4.83334 6.39056 4.83334 6.66671C4.83334 6.94285 5.0572 7.16671 5.33334 7.16671H5.90518C6.11116 7.16671 6.26784 7.35166 6.23398 7.55484L6.06731 8.55484C6.04052 8.71557 5.90146 8.83337 5.73851 8.83337H5.00001C4.72387 8.83337 4.50001 9.05723 4.50001 9.33337C4.50001 9.60952 4.72387 9.83337 5.00001 9.83337H5.46073C5.66671 9.83337 5.82339 10.0183 5.78953 10.2215L5.67348 10.9178C5.62808 11.1902 5.81209 11.4478 6.08447 11.4932C6.35686 11.5386 6.61447 11.3546 6.65987 11.0822L6.82159 10.1119C6.84838 9.95118 6.98744 9.83337 7.15039 9.83337H8.1274C8.33338 9.83337 8.49006 10.0183 8.4562 10.2215L8.34014 10.9178C8.29475 11.1902 8.47876 11.4478 8.75114 11.4932C9.02353 11.5386 9.28114 11.3546 9.32654 11.0822L9.48826 10.1119C9.51505 9.95118 9.65411 9.83337 9.81706 9.83337H10.6667C10.9428 9.83337 11.1667 9.60952 11.1667 9.33337C11.1667 9.05723 10.9428 8.83337 10.6667 8.83337H10.0948C9.88885 8.83337 9.73217 8.64842 9.76604 8.44524L9.9327 7.44524C9.95949 7.28451 10.0986 7.16671 10.2615 7.16671H11C11.2762 7.16671 11.5 6.94285 11.5 6.66671C11.5 6.39056 11.2762 6.16671 11 6.16671H10.5393C10.3333 6.16671 10.1766 5.98175 10.2105 5.77857L10.3265 5.08224C10.3719 4.80985 10.1879 4.55224 9.91554 4.50684C9.64316 4.46144 9.38554 4.64545 9.34014 4.91784L9.17842 5.88817C9.15163 6.0489 9.01257 6.16671 8.84962 6.16671H7.87261C7.66663 6.16671 7.50995 5.98175 7.54381 5.77857L7.65987 5.08224ZM8.40518 8.83337C8.56812 8.83337 8.70719 8.71557 8.73398 8.55484L8.90064 7.55484C8.93451 7.35166 8.77782 7.16671 8.57185 7.16671H7.59483C7.43189 7.16671 7.29282 7.28451 7.26604 7.44524L7.09937 8.44524C7.06551 8.64842 7.22219 8.83337 7.42817 8.83337H8.40518Z" fill="#4360DF"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.58709 8.08709C5.9532 7.72097 6.5468 7.72097 6.91291 8.08709L9.64645 10.8206C9.84171 11.0159 10.1583 11.0159 10.3536 10.8206L13.0871 8.08709C13.4532 7.72097 14.0468 7.72097 14.4129 8.08709C14.779 8.4532 14.779 9.0468 14.4129 9.41291L10.6629 13.1629C10.2968 13.529 9.7032 13.529 9.33709 13.1629L5.58709 9.41291C5.22097 9.0468 5.22097 8.4532 5.58709 8.08709Z" fill="black" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 546 B

5
ui/app/img/more.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="4.375" cy="10" r="1.875" fill="black" fill-opacity="0.7"/>
<circle cx="15.625" cy="10" r="1.875" fill="black" fill-opacity="0.7"/>
<circle cx="10" cy="10" r="1.875" fill="black" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 314 B

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.46209 6.21209C7.09597 6.5782 7.09597 7.1718 7.46209 7.53791L10.1072 10.1831C10.3513 10.4271 10.3513 10.8229 10.1072 11.0669L7.46209 13.7121C7.09597 14.0782 7.09597 14.6718 7.46209 15.0379C7.8282 15.404 8.4218 15.404 8.78791 15.0379L12.5379 11.2879C12.904 10.9218 12.904 10.3282 12.5379 9.96209L8.78791 6.21209C8.4218 5.84597 7.8282 5.84597 7.46209 6.21209Z" fill="black" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 547 B

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 71f66f68064e9897cd17b6bcecba426a91405034 Subproject commit 92032c7158b80908508cd67c08ac6f6c75eee35b