feat(CommunityPermissions): Implement duplication checking

Moreover:
- adapt flow to the design
- introduce ModelChangeTracker utility component

Closes: #9048
This commit is contained in:
Michał Cieślak 2023-02-14 21:39:18 +01:00 committed by Michał
parent c78eaef2b6
commit a97c8a720e
12 changed files with 236 additions and 112 deletions

View File

@ -27,6 +27,7 @@ SplitView {
isEditState: isEditStateCheckBox.checked
isPrivate: isPrivateCheckBox.checked
duplicationWarningVisible: isDuplicationWarningVisibleCheckBox.checked
store: CommunitiesStore {
readonly property var assetsModel: AssetsModel {}
@ -50,10 +51,6 @@ SplitView {
logs.logEvent("CommunitiesStore::editPermission - index: " + index)
}
function duplicatePermission(index) {
logs.logEvent("CommunitiesStore::duplicatePermission - index: " + index)
}
function removePermission(index) {
logs.logEvent("CommunitiesStore::removePermission - index: " + index)
}
@ -111,6 +108,12 @@ SplitView {
text: "Is private"
}
CheckBox {
id: isDuplicationWarningVisibleCheckBox
text: "Is duplication warning visible"
}
}
Button {

View File

@ -39,10 +39,6 @@ SplitView {
readonly property var collectiblesModel: CollectiblesModel {
id: collectiblesModel
}
function duplicatePermission(index) {
logs.logEvent("CommunitiesStore::duplicatePermission - index: " + index)
}
}
rootStore: QtObject {

View File

@ -0,0 +1,41 @@
import QtQml 2.14
QtObject {
property alias model: d.target
readonly property alias revision: d.revision
function reset() {
d.revision = 0
}
readonly property Connections _d: Connections {
id: d
property int revision: 0
function onRowsInserted() {
revision++
}
function onRowsMoved() {
revision++
}
function onRowsRemoved() {
revision++
}
function onLayoutChanged() {
revision++
}
function onModelReset() {
revision++
}
function onDataChanged() {
revision++
}
}
}

View File

@ -36,4 +36,63 @@ QtObject {
return -1
}
function checkItemsEquality(itemA, itemB, roles) {
return roles.every((role) => itemA[role] === itemB[role])
}
function checkEqualityStrict(modelA, modelB, roles) {
if (modelA === modelB)
return true
const countA = modelA === null ? 0 : modelA.rowCount()
const countB = modelB === null ? 0 : modelB.rowCount()
if (countA !== countB)
return false
if (countA === 0)
return true
for (let i = 0; i < countA; i++) {
const itemA = modelA.get(i)
const itemB = modelB.get(i)
if (!checkItemsEquality(itemA, itemB, roles))
return false
}
return true
}
function checkEqualitySet(modelA, modelB, roles) {
if (modelA === modelB)
return true
const countA = modelA === null ? 0 : modelA.rowCount()
const countB = modelB === null ? 0 : modelB.rowCount()
if (countA !== countB)
return false
if (countA === 0)
return true
for (let i = 0; i < countA; i++) {
const itemA = modelA.get(i)
let found = false
for (let j = 0; j < countB; j++) {
const itemB = modelB.get(j)
if (checkItemsEquality(itemA, itemB, roles))
found = true
}
if (!found)
return false
}
return true
}
}

View File

@ -16,101 +16,24 @@ QtObject {
readonly property QtObject _d: QtObject {
id: d
component ModelObserver: Connections {
function onRowsInserted() {
d.changeCounter++
}
function onRowsMoved() {
d.changeCounter++
}
function onRowsRemoved() {
d.changeCounter++
}
function onLayoutChanged() {
d.changeCounter++
}
function onModelReset() {
d.changeCounter++
}
function onDataChanged() {
d.changeCounter++
}
readonly property ModelChangeTracker trackerA: ModelChangeTracker {
model: modelA
}
property int changeCounter: 0
readonly property ModelChangeTracker trackerB: ModelChangeTracker {
model: modelB
}
readonly property int revision: trackerA.revision + trackerB.revision
readonly property bool equal: checkEquality(modelA, modelB, roles, mode,
changeCounter)
readonly property Connections observerA: ModelObserver {
target: modelA
}
readonly property Connections observerB: ModelObserver {
target: modelB
}
revision)
function checkEquality(modelA, modelB, roles, mode, dummy) {
if (modelA === modelB)
return true
const countA = modelA === null ? 0 : modelA.rowCount()
const countB = modelB === null ? 0 : modelB.rowCount()
if (countA !== countB)
return false
if (countA === 0)
return true
if (mode === ModelsComparator.CompareMode.Strict)
return checkEqualityStrict(modelA, modelB, roles)
return ModelUtils.checkEqualityStrict(modelA, modelB, roles)
return checkEqualitySet(modelA, modelB, roles)
}
function checkEqualityStrict(modelA, modelB, roles) {
const count = modelA.rowCount()
for (let i = 0; i < count; i++) {
const itemA = modelA.get(i)
const itemB = modelB.get(i)
if (!checkItemsEquality(itemA, itemB, roles))
return false
}
return true
}
function checkEqualitySet(modelA, modelB, roles) {
const count = modelA.rowCount()
for (let i = 0; i < count; i++) {
const itemA = modelA.get(i)
let found = false
for (let j = 0; j < count; j++) {
const itemB = modelB.get(j)
if (checkItemsEquality(itemA, itemB, roles))
found = true
}
if (!found)
return false
}
return true
}
function checkItemsEquality(itemA, itemB, roles) {
return roles.every((role) => itemA[role] === itemB[role])
return ModelUtils.checkEqualitySet(modelA, modelB, roles)
}
}
}

View File

@ -2,6 +2,7 @@ module StatusQ.Core.Utils
EmojiJSON 1.0 emojiList.js
JSONListModel 0.1 JSONListModel.qml
ModelChangeTracker 0.1 ModelChangeTracker.qml
ModelsComparator 0.1 ModelsComparator.qml
XSS 1.0 xss.js
singleton Emoji 0.1 Emoji.qml

View File

@ -195,5 +195,6 @@
<file>StatusQ/Components/LoadingComponent.qml</file>
<file>StatusQ/Core/Utils/ModelUtils.qml</file>
<file>StatusQ/Core/Utils/ModelsComparator.qml</file>
<file>StatusQ/Core/Utils/ModelChangeTracker.qml</file>
</qresource>
</RCC>

View File

@ -5,6 +5,7 @@ import AppLayouts.Chat.layouts 1.0
import AppLayouts.Chat.stores 1.0
import AppLayouts.Chat.views.communities 1.0
import StatusQ.Core.Utils 0.1
import utils 1.0
SettingsPageLayout {
@ -57,8 +58,8 @@ SettingsPageLayout {
? d.permissionsViewState : d.welcomeViewState
function initializeData() {
holdingsToEditModel = defaultListObject.createObject(d)
channelsToEditModel = defaultListObject.createObject(d)
holdingsToEditModel = emptyModel
channelsToEditModel = emptyModel
permissionTypeToEdit = PermissionTypes.Type.None
isPrivateToEditValue = false
}
@ -146,6 +147,8 @@ SettingsPageLayout {
id: newPermissionView
CommunityNewPermissionView {
id: communityNewPermissionView
viewWidth: root.viewWidth
rootStore: root.rootStore
@ -190,6 +193,55 @@ SettingsPageLayout {
property: "dirty"
value: isEditState && dirty
}
ModelChangeTracker {
id: holdingsTracker
model: communityNewPermissionView.dirtyValues.holdingsModel
}
ModelChangeTracker {
id: channelsTracker
model: communityNewPermissionView.dirtyValues.channelsModel
}
Binding {
target: root
property: "saveChangesButtonEnabled"
value: !communityNewPermissionView.duplicationWarningVisible
}
duplicationWarningVisible: {
// dependencies
holdingsTracker.revision
channelsTracker.revision
communityNewPermissionView.dirtyValues.permissionType
communityNewPermissionView.dirtyValues.isPrivate
const model = root.store.permissionsModel
for (let i = 0; i < model.count; i++) {
if (root.state === d.editPermissionViewState
&& d.permissionIndexToEdit === i)
continue
const item = model.get(i)
const holdings = item.holdingsListModel
const channels = item.channelsListModel
const permissionType = item.permissionType
const same = (a, b) => ModelUtils.checkEqualitySet(a, b, ["key"])
if (same(dirtyValues.holdingsModel, holdings)
&& same(dirtyValues.channelsModel, channels)
&& dirtyValues.permissionType === permissionType)
return true
}
return false
}
}
}
@ -201,19 +253,24 @@ SettingsPageLayout {
rootStore: root.rootStore
store: root.store
onEditPermissionRequested: {
function setInitialValuesFromIndex(index) {
const item = root.store.permissionsModel.get(index)
d.permissionIndexToEdit = index
d.holdingsToEditModel = item.holdingsListModel
d.channelsToEditModel = item.channelsListModel
d.permissionTypeToEdit = item.permissionType
d.isPrivateToEditValue = item.isPrivate
}
onEditPermissionRequested: {
setInitialValuesFromIndex(index)
d.permissionIndexToEdit = index
root.state = d.editPermissionViewState
}
onDuplicatePermissionRequested: {
root.store.duplicatePermission(index)
setInitialValuesFromIndex(index)
root.state = d.newPermissionViewState
}
onRemovePermissionRequested: {
@ -222,8 +279,7 @@ SettingsPageLayout {
}
}
Component {
id: defaultListObject
ListModel {}
ListModel {
id: emptyModel
}
}

View File

@ -0,0 +1,40 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
Control {
id: root
spacing: Style.current.halfPadding
QtObject {
id: d
property int iconSize: 20
}
contentItem: RowLayout {
spacing: root.spacing
StatusIcon {
Layout.preferredWidth: d.iconSize
Layout.preferredHeight: d.iconSize
Layout.alignment: Qt.AlignTop
color: Theme.palette.dangerColor1
icon: "warning"
}
StatusBaseText {
Layout.fillWidth: true
wrapMode: Text.Wrap
font.pixelSize: Style.current.primaryTextFontSize
color: Theme.palette.dangerColor1
text: qsTr("Permission with same properties is already active, edit properties to create a new permission.")
}
}
}

View File

@ -6,4 +6,5 @@ CommunityProfilePopupInviteMessagePanel 1.0 CommunityProfilePopupInviteMessagePa
HidePermissionPanel 1.0 HidePermissionPanel.qml
JoinPermissionsOverlayPanel 1.0 JoinPermissionsOverlayPanel.qml
PermissionConflictWarningPanel 1.0 PermissionConflictWarningPanel.qml
PermissionDuplicationWarningPanel 1.0 PermissionDuplicationWarningPanel.qml
PermissionQualificationPanel 1.0 PermissionQualificationPanel.qml

View File

@ -179,14 +179,6 @@ QtObject {
createPermission(holdings, permissionType, isPrivate, channels, index)
}
function duplicatePermission(index) {
// TO BE REPLACED: Call to backend
console.log("TODO: Duplicate permissions - backend call")
const permission = root.permissionsModel.get(index)
createPermission(permission.holdingsListModel, permission.permissionType,
permission.isPrivate, permission.channelsListModel)
}
function removePermission(index) {
console.log("TODO: Remove permissions - backend call")
root.permissionsModel.remove(index)

View File

@ -44,6 +44,8 @@ StatusScrollView {
readonly property alias dirtyValues: d.dirtyValues
property alias duplicationWarningVisible: duplicationPanel.visible
signal createPermissionClicked
function resetChanges() {
@ -156,7 +158,7 @@ StatusScrollView {
contentWidth: mainLayout.width
contentHeight: mainLayout.height
onPermissionTypeChanged: d.loadInitValues()
onPermissionTypeChanged: Qt.callLater(() => d.loadInitValues())
ColumnLayout {
id: mainLayout
@ -517,6 +519,14 @@ StatusScrollView {
channels: store.permissionConflict.channels
}
PermissionDuplicationWarningPanel {
id: duplicationPanel
visible: false
Layout.fillWidth: true
Layout.topMargin: 50 // by desing
}
StatusButton {
visible: !root.isEditState
Layout.topMargin: conflictPanel.visible ? conflictPanel.Layout.topMargin : 24 // by design
@ -524,6 +534,7 @@ StatusScrollView {
enabled: d.dirtyValues.holdingsModel.count > 0
&& d.dirtyValues.permissionType !== PermissionTypes.Type.None
&& (d.dirtyValues.channelsModel.count > 0 || d.isCommunityPermission)
&& !root.duplicationWarningVisible
Layout.preferredHeight: 44
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true