diff --git a/lib/types/thread-permission-types.js b/lib/types/thread-permission-types.js --- a/lib/types/thread-permission-types.js +++ b/lib/types/thread-permission-types.js @@ -84,6 +84,186 @@ export type ThreadPermissionFilterPrefix = $Values< typeof threadPermissionFilterPrefixes, >; + +const calendarEditPermission = { + title: 'Edit calendar', + description: 'Allows members to edit the community calendar', +}; +const editEntries = threadPermissions.EDIT_ENTRIES; +const descendantEditEntries = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.EDIT_ENTRIES; + +const secretChannelsPermission = { + title: 'See secret channels', + description: 'Allows members to see all secret channels', +}; +const descendantKnowOf = + threadPermissionPropagationPrefixes.DESCENDANT + threadPermissions.KNOW_OF; +const descendantVisible = + threadPermissionPropagationPrefixes.DESCENDANT + threadPermissions.VISIBLE; + +const voicedPermission = { + title: 'Speak in announcement channels', + description: 'Allows members to speak in announcement channels', +}; +const voiced = threadPermissions.VOICED; + +const createAndEditChannelsPermission = { + title: 'Create and edit channels', + description: 'Allows members to create new and edit existing channels', +}; +const editThreadName = threadPermissions.EDIT_THREAD_NAME; +const descendantEditThreadName = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.EDIT_THREAD_NAME; +const editThreadDescription = threadPermissions.EDIT_THREAD_DESCRIPTION; +const descendantEditThreadDescription = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.EDIT_THREAD_DESCRIPTION; +const editThreadColor = threadPermissions.EDIT_THREAD_COLOR; +const descendantEditThreadColor = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.EDIT_THREAD_COLOR; +const createSubchannels = threadPermissions.CREATE_SUBCHANNELS; +const descendantCreateSubchannels = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissionFilterPrefixes.TOP_LEVEL + + threadPermissions.CREATE_SUBCHANNELS; +const editThreadAvatar = threadPermissions.EDIT_THREAD_AVATAR; +const descendantEditThreadAvatar = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.EDIT_THREAD_AVATAR; + +const deleteChannelsPermission = { + title: 'Delete channels', + description: 'Allows members to delete channels', +}; +const deleteThread = threadPermissions.DELETE_THREAD; +const descendantDeleteThread = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.DELETE_THREAD; + +const addMembersPermission = { + title: 'Add members', + description: 'Allows members to add other members to channels', +}; +const addMembers = threadPermissions.ADD_MEMBERS; +const descendantAddMembers = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.ADD_MEMBERS; + +const removeMembersPermission = { + title: 'Remove members', + description: 'Allows members to remove other members from channels', +}; +const removeMembers = threadPermissions.REMOVE_MEMBERS; +const descendantRemoveMembers = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.REMOVE_MEMBERS; + +const changeRolePermission = { + title: 'Change roles', + description: 'Allows members to change the roles of other members', +}; +const changeRole = threadPermissions.CHANGE_ROLE; +const descendantChangeRole = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.CHANGE_ROLE; + +const editVisibilityPermission = { + title: 'Edit visibility', + description: 'Allows members to edit visibility permissions of channels', +}; +const editPermissions = threadPermissions.EDIT_PERMISSIONS; +const descendantEditPermissions = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.EDIT_PERMISSIONS; + +const managePinsPermission = { + title: 'Manage pins', + description: 'Allows members to pin or unpin messages in channels', +}; +const managePins = threadPermissions.MANAGE_PINS; +const descendantManagePins = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.MANAGE_PINS; + +const reactToMessagePermission = { + title: 'React to messages', + description: 'Allows members to add reactions to messages', +}; +const reactToMessage = threadPermissions.REACT_TO_MESSAGE; +const descendantReactToMessage = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.REACT_TO_MESSAGE; + +const editMessagePermission = { + title: 'Edit messages', + description: 'Allows members to edit their sent messages', +}; +const editMessage = threadPermissions.EDIT_MESSAGE; +const descendantEditMessage = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.EDIT_MESSAGE; + +const manageInviteLinksPermission = { + title: 'Manage invite links', + description: 'Allows members to create and delete invite links', +}; +const manageInviteLinks = threadPermissions.MANAGE_INVITE_LINKS; +const descendantManageInviteLinks = + threadPermissionPropagationPrefixes.DESCENDANT + + threadPermissions.MANAGE_INVITE_LINKS; + +export type ConfigurableCommunityPermissionOption = { + title: string, + description: string, +}; +type ConfigurableCommunityPermission = { + [permission: string]: ConfigurableCommunityPermissionOption, +}; + +export const configurableCommunityPermissions: ConfigurableCommunityPermission = + Object.freeze({ + [editEntries]: calendarEditPermission, + [descendantEditEntries]: calendarEditPermission, + [descendantKnowOf]: secretChannelsPermission, + [descendantVisible]: secretChannelsPermission, + [voiced]: voicedPermission, + [editThreadName]: createAndEditChannelsPermission, + [descendantEditThreadName]: createAndEditChannelsPermission, + [editThreadDescription]: createAndEditChannelsPermission, + [descendantEditThreadDescription]: createAndEditChannelsPermission, + [editThreadColor]: createAndEditChannelsPermission, + [descendantEditThreadColor]: createAndEditChannelsPermission, + [createSubchannels]: createAndEditChannelsPermission, + [descendantCreateSubchannels]: createAndEditChannelsPermission, + [editThreadAvatar]: createAndEditChannelsPermission, + [descendantEditThreadAvatar]: createAndEditChannelsPermission, + [deleteThread]: deleteChannelsPermission, + [descendantDeleteThread]: deleteChannelsPermission, + [addMembers]: addMembersPermission, + [descendantAddMembers]: addMembersPermission, + [removeMembers]: removeMembersPermission, + [descendantRemoveMembers]: removeMembersPermission, + [changeRole]: changeRolePermission, + [descendantChangeRole]: changeRolePermission, + [editPermissions]: editVisibilityPermission, + [descendantEditPermissions]: editVisibilityPermission, + [managePins]: managePinsPermission, + [descendantManagePins]: managePinsPermission, + [reactToMessage]: reactToMessagePermission, + [descendantReactToMessage]: reactToMessagePermission, + [editMessage]: editMessagePermission, + [descendantEditMessage]: editMessagePermission, + [manageInviteLinks]: manageInviteLinksPermission, + [descendantManageInviteLinks]: manageInviteLinksPermission, + }); + +export const configurableCommunityPermissionsOptions: $ReadOnlySet = + new Set(values(configurableCommunityPermissions)); + export type ThreadPermissionInfo = | { +value: true, +source: string } | { +value: false, +source: null }; diff --git a/lib/utils/objects.js b/lib/utils/objects.js --- a/lib/utils/objects.js +++ b/lib/utils/objects.js @@ -116,4 +116,15 @@ ); } -export { findMaximumDepth, values, hash, assertObjectsAreEqual, deepDiff }; +function getKeysByValue(obj: ObjectMap, value: T): K[] { + return Object.keys(obj).filter((key: K) => obj[key] === value); +} + +export { + findMaximumDepth, + values, + hash, + assertObjectsAreEqual, + deepDiff, + getKeysByValue, +}; diff --git a/native/roles/create-roles-screen.react.js b/native/roles/create-roles-screen.react.js --- a/native/roles/create-roles-screen.react.js +++ b/native/roles/create-roles-screen.react.js @@ -1,11 +1,22 @@ // @flow import * as React from 'react'; +import { View, Text, TouchableOpacity, ScrollView } from 'react-native'; +import { + configurableCommunityPermissions, + configurableCommunityPermissionsOptions, + type ConfigurableCommunityPermissionOption, +} from 'lib/types/thread-permission-types.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; +import { getKeysByValue } from 'lib/utils/objects.js'; import type { RolesNavigationProp } from './roles-navigator.react.js'; +import EnumSettingsOption from '../components/enum-settings-option.react.js'; +import SWMansionIcon from '../components/swmansion-icon.react.js'; +import TextInput from '../components/text-input.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; +import { useStyles } from '../themes/colors.js'; export type CreateRolesScreenParams = { +threadInfo: ThreadInfo, @@ -17,9 +28,183 @@ +route: NavigationRoute<'CreateRolesScreen'>, }; -// eslint-disable-next-line no-unused-vars function CreateRolesScreen(props: CreateRolesScreenProps): React.Node { - return <>; + // eslint-disable-next-line no-unused-vars + const { threadInfo, action } = props.route.params; + + const [customRoleName, setCustomRoleName] = + React.useState('New Role'); + const [selectedPermissions, setSelectedPermissions] = React.useState([]); + + const styles = useStyles(unboundStyles); + + const onClearPermissions = React.useCallback(() => { + setSelectedPermissions([]); + }, []); + + const isSelectedPermissionsEmpty = selectedPermissions.length === 0; + const clearPermissionsText = React.useMemo(() => { + if (isSelectedPermissionsEmpty) { + return ( + + Clear permissions + + ); + } + + return ( + + Clear permissions + + ); + }, [ + isSelectedPermissionsEmpty, + onClearPermissions, + styles.clearPermissionsText, + styles.clearPermissionsTextDisabled, + ]); + + const isUserFacingPermissionSelected = React.useCallback( + (option: ConfigurableCommunityPermissionOption) => { + const associatedPermissions = getKeysByValue( + configurableCommunityPermissions, + option, + ); + + return associatedPermissions.every(permission => + selectedPermissions.includes(permission), + ); + }, + [selectedPermissions], + ); + + const onEnumValuePress = React.useCallback( + (option: ConfigurableCommunityPermissionOption) => { + // configurableCommunityPermissions is an object where one permission + // is associated with exactly one user-facing option. This means that + // permission propagation are classed as separate entries in the object. + // So, for example, 'edit_entries' and 'descendants_edit_entries' are + // separate entries in the object, but they are both associated with the + // same user-facing option, 'Edit entries'. So when a user-facing option + // is selected, we need to select all permissions associated with that. + const associatedPermissions = getKeysByValue( + configurableCommunityPermissions, + option, + ); + + // Toggle the inclusivity of the permissions associated with the + // user-facing option. + if (isUserFacingPermissionSelected(option)) { + setSelectedPermissions(currentPermissions => + currentPermissions.filter( + permission => !associatedPermissions.includes(permission), + ), + ); + } else { + setSelectedPermissions(currentPermissions => [ + ...currentPermissions, + ...associatedPermissions, + ]); + } + }, + [isUserFacingPermissionSelected], + ); + + const permissionsList = React.useMemo( + () => + [...configurableCommunityPermissionsOptions].map(permission => ( + onEnumValuePress(permission)} + /> + )), + [isUserFacingPermissionSelected, onEnumValuePress], + ); + + const onChangeRoleNameInput = React.useCallback((roleName: string) => { + setCustomRoleName(roleName); + }, []); + + return ( + + + ROLE NAME + + + + + + + + PERMISSIONS + {clearPermissionsText} + + + {permissionsList} + + + + ); } +const unboundStyles = { + roleNameContainer: { + marginTop: 30, + }, + roleNameText: { + color: 'panelBackgroundLabel', + fontSize: 12, + marginBottom: 5, + marginLeft: 10, + }, + roleInput: { + backgroundColor: 'panelForeground', + padding: 12, + flexDirection: 'row', + justifyContent: 'space-between', + }, + roleInputComponent: { + color: 'whiteText', + fontSize: 16, + }, + pencilIcon: { + color: 'panelInputSecondaryForeground', + }, + permissionsContainer: { + marginTop: 20, + paddingBottom: 220, + }, + permissionsHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + permissionsText: { + color: 'panelBackgroundLabel', + fontSize: 12, + marginLeft: 10, + }, + clearPermissionsText: { + color: 'purpleLink', + fontSize: 12, + marginRight: 15, + }, + clearPermissionsTextDisabled: { + color: 'disabledButton', + fontSize: 12, + marginRight: 15, + }, + permissionsListContainer: { + backgroundColor: 'panelForeground', + marginTop: 10, + }, +}; + export default CreateRolesScreen;