diff --git a/storybook/pages/CommunityNewPermissionViewPage.qml b/storybook/pages/CommunityNewPermissionViewPage.qml index 1357cf75ed..ee8e3a8cb4 100644 --- a/storybook/pages/CommunityNewPermissionViewPage.qml +++ b/storybook/pages/CommunityNewPermissionViewPage.qml @@ -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 { diff --git a/storybook/pages/CommunityPermissionsViewPage.qml b/storybook/pages/CommunityPermissionsViewPage.qml index fbcd16b5a2..413fea50eb 100644 --- a/storybook/pages/CommunityPermissionsViewPage.qml +++ b/storybook/pages/CommunityPermissionsViewPage.qml @@ -39,10 +39,6 @@ SplitView { readonly property var collectiblesModel: CollectiblesModel { id: collectiblesModel } - - function duplicatePermission(index) { - logs.logEvent("CommunitiesStore::duplicatePermission - index: " + index) - } } rootStore: QtObject { diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/ModelChangeTracker.qml b/ui/StatusQ/src/StatusQ/Core/Utils/ModelChangeTracker.qml new file mode 100644 index 0000000000..fa32aae2ec --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Core/Utils/ModelChangeTracker.qml @@ -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++ + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/ModelUtils.qml b/ui/StatusQ/src/StatusQ/Core/Utils/ModelUtils.qml index ac59f5e0c7..c593e006b4 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/ModelUtils.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/ModelUtils.qml @@ -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 + } } diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/ModelsComparator.qml b/ui/StatusQ/src/StatusQ/Core/Utils/ModelsComparator.qml index 21abd0b08e..2d723b33fb 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/ModelsComparator.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/ModelsComparator.qml @@ -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) } } } diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/qmldir b/ui/StatusQ/src/StatusQ/Core/Utils/qmldir index 35ec7249d4..8fbc663629 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/qmldir +++ b/ui/StatusQ/src/StatusQ/Core/Utils/qmldir @@ -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 diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc index 167533aa44..12e767cce3 100644 --- a/ui/StatusQ/src/statusq.qrc +++ b/ui/StatusQ/src/statusq.qrc @@ -195,5 +195,6 @@ StatusQ/Components/LoadingComponent.qml StatusQ/Core/Utils/ModelUtils.qml StatusQ/Core/Utils/ModelsComparator.qml + StatusQ/Core/Utils/ModelChangeTracker.qml diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml index df690701ea..8b8ae66154 100644 --- a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml @@ -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 } } diff --git a/ui/app/AppLayouts/Chat/panels/communities/PermissionDuplicationWarningPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/PermissionDuplicationWarningPanel.qml new file mode 100644 index 0000000000..3cb0c77a25 --- /dev/null +++ b/ui/app/AppLayouts/Chat/panels/communities/PermissionDuplicationWarningPanel.qml @@ -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.") + } + } +} diff --git a/ui/app/AppLayouts/Chat/panels/communities/qmldir b/ui/app/AppLayouts/Chat/panels/communities/qmldir index 105b3070a3..edf906fe2d 100644 --- a/ui/app/AppLayouts/Chat/panels/communities/qmldir +++ b/ui/app/AppLayouts/Chat/panels/communities/qmldir @@ -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 diff --git a/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml b/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml index d82725ea52..d2e53284eb 100644 --- a/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml +++ b/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml @@ -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) diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml index e609f8d852..c56d15ca4e 100644 --- a/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml @@ -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