diff --git a/ui/app/AppLayouts/Communities/models/ChannelPermissionsModelEditor.qml b/ui/app/AppLayouts/Communities/models/ChannelPermissionsModelEditor.qml new file mode 100644 index 0000000000..1e726fdbb9 --- /dev/null +++ b/ui/app/AppLayouts/Communities/models/ChannelPermissionsModelEditor.qml @@ -0,0 +1,343 @@ +import QtQuick 2.15 + +import StatusQ 0.1 + +import AppLayouts.Communities.controls 1.0 + +import SortFilterProxyModel 0.2 + +import StatusQ.Core.Utils 0.1 as StatusQUtils + +import utils 1.0 + + +/// Helper component for editing channel permissions +/// This component will create the necessary temporary models to be used until the changes are saved + +QtObject { + id: root + + // Input properties + + // Model containing all channels + required property var channelsModel + // Model containing all permissions for the current community + required property var permissionsModel + + // Channel ID + required property string channelId + // Channel name + required property string name + // Channel color + required property string color + // Channel emoji + required property string emoji + // Specify whether the channel is being edited or created + required property bool newChannelMode + + + // Output properties + + // The edited channel permissions model + readonly property alias channelPermissionsModel: d.channelPermissionsModel + // Live model of channels contaning the temporarely edited channel + readonly property alias liveChannelsModel: d.liveChannelsModel + + readonly property alias dirtyPermissions: d.channelPermissionsModel.dirty + + + // Input functions + + // Function creating a new permission based on the given arguments + function appendPermission(holdings, channels, permissionType, isPrivate) { + d.appendPermission(holdings, channels, permissionType, isPrivate) + } + + // Function editing a permission based on the given arguments + function editPermission(key, permissionType, holdings, channels, isPrivate) { + d.editPermission(key, permissionType, holdings, channels, isPrivate) + } + + // Function duplicating a permission. The new permission will be have a different id and key + function duplicatePermission(index) { + const permission = channelPermissionsModel.get(index) + + if (!permission) + return + + permission.id = Utils.uuid() + permission.key = Utils.uuid() + permission.holdingsListModel = d.newHoldingsModel(StatusQUtils.ModelUtils.modelToArray(permission.holdingsListModel)) + permission.channelsListModel = d.newChannelsModel(StatusQUtils.ModelUtils.modelToArray(permission.channelsListModel)) + channelPermissionsModel.append(permission) + } + + // Function removing a permission by index + function removePermission(index) { + channelPermissionsModel.remove(index, 1); + } + + + // Output functions + + // Function returning the list of added permissions + // The returned list contains the permissions that were added since the last reset + // Each item contains a map of role names and data value pairs as defined in the model + function getAddedPermissions() { + return d.flattenPermissions(channelPermissionsModel.getInsertedItems()) + } + + // Function returning the list of removed permissions + // The returned list contains the permissions that were removed since the last reset + // This list contains the permissions that are no longer available in the root.channelPermissionsModel, but are still available in the root.permissionsModel + // Each item contains a map of role names and data value pairs as defined in the model + function getRemovedPermissions() { + return d.flattenPermissions(channelPermissionsModel.getRemovedItems()) + } + + // Function returning the list of edited permissions + // The returned list contains the permissions that were edited since the last reset + // Each item contains a map of role names and data value pairs as defined in the model + function getEditedPermissions() { + return d.flattenPermissions(channelPermissionsModel.getEditedItems()) + } + + // This function resets the temporary models + // The dirtyPermissions property will be set to false + function reset() { + d.reset() + } + + //internals + readonly property QtObject d: QtObject { + id: d + + signal resetDone() + + function reset() { + channelPermissionsModel.revert(); + liveChannelsModel.revert(); + d.resetDone(); + } + + function appendPermission(holdings, channels, permissionType, isPrivate) { + var permissionsModelData = { + id: Utils.uuid(), + key: Utils.uuid(), + holdingsListModel: d.newHoldingsModel(holdings), + channelsListModel: d.newChannelsModel(channels), + "permissionType": permissionType, + "isPrivate": isPrivate + }; + + d.channelPermissionsModel.append(permissionsModelData) + } + + function editPermission(key, permissionType, holdings, channels, isPrivate) { + const index = StatusQUtils.ModelUtils.indexOf(d.channelPermissionsModel, "key", key) + if (index === -1) + return + + const permissionItem = d.channelPermissionsModel.get(index) + + d.channelPermissionsModel.set(index, { + "permissionType": permissionType, + "channelsListModel": d.newEditedChannelsModel(channels, permissionItem.channelsListModel), + "holdingsListModel": d.newEditedHoldingsModel(holdings, permissionItem.holdingsListModel), + "isPrivate": isPrivate + }) + } + + // Creates a new channel model to be used in the permissions model + // Expected roles: "key" + function newChannelsModel(channelsArray) { + var channelsModel = selfDestroyingModel.createObject(d.channelPermissionsModel); + + for (var i = 0; i < channelsArray.length; i++) { + channelsModel.append({ key: channelsArray[i].key }) + } + + return channelsModel; + } + + // Creates a new writable channel model on top of base model to be used in the permissions model + function newEditedChannelsModel(channelsArray, baseModel) { + var channelsModel = selfDestroyingWritableModel.createObject(d.channelPermissionsModel, {sourceModel: baseModel}); + + const count = channelsModel.rowCount() + for (var i = 0; i < count; i++) { + const ok = channelsModel.remove(0) + + console.assert(ok + , "Failed to remove channel") + } + + for (var i = 0; i < channelsArray.length; i++) { + const ok = channelsModel.append({ + key: channelsArray[i].key, + }) + + console.assert(ok, "Failed to append channel"); + } + + return channelsModel; + } + + // Creates a new holdings model to be used in the permissions model + function newHoldingsModel(holdingsArray, sourceModel) { + var holdingsModel = selfDestroyingModel.createObject(d.channelPermissionsModel); + for (var i = 0; i < holdingsArray.length; i++) { + holdingsModel.append(holdingsArray[i]); + } + + return holdingsModel; + } + + function newEditedHoldingsModel(holdingsArray, baseModel) { + var holdingsModel = selfDestroyingWritableModel.createObject(d.channelPermissionsModel, {sourceModel: baseModel}); + + const count = holdingsModel.rowCount() + for (var i = 0; i < count; i++) { + const ok = holdingsModel.remove(0) + console.assert(ok, "Failed to remove holding") + } + + for (var i = 0; i < holdingsArray.length; i++) { + const ok = holdingsModel.append(holdingsArray[i]) + console.assert(ok, "Failed to append holding"); + } + + return holdingsModel; + } + + function flattenPermissions(permissionsItems) { + for (var i = 0; i < permissionsItems.length; i++) { + permissionsItems[i].holdingsListModel = StatusQUtils.ModelUtils.modelToArray(permissionsItems[i].holdingsListModel) + permissionsItems[i].channelsListModel = StatusQUtils.ModelUtils.modelToArray(permissionsItems[i].channelsListModel) + } + + return permissionsItems + } + + + property Connections chatIdConnections: Connections { + target: root + enabled: root.newChannelMode + + property string previousChannelId: "" + + Component.onCompleted: previousChannelId = root.channelId + + /// go through all the channels and replace the old channel id with the new one + function onChannelIdChanged() { + if (previousChannelId === root.channelId) + return; + + if (previousChannelId == "") { + previousChannelId = root.channelId; + return; + } + + for (var i = 0; i < liveChannelsModel.rowCount(); i++) { + if (liveChannelsModel.get(i).itemId === previousChannelId) { + liveChannelsModel.set(i, { itemId: root.channelId }) + break; + } + } + + for (var i = 0; i < channelPermissionsModel.rowCount(); i++) { + const currentItem = channelPermissionsModel.get(i) + const channelsListModel = currentItem.channelsListModel + + for (var j = 0; j < channelsListModel.rowCount(); j++) { + if (channelsListModel.get(j).key === previousChannelId) { + channelsListModel.set(j, { key: root.channelId }) + break; + } + } + } + previousChannelId = root.channelId; + } + } + + + // Channel permissions model containing the temporarely edited permissions + property WritableProxyModel channelPermissionsModel: WritableProxyModel { + sourceModel: SortFilterProxyModel { + id: filteredPermissionsModel + + sourceModel: root.permissionsModel + + filters: [ + FastExpressionFilter { + function filterPredicate(id, permissionType) { + return !PermissionTypes.isCommunityPermission(permissionType) && root.permissionsModel.belongsToChat(id, root.channelId) + } + expression: { + return filterPredicate(model.id, model.permissionType) + } + expectedRoles: [ "id", "permissionType" ] + } + ] + } + } + + // Channels model containing the temporarely edited channel + property WritableProxyModel liveChannelsModel: WritableProxyModel { + id: newChannelModel + + function updateCurrentChannelProperty(nameAndValue) { + const index = StatusQUtils.ModelUtils.indexOf(newChannelModel, "itemId", root.channelId) + if (index !== -1) { + newChannelModel.set(index, nameAndValue) + } + } + + Component.onCompleted: { + if (!root.newChannelMode) + return; + + console.assert(newChannelModel.append({ + itemId: root.channelId, + name: root.name, + emoji: root.emoji, + color: root.color + }), "Failed to add channel to channelsModel") + } + + property Connections channelConnections: Connections { + target: root + function onColorChanged() { + newChannelModel.updateCurrentChannelProperty({"color": root.color}) + } + function onNameChanged() { + newChannelModel.updateCurrentChannelProperty({"name": root.name}) + } + function onEmojiChanged() { + newChannelModel.updateCurrentChannelProperty({"emoji": root.emoji}) + } + } + + sourceModel: root.channelsModel + } + + // Used for dynamic model creation using Component.createObject + property Component selfDestroyingModel: Component { + ListModel { + id: model + + Component.onCompleted: d.resetDone.connect(model.destroy) + Component.onDestruction: d.resetDone.disconnect(model.destroy) + } + } + + property Component selfDestroyingWritableModel: Component { + WritableProxyModel { + id: model + + Component.onCompleted: d.resetDone.connect(model.destroy) + Component.onDestruction: d.resetDone.disconnect(model.destroy) + } + } + } +} diff --git a/ui/app/AppLayouts/Communities/models/qmldir b/ui/app/AppLayouts/Communities/models/qmldir new file mode 100644 index 0000000000..97ff2ce0e4 --- /dev/null +++ b/ui/app/AppLayouts/Communities/models/qmldir @@ -0,0 +1 @@ +ChannelPermissionsModelEditor 1.0 ChannelPermissionsModelEditor.qml \ No newline at end of file