feat(CommunityPermissions): Implement duplication checking
Moreover: - adapt flow to the design - introduce ModelChangeTracker utility component Closes: #9048
This commit is contained in:
parent
c78eaef2b6
commit
a97c8a720e
|
@ -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 {
|
||||
|
|
|
@ -39,10 +39,6 @@ SplitView {
|
|||
readonly property var collectiblesModel: CollectiblesModel {
|
||||
id: collectiblesModel
|
||||
}
|
||||
|
||||
function duplicatePermission(index) {
|
||||
logs.logEvent("CommunitiesStore::duplicatePermission - index: " + index)
|
||||
}
|
||||
}
|
||||
|
||||
rootStore: QtObject {
|
||||
|
|
|
@ -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++
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,101 +16,24 @@ QtObject {
|
|||
readonly property QtObject _d: QtObject {
|
||||
id: d
|
||||
|
||||
component ModelObserver: Connections {
|
||||
function onRowsInserted() {
|
||||
d.changeCounter++
|
||||
readonly property ModelChangeTracker trackerA: ModelChangeTracker {
|
||||
model: modelA
|
||||
}
|
||||
|
||||
function onRowsMoved() {
|
||||
d.changeCounter++
|
||||
readonly property ModelChangeTracker trackerB: ModelChangeTracker {
|
||||
model: modelB
|
||||
}
|
||||
|
||||
function onRowsRemoved() {
|
||||
d.changeCounter++
|
||||
}
|
||||
|
||||
function onLayoutChanged() {
|
||||
d.changeCounter++
|
||||
}
|
||||
|
||||
function onModelReset() {
|
||||
d.changeCounter++
|
||||
}
|
||||
|
||||
function onDataChanged() {
|
||||
d.changeCounter++
|
||||
}
|
||||
}
|
||||
|
||||
property int changeCounter: 0
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue