657 lines
28 KiB
QML
657 lines
28 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
|
|
import StatusQ 0.1
|
|
import StatusQ.Core 0.1
|
|
import StatusQ.Components 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
import StatusQ.Core.Utils 0.1
|
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
import AppLayouts.Communities.controls 1.0
|
|
import AppLayouts.Communities.views 1.0
|
|
import AppLayouts.Communities.helpers 1.0
|
|
|
|
import utils 1.0
|
|
|
|
Rectangle {
|
|
id: root
|
|
|
|
required property int /*PermissionTypes.Type*/ eligibleToJoinAs
|
|
|
|
property bool isEditMode
|
|
property bool isDirty
|
|
|
|
property string communityName
|
|
property string communityIcon
|
|
|
|
property var permissionsModel
|
|
property var assetsModel
|
|
property var collectiblesModel
|
|
|
|
property bool requirementsCheckPending
|
|
property bool checkingPermissionToJoinInProgress
|
|
property bool joinPermissionsCheckCompletedWithoutErrors
|
|
|
|
readonly property bool lostPermissionToJoin: d.lostPermissionToJoin
|
|
readonly property bool lostChannelPermissions: d.lostChannelPermissions
|
|
|
|
implicitHeight: permissionsScrollView.contentHeight - permissionsScrollView.anchors.topMargin
|
|
color: Theme.palette.baseColor4
|
|
|
|
readonly property bool hasAnyVisiblePermission: root.permissionsModel && root.permissionsModel.count &&
|
|
(d.tokenMasterPermissionsModel.count > 0 || d.adminPermissionsModel.count > 0 ||
|
|
d.joinPermissionsModel.count > 0 || d.channelsPermissionsModel.count > 0)
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
// UI
|
|
readonly property int absLeftMargin: 12
|
|
readonly property color tableBorderColor: Theme.palette.directColor7
|
|
|
|
// internal logic
|
|
readonly property bool lostPermissionToJoin: root.isEditMode && joinPermissionsModel.count && !joinPermissionPanel.tokenCriteriaMet
|
|
|
|
readonly property var uniquePermissionChannels:
|
|
d.channelsPermissionsModel.count ?
|
|
PermissionsHelpers.getUniquePermissionChannels(root.permissionsModel, [PermissionTypes.Type.Read, PermissionTypes.Type.ViewAndPost])
|
|
: []
|
|
|
|
property var initialChannelPermissions
|
|
|
|
function getChannelPermissions() {
|
|
var result = {}
|
|
for (let i = 0; i < channelPermissionsPanel.count; i++) {
|
|
const channel = channelPermissionsPanel.itemAt(i)
|
|
const ckey = channel.channelKey
|
|
result[ckey] = [channel.readPermissionMet, channel.viewAndPostPermissionMet]
|
|
}
|
|
return result
|
|
}
|
|
readonly property bool lostChannelPermissions: root.isEditMode && d.uniquePermissionChannels.length > 0 && channelPermissionsPanel.anyPermissionLost
|
|
|
|
// models
|
|
readonly property var tokenMasterPermissionsModel: SortFilterProxyModel {
|
|
id: tokenMasterPermissionsModel
|
|
sourceModel: root.permissionsModel
|
|
filters: [
|
|
ValueFilter {
|
|
roleName: "permissionType"
|
|
value: Constants.permissionType.becomeTokenMaster
|
|
},
|
|
ValueFilter {
|
|
roleName: "tokenCriteriaMet"
|
|
value: true
|
|
}
|
|
]
|
|
}
|
|
readonly property var adminPermissionsModel: SortFilterProxyModel {
|
|
id: adminPermissionsModel
|
|
sourceModel: root.permissionsModel
|
|
filters: [
|
|
ValueFilter {
|
|
roleName: "permissionType"
|
|
value: Constants.permissionType.admin
|
|
},
|
|
AnyOf {
|
|
ValueFilter {
|
|
roleName: "isPrivate"
|
|
value: false
|
|
}
|
|
AllOf {
|
|
ValueFilter {
|
|
roleName: "tokenCriteriaMet"
|
|
value: true
|
|
}
|
|
ValueFilter {
|
|
roleName: "isPrivate"
|
|
value: true
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
readonly property var joinPermissionsModel: SortFilterProxyModel {
|
|
id: joinPermissionsModel
|
|
sourceModel: root.permissionsModel
|
|
filters: [
|
|
ValueFilter {
|
|
roleName: "permissionType"
|
|
value: Constants.permissionType.member
|
|
},
|
|
AnyOf {
|
|
ValueFilter {
|
|
roleName: "isPrivate"
|
|
value: false
|
|
}
|
|
AllOf {
|
|
ValueFilter {
|
|
roleName: "tokenCriteriaMet"
|
|
value: true
|
|
}
|
|
ValueFilter {
|
|
roleName: "isPrivate"
|
|
value: true
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
// used to check if there are any visible channel permissions
|
|
readonly property var channelsPermissionsModel: SortFilterProxyModel {
|
|
id: channelsPermissionsModel
|
|
sourceModel: root.permissionsModel
|
|
filters: [
|
|
AnyOf {
|
|
ValueFilter {
|
|
roleName: "permissionType"
|
|
value: Constants.permissionType.read
|
|
}
|
|
ValueFilter {
|
|
roleName: "permissionType"
|
|
value: Constants.permissionType.viewAndPost
|
|
}
|
|
},
|
|
AnyOf {
|
|
ValueFilter {
|
|
roleName: "isPrivate"
|
|
value: false
|
|
}
|
|
AllOf {
|
|
ValueFilter {
|
|
roleName: "tokenCriteriaMet"
|
|
value: true
|
|
}
|
|
ValueFilter {
|
|
roleName: "isPrivate"
|
|
value: true
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
d.initialChannelPermissions = d.getChannelPermissions()
|
|
}
|
|
|
|
StatusScrollView {
|
|
id: permissionsScrollView
|
|
anchors.fill: parent
|
|
anchors.topMargin: -Style.current.padding
|
|
bottomPadding: eligibilityHintBubble.visible ? eligibilityHintBubble.height + eligibilityHintBubble.anchors.bottomMargin*2
|
|
: 16
|
|
contentWidth: availableWidth
|
|
|
|
ColumnLayout {
|
|
width: parent.width
|
|
spacing: Style.current.halfPadding
|
|
|
|
// header
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.bottomMargin: 4
|
|
spacing: Style.current.padding
|
|
StatusRoundedImage {
|
|
Layout.preferredWidth: 40
|
|
Layout.preferredHeight: 40
|
|
Layout.leftMargin: d.absLeftMargin
|
|
image.source: root.communityIcon
|
|
}
|
|
StatusBaseText {
|
|
font.weight: Font.Medium
|
|
text: qsTr("Permissions")
|
|
}
|
|
Item { Layout.fillWidth: true }
|
|
RowLayout {
|
|
Layout.rightMargin: Style.current.halfPadding
|
|
spacing: 4
|
|
visible: root.requirementsCheckPending
|
|
StatusBaseText {
|
|
text: qsTr("Updating eligibility")
|
|
font.pixelSize: Style.current.tertiaryTextFontSize
|
|
color: Theme.palette.baseColor1
|
|
}
|
|
StatusLoadingIndicator {
|
|
Layout.preferredWidth: 12
|
|
Layout.preferredHeight: 12
|
|
}
|
|
}
|
|
}
|
|
|
|
// permission types
|
|
PermissionPanel {
|
|
id: joinPermissionPanel
|
|
permissionType: PermissionTypes.Type.Member
|
|
permissionsModel: d.joinPermissionsModel
|
|
}
|
|
PermissionPanel {
|
|
permissionType: PermissionTypes.Type.Admin
|
|
permissionsModel: d.adminPermissionsModel
|
|
}
|
|
PermissionPanel {
|
|
id: tokenMasterPermissionPanel
|
|
permissionType: PermissionTypes.Type.TokenMaster
|
|
permissionsModel: d.tokenMasterPermissionsModel
|
|
}
|
|
|
|
Repeater { // channel repeater
|
|
id: channelPermissionsPanel
|
|
model: d.uniquePermissionChannels
|
|
delegate: ChannelPermissionPanel {}
|
|
readonly property bool anyPermissionLost: {
|
|
for (let i = 0; i < channelPermissionsPanel.count; i++) {
|
|
const channel = channelPermissionsPanel.itemAt(i)
|
|
if (channel && channel.anyPermissionLost)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CommunityEligibilityTag {
|
|
id: eligibilityHintBubble
|
|
visible: !root.checkingPermissionToJoinInProgress && root.joinPermissionsCheckCompletedWithoutErrors
|
|
eligibleToJoinAs: root.eligibleToJoinAs
|
|
isEditMode: root.isEditMode
|
|
isDirty: root.isDirty
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 24
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
component PanelBg: Rectangle {
|
|
color: Theme.palette.statusListItem.backgroundColor
|
|
border.width: 1
|
|
border.color: Theme.palette.baseColor2
|
|
radius: Style.current.radius
|
|
}
|
|
|
|
component PanelIcon: StatusRoundIcon {
|
|
Layout.preferredWidth: 40
|
|
Layout.preferredHeight: 40
|
|
Layout.alignment: Qt.AlignTop
|
|
asset.name: {
|
|
switch (permissionType) {
|
|
case PermissionTypes.Type.TokenMaster:
|
|
return "arbitrator"
|
|
case PermissionTypes.Type.Admin:
|
|
return "admin"
|
|
case PermissionTypes.Type.Member:
|
|
return "communities"
|
|
default:
|
|
return "channel"
|
|
}
|
|
}
|
|
radius: height/2
|
|
}
|
|
|
|
component PanelHeading: StatusBaseText {
|
|
Layout.fillWidth: true
|
|
elide: Text.ElideRight
|
|
font.weight: Font.Medium
|
|
text: {
|
|
switch (permissionType) {
|
|
case PermissionTypes.Type.Admin:
|
|
return qsTr("Become an admin")
|
|
case PermissionTypes.Type.Member:
|
|
return qsTr("Join %1").arg(root.communityName)
|
|
case PermissionTypes.Type.TokenMaster:
|
|
return qsTr("Become a TokenMaster")
|
|
default:
|
|
return d.uniquePermissionChannels[index][1]
|
|
}
|
|
}
|
|
}
|
|
|
|
component SinglePermissionFlow: Flow {
|
|
Layout.fillWidth: true
|
|
spacing: Style.current.halfPadding
|
|
Repeater {
|
|
model: HoldingsSelectionModel {
|
|
sourceModel: model.holdingsListModel
|
|
assetsModel: root.assetsModel
|
|
collectiblesModel: root.collectiblesModel
|
|
}
|
|
delegate: Row {
|
|
spacing: 4
|
|
StatusRoundedImage {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 16
|
|
height: 16
|
|
image.source: model.imageSource
|
|
visible: !isError
|
|
}
|
|
StatusBaseText {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
font.pixelSize: Theme.tertiaryTextFontSize
|
|
text: !!model.text ? model.text : ""
|
|
color: model.available ? Theme.palette.successColor1 : Theme.palette.baseColor1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component PermissionPanel: Control {
|
|
id: permissionPanel
|
|
property int permissionType: PermissionTypes.Type.None
|
|
property var permissionsModel
|
|
|
|
readonly property bool tokenCriteriaMet: overallPermissionRow.tokenCriteriaMet
|
|
|
|
visible: permissionsModel.count
|
|
Layout.fillWidth: true
|
|
padding: d.absLeftMargin
|
|
background: PanelBg {}
|
|
contentItem: RowLayout {
|
|
spacing: Style.current.padding
|
|
PanelIcon {}
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
PanelHeading {}
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: grid.implicitHeight + grid.anchors.margins*2
|
|
border.width: 1
|
|
border.color: d.tableBorderColor
|
|
radius: Style.current.radius
|
|
color: "transparent"
|
|
|
|
GridLayout {
|
|
id: grid
|
|
anchors.fill: parent
|
|
anchors.margins: Style.current.halfPadding
|
|
rowSpacing: Style.current.halfPadding
|
|
columnSpacing: Style.current.halfPadding
|
|
columns: 2
|
|
|
|
Repeater {
|
|
id: permissionsRepeater
|
|
|
|
property int revision
|
|
onItemAdded: revision++
|
|
onItemRemoved: revision++
|
|
|
|
model: permissionPanel.permissionsModel
|
|
delegate: ColumnLayout {
|
|
Layout.column: 0
|
|
Layout.row: index
|
|
Layout.fillWidth: true
|
|
spacing: Style.current.halfPadding
|
|
|
|
readonly property bool tokenCriteriaMet: model.tokenCriteriaMet ?? false
|
|
onTokenCriteriaMetChanged: permissionsRepeater.revision++
|
|
|
|
SinglePermissionFlow {}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: -grid.anchors.leftMargin
|
|
Layout.rightMargin: -grid.anchors.rightMargin
|
|
Layout.preferredHeight: 1
|
|
color: d.tableBorderColor
|
|
visible: index < permissionsRepeater.count - 1
|
|
}
|
|
}
|
|
}
|
|
RowLayout {
|
|
id: overallPermissionRow
|
|
Layout.column: 1
|
|
Layout.rowSpan: permissionsRepeater.count || 1
|
|
Layout.preferredWidth: 110
|
|
Layout.fillHeight: true
|
|
|
|
readonly property bool tokenCriteriaMet: {
|
|
permissionsRepeater.revision
|
|
for (var i = 0; i < permissionsRepeater.count; i++) { // NB no let/const here b/c of https://bugreports.qt.io/browse/QTBUG-91917
|
|
var permissionItem = permissionsRepeater.itemAt(i);
|
|
if (permissionItem && permissionItem.tokenCriteriaMet)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: 1
|
|
Layout.fillHeight: true
|
|
Layout.topMargin: -Style.current.halfPadding
|
|
Layout.bottomMargin: -Style.current.halfPadding
|
|
color: d.tableBorderColor
|
|
}
|
|
Row {
|
|
Layout.alignment: Qt.AlignCenter
|
|
spacing: 4
|
|
StatusIcon {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 16
|
|
height: 16
|
|
icon: overallPermissionRow.tokenCriteriaMet ? "tiny/checkmark" : "tiny/secure"
|
|
color: {
|
|
if (d.lostPermissionToJoin)
|
|
return Theme.palette.dangerColor1
|
|
if (overallPermissionRow.tokenCriteriaMet)
|
|
return Theme.palette.successColor1
|
|
return Theme.palette.baseColor1
|
|
}
|
|
}
|
|
StatusBaseText {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
font.pixelSize: Theme.tertiaryTextFontSize
|
|
text: {
|
|
switch (permissionPanel.permissionType) {
|
|
case PermissionTypes.Type.Admin:
|
|
return qsTr("Admin")
|
|
case PermissionTypes.Type.Member:
|
|
return qsTr("Join")
|
|
case PermissionTypes.Type.Read:
|
|
return qsTr("View only")
|
|
case PermissionTypes.Type.ViewAndPost:
|
|
return qsTr("View & post")
|
|
case PermissionTypes.Type.TokenMaster:
|
|
return qsTr("TokenMaster")
|
|
default:
|
|
return "???"
|
|
}
|
|
}
|
|
|
|
color: {
|
|
if (d.lostPermissionToJoin)
|
|
return Theme.palette.dangerColor1
|
|
if (overallPermissionRow.tokenCriteriaMet)
|
|
return Theme.palette.directColor1
|
|
return Theme.palette.baseColor1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
component ChannelPermissionPanel: Control {
|
|
id: channelPermsPanel
|
|
Layout.fillWidth: true
|
|
spacing: 10
|
|
padding: d.absLeftMargin
|
|
background: PanelBg {}
|
|
|
|
visible: {
|
|
for (var i = 0; i < channelPermsRepeater.count; i++) {
|
|
var chanPermissionItem = channelPermsRepeater.itemAt(i);
|
|
if (chanPermissionItem.channelPermissionsModel.count > 0)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
readonly property string channelKey: modelData[0]
|
|
readonly property bool readPermissionMet: channelPermsRepeater.count > 0 ? channelPermsRepeater.itemAt(0).tokenCriteriaMet : false
|
|
readonly property bool viewAndPostPermissionMet: channelPermsRepeater.count > 1 ? channelPermsRepeater.itemAt(1).tokenCriteriaMet : false
|
|
readonly property bool anyPermissionLost: channelPermsRepeater.count > 0 ? channelPermsRepeater.itemAt(0).permissionLost || channelPermsRepeater.itemAt(1).permissionLost : false
|
|
|
|
contentItem: RowLayout {
|
|
spacing: Style.current.padding
|
|
PanelIcon {}
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
spacing: Style.current.smallPadding
|
|
PanelHeading {}
|
|
Repeater { // permissions repeater
|
|
id: channelPermsRepeater
|
|
model: [PermissionTypes.Type.Read, PermissionTypes.Type.ViewAndPost]
|
|
|
|
delegate: Rectangle {
|
|
id: channelPermsSubPanel
|
|
|
|
readonly property int permissionType: modelData
|
|
readonly property alias tokenCriteriaMet: overallPermissionRow2.tokenCriteriaMet
|
|
readonly property bool permissionLost: d.initialChannelPermissions[channelPermsPanel.channelKey][index] && !tokenCriteriaMet
|
|
readonly property alias channelPermissionsModel: permissionsRepeater2.model
|
|
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: grid2.implicitHeight + grid2.anchors.margins*2
|
|
border.width: 1
|
|
border.color: d.tableBorderColor
|
|
radius: Style.current.radius
|
|
color: "transparent"
|
|
visible: permissionsRepeater2.model.count > 0
|
|
|
|
GridLayout {
|
|
id: grid2
|
|
anchors.fill: parent
|
|
anchors.margins: Style.current.halfPadding
|
|
rowSpacing: Style.current.halfPadding
|
|
columnSpacing: Style.current.halfPadding
|
|
columns: 2
|
|
|
|
Repeater {
|
|
id: permissionsRepeater2
|
|
|
|
property int revision
|
|
onItemAdded: revision++
|
|
onItemRemoved: revision++
|
|
|
|
model: SortFilterProxyModel {
|
|
id: channelPermissionsModel
|
|
sourceModel: d.channelsPermissionsModel
|
|
|
|
function filterPredicate(channelsListModel) {
|
|
return ModelUtils.contains(channelsListModel, "key", channelPermsPanel.channelKey)
|
|
}
|
|
|
|
filters: [
|
|
ValueFilter {
|
|
roleName: "permissionType"
|
|
value: channelPermsSubPanel.permissionType
|
|
},
|
|
FastExpressionFilter {
|
|
expression: channelPermissionsModel.filterPredicate(model.channelsListModel) // filter and group by channel "key"
|
|
expectedRoles: ["channelsListModel"]
|
|
}
|
|
]
|
|
}
|
|
delegate: ColumnLayout {
|
|
Layout.column: 0
|
|
Layout.row: index
|
|
Layout.fillWidth: true
|
|
spacing: Style.current.halfPadding
|
|
|
|
readonly property bool tokenCriteriaMet: model.tokenCriteriaMet ?? false
|
|
onTokenCriteriaMetChanged: permissionsRepeater2.revision++
|
|
|
|
SinglePermissionFlow {}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: -grid2.anchors.leftMargin
|
|
Layout.rightMargin: -grid2.anchors.rightMargin
|
|
Layout.preferredHeight: 1
|
|
color: d.tableBorderColor
|
|
visible: index < permissionsRepeater2.count - 1
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
id: overallPermissionRow2
|
|
Layout.column: 1
|
|
Layout.rowSpan: channelPermissionsModel.count || 1
|
|
Layout.preferredWidth: 110
|
|
Layout.fillHeight: true
|
|
|
|
readonly property bool tokenCriteriaMet: {
|
|
permissionsRepeater2.revision
|
|
for (var i = 0; i < permissionsRepeater2.count; i++) { // NB no let/const here b/c of https://bugreports.qt.io/browse/QTBUG-91917
|
|
const permissionItem = permissionsRepeater2.itemAt(i);
|
|
if (permissionItem && permissionItem.tokenCriteriaMet)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: 1
|
|
Layout.fillHeight: true
|
|
Layout.topMargin: -Style.current.halfPadding
|
|
Layout.bottomMargin: -Style.current.halfPadding
|
|
color: d.tableBorderColor
|
|
}
|
|
Row {
|
|
Layout.alignment: Qt.AlignCenter
|
|
spacing: 4
|
|
StatusIcon {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 16
|
|
height: 16
|
|
icon: overallPermissionRow2.tokenCriteriaMet ? "tiny/checkmark" : "tiny/secure"
|
|
color: {
|
|
if (channelPermsSubPanel.permissionLost)
|
|
return Theme.palette.dangerColor1
|
|
if (overallPermissionRow2.tokenCriteriaMet)
|
|
return Theme.palette.successColor1
|
|
return Theme.palette.baseColor1
|
|
}
|
|
}
|
|
StatusBaseText {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
font.pixelSize: Theme.tertiaryTextFontSize
|
|
text: {
|
|
switch (channelPermsSubPanel.permissionType) {
|
|
case PermissionTypes.Type.Read:
|
|
return qsTr("View only")
|
|
case PermissionTypes.Type.ViewAndPost:
|
|
return qsTr("View & post")
|
|
default:
|
|
return "???"
|
|
}
|
|
}
|
|
|
|
color: {
|
|
if (channelPermsSubPanel.permissionLost)
|
|
return Theme.palette.dangerColor1
|
|
if (overallPermissionRow2.tokenCriteriaMet)
|
|
return Theme.palette.directColor1
|
|
return Theme.palette.baseColor1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|