mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-10 09:10:29 +03:00
New room settings, add customizable power levels and dev tools (#2222)
* WIP - add room settings dialog * join rule setting - WIP * show emojis & stickers in room settings - WIP * restyle join rule switcher * Merge branch 'dev' into new-room-settings * add join rule hook * open room settings from global state * open new room settings from all places * rearrange settings menu item * add option for creating new image pack * room devtools - WIP * render room state events as list * add option to open state event * add option to edit state event * refactor text area code editor into hook * add option to send message and state event * add cutout card component * add hook for room account data * display room account data - WIP * refactor global account data editor component * add account data editor in room * fix font style in devtool * show state events in compact form * add option to delete room image pack * add server badge component * add member tile component * render members in room settings * add search in room settings member * add option to reset member search * add filter in room members * fix member virtual item key * remove color from serve badge in room members * show room in settings * fix loading indicator position * power level tags in room setting - WIP * generate fallback tag in backward compatible way * add color picker * add powers editor - WIP * add props to stop adding emoji to recent usage * add beta feature notice badge * add types for power level tag icon * refactor image pack rooms code to hook * option for adding new power levels tags * remove console log * refactor power icon * add option to edit power level tags * remove power level from powers pill * fix power level labels * add option to delete power levels * fix long power level name shrinks power integer * room permissions - WIP * add power level selector component * add room permissions * move user default permission setting to other group * add power permission peek menu * fix weigh of power switch text * hide above for max power in permission switcher * improve beta badge description * render room profile in room settings * add option to edit room profile * make room topic input text area * add option to enable room encryption in room settings * add option to change message history visibility * add option to change join rule * add option for addresses in room settings * close encryption dialog after enabling
This commit is contained in:
parent
00f3df8719
commit
286983c833
73 changed files with 6196 additions and 420 deletions
|
|
@ -1,26 +1,16 @@
|
|||
import { Room } from 'matrix-js-sdk';
|
||||
import { createContext, useCallback, useContext, useMemo } from 'react';
|
||||
import { MatrixEvent, Room } from 'matrix-js-sdk';
|
||||
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import produce from 'immer';
|
||||
import { useStateEvent } from './useStateEvent';
|
||||
import { StateEvent } from '../../types/matrix/room';
|
||||
import { useForceUpdate } from './useForceUpdate';
|
||||
import { useStateEventCallback } from './useStateEventCallback';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { getStateEvent } from '../utils/room';
|
||||
|
||||
export type PowerLevelActions = 'invite' | 'redact' | 'kick' | 'ban' | 'historical';
|
||||
export type PowerLevelNotificationsAction = 'room';
|
||||
|
||||
enum DefaultPowerLevels {
|
||||
usersDefault = 0,
|
||||
stateDefault = 50,
|
||||
eventsDefault = 0,
|
||||
invite = 0,
|
||||
redact = 50,
|
||||
kick = 50,
|
||||
ban = 50,
|
||||
historical = 0,
|
||||
}
|
||||
|
||||
export interface IPowerLevels {
|
||||
export type IPowerLevels = {
|
||||
users_default?: number;
|
||||
state_default?: number;
|
||||
events_default?: number;
|
||||
|
|
@ -33,12 +23,53 @@ export interface IPowerLevels {
|
|||
events?: Record<string, number>;
|
||||
users?: Record<string, number>;
|
||||
notifications?: Record<string, number>;
|
||||
}
|
||||
};
|
||||
|
||||
const DEFAULT_POWER_LEVELS: Required<IPowerLevels> = {
|
||||
users_default: 0,
|
||||
state_default: 50,
|
||||
events_default: 0,
|
||||
invite: 0,
|
||||
redact: 50,
|
||||
kick: 50,
|
||||
ban: 50,
|
||||
historical: 0,
|
||||
events: {},
|
||||
users: {},
|
||||
notifications: {
|
||||
room: 50,
|
||||
},
|
||||
};
|
||||
|
||||
const fillMissingPowers = (powerLevels: IPowerLevels): IPowerLevels =>
|
||||
produce(powerLevels, (draftPl: IPowerLevels) => {
|
||||
const keys = Object.keys(DEFAULT_POWER_LEVELS) as unknown as (keyof IPowerLevels)[];
|
||||
keys.forEach((key) => {
|
||||
if (draftPl[key] === undefined) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
draftPl[key] = DEFAULT_POWER_LEVELS[key] as any;
|
||||
}
|
||||
});
|
||||
if (draftPl.notifications && typeof draftPl.notifications.room !== 'number') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
draftPl.notifications.room = DEFAULT_POWER_LEVELS.notifications.room;
|
||||
}
|
||||
return draftPl;
|
||||
});
|
||||
|
||||
const getPowersLevelFromMatrixEvent = (mEvent?: MatrixEvent): IPowerLevels => {
|
||||
const pl = mEvent?.getContent<IPowerLevels>();
|
||||
if (!pl) return DEFAULT_POWER_LEVELS;
|
||||
|
||||
return fillMissingPowers(pl);
|
||||
};
|
||||
|
||||
export function usePowerLevels(room: Room): IPowerLevels {
|
||||
const powerLevelsEvent = useStateEvent(room, StateEvent.RoomPowerLevels);
|
||||
const powerLevels: IPowerLevels =
|
||||
powerLevelsEvent?.getContent<IPowerLevels>() ?? DefaultPowerLevels;
|
||||
const powerLevels: IPowerLevels = useMemo(
|
||||
() => getPowersLevelFromMatrixEvent(powerLevelsEvent),
|
||||
[powerLevelsEvent]
|
||||
);
|
||||
|
||||
return powerLevels;
|
||||
}
|
||||
|
|
@ -55,7 +86,18 @@ export const usePowerLevelsContext = (): IPowerLevels => {
|
|||
|
||||
export const useRoomsPowerLevels = (rooms: Room[]): Map<string, IPowerLevels> => {
|
||||
const mx = useMatrixClient();
|
||||
const [updateCount, forceUpdate] = useForceUpdate();
|
||||
const getRoomsPowerLevels = useCallback(() => {
|
||||
const rToPl = new Map<string, IPowerLevels>();
|
||||
|
||||
rooms.forEach((room) => {
|
||||
const mEvent = getStateEvent(room, StateEvent.RoomPowerLevels, '');
|
||||
rToPl.set(room.roomId, getPowersLevelFromMatrixEvent(mEvent));
|
||||
});
|
||||
|
||||
return rToPl;
|
||||
}, [rooms]);
|
||||
|
||||
const [roomToPowerLevels, setRoomToPowerLevels] = useState(() => getRoomsPowerLevels());
|
||||
|
||||
useStateEventCallback(
|
||||
mx,
|
||||
|
|
@ -68,28 +110,13 @@ export const useRoomsPowerLevels = (rooms: Room[]): Map<string, IPowerLevels> =>
|
|||
event.getStateKey() === '' &&
|
||||
rooms.find((r) => r.roomId === roomId)
|
||||
) {
|
||||
forceUpdate();
|
||||
setRoomToPowerLevels(getRoomsPowerLevels());
|
||||
}
|
||||
},
|
||||
[rooms, forceUpdate]
|
||||
[rooms, getRoomsPowerLevels]
|
||||
)
|
||||
);
|
||||
|
||||
const roomToPowerLevels = useMemo(
|
||||
() => {
|
||||
const rToPl = new Map<string, IPowerLevels>();
|
||||
|
||||
rooms.forEach((room) => {
|
||||
const pl = getStateEvent(room, StateEvent.RoomPowerLevels, '')?.getContent<IPowerLevels>();
|
||||
if (pl) rToPl.set(room.roomId, pl);
|
||||
});
|
||||
|
||||
return rToPl;
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[rooms, updateCount]
|
||||
);
|
||||
|
||||
return roomToPowerLevels;
|
||||
};
|
||||
|
||||
|
|
@ -104,42 +131,83 @@ export type CanDoAction = (
|
|||
action: PowerLevelActions,
|
||||
powerLevel: number
|
||||
) => boolean;
|
||||
export type CanDoNotificationAction = (
|
||||
powerLevels: IPowerLevels,
|
||||
action: PowerLevelNotificationsAction,
|
||||
powerLevel: number
|
||||
) => boolean;
|
||||
|
||||
export type PowerLevelsAPI = {
|
||||
getPowerLevel: GetPowerLevel;
|
||||
canSendEvent: CanSend;
|
||||
canSendStateEvent: CanSend;
|
||||
canDoAction: CanDoAction;
|
||||
canDoNotificationAction: CanDoNotificationAction;
|
||||
};
|
||||
|
||||
export const powerLevelAPI: PowerLevelsAPI = {
|
||||
getPowerLevel: (powerLevels, userId) => {
|
||||
export type ReadPowerLevelAPI = {
|
||||
user: GetPowerLevel;
|
||||
event: (powerLevels: IPowerLevels, eventType: string | undefined) => number;
|
||||
state: (powerLevels: IPowerLevels, eventType: string | undefined) => number;
|
||||
action: (powerLevels: IPowerLevels, action: PowerLevelActions) => number;
|
||||
notification: (powerLevels: IPowerLevels, action: PowerLevelNotificationsAction) => number;
|
||||
};
|
||||
|
||||
export const readPowerLevel: ReadPowerLevelAPI = {
|
||||
user: (powerLevels, userId) => {
|
||||
const { users_default: usersDefault, users } = powerLevels;
|
||||
if (userId && users && typeof users[userId] === 'number') {
|
||||
return users[userId];
|
||||
}
|
||||
return usersDefault ?? DefaultPowerLevels.usersDefault;
|
||||
return usersDefault ?? DEFAULT_POWER_LEVELS.users_default;
|
||||
},
|
||||
canSendEvent: (powerLevels, eventType, powerLevel) => {
|
||||
event: (powerLevels, eventType) => {
|
||||
const { events, events_default: eventsDefault } = powerLevels;
|
||||
if (events && eventType && typeof events[eventType] === 'number') {
|
||||
return powerLevel >= events[eventType];
|
||||
return events[eventType];
|
||||
}
|
||||
return powerLevel >= (eventsDefault ?? DefaultPowerLevels.eventsDefault);
|
||||
return eventsDefault ?? DEFAULT_POWER_LEVELS.events_default;
|
||||
},
|
||||
canSendStateEvent: (powerLevels, eventType, powerLevel) => {
|
||||
state: (powerLevels, eventType) => {
|
||||
const { events, state_default: stateDefault } = powerLevels;
|
||||
if (events && eventType && typeof events[eventType] === 'number') {
|
||||
return powerLevel >= events[eventType];
|
||||
return events[eventType];
|
||||
}
|
||||
return powerLevel >= (stateDefault ?? DefaultPowerLevels.stateDefault);
|
||||
return stateDefault ?? DEFAULT_POWER_LEVELS.state_default;
|
||||
},
|
||||
action: (powerLevels, action) => {
|
||||
const powerLevel = powerLevels[action];
|
||||
if (typeof powerLevel === 'number') {
|
||||
return powerLevel;
|
||||
}
|
||||
return DEFAULT_POWER_LEVELS[action];
|
||||
},
|
||||
notification: (powerLevels, action) => {
|
||||
const powerLevel = powerLevels.notifications?.[action];
|
||||
if (typeof powerLevel === 'number') {
|
||||
return powerLevel;
|
||||
}
|
||||
return DEFAULT_POWER_LEVELS.notifications[action];
|
||||
},
|
||||
};
|
||||
|
||||
export const powerLevelAPI: PowerLevelsAPI = {
|
||||
getPowerLevel: (powerLevels, userId) => readPowerLevel.user(powerLevels, userId),
|
||||
canSendEvent: (powerLevels, eventType, powerLevel) => {
|
||||
const requiredPL = readPowerLevel.event(powerLevels, eventType);
|
||||
return powerLevel >= requiredPL;
|
||||
},
|
||||
canSendStateEvent: (powerLevels, eventType, powerLevel) => {
|
||||
const requiredPL = readPowerLevel.state(powerLevels, eventType);
|
||||
return powerLevel >= requiredPL;
|
||||
},
|
||||
canDoAction: (powerLevels, action, powerLevel) => {
|
||||
const requiredPL = powerLevels[action];
|
||||
if (typeof requiredPL === 'number') {
|
||||
return powerLevel >= requiredPL;
|
||||
}
|
||||
return powerLevel >= DefaultPowerLevels[action];
|
||||
const requiredPL = readPowerLevel.action(powerLevels, action);
|
||||
return powerLevel >= requiredPL;
|
||||
},
|
||||
canDoNotificationAction: (powerLevels, action, powerLevel) => {
|
||||
const requiredPL = readPowerLevel.notification(powerLevels, action);
|
||||
return powerLevel >= requiredPL;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -167,10 +235,121 @@ export const usePowerLevelsAPI = (powerLevels: IPowerLevels) => {
|
|||
[powerLevels]
|
||||
);
|
||||
|
||||
const canDoNotificationAction = useCallback(
|
||||
(action: PowerLevelNotificationsAction, powerLevel: number) =>
|
||||
powerLevelAPI.canDoNotificationAction(powerLevels, action, powerLevel),
|
||||
[powerLevels]
|
||||
);
|
||||
|
||||
return {
|
||||
getPowerLevel,
|
||||
canSendEvent,
|
||||
canSendStateEvent,
|
||||
canDoAction,
|
||||
canDoNotificationAction,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Permissions
|
||||
*/
|
||||
|
||||
type DefaultPermissionLocation = {
|
||||
user: true;
|
||||
key?: string;
|
||||
};
|
||||
|
||||
type ActionPermissionLocation = {
|
||||
action: true;
|
||||
key: PowerLevelActions;
|
||||
};
|
||||
|
||||
type EventPermissionLocation = {
|
||||
state?: true;
|
||||
key?: string;
|
||||
};
|
||||
|
||||
type NotificationPermissionLocation = {
|
||||
notification: true;
|
||||
key: PowerLevelNotificationsAction;
|
||||
};
|
||||
|
||||
export type PermissionLocation =
|
||||
| DefaultPermissionLocation
|
||||
| ActionPermissionLocation
|
||||
| EventPermissionLocation
|
||||
| NotificationPermissionLocation;
|
||||
|
||||
export const getPermissionPower = (
|
||||
powerLevels: IPowerLevels,
|
||||
location: PermissionLocation
|
||||
): number => {
|
||||
if ('user' in location) {
|
||||
return readPowerLevel.user(powerLevels, location.key);
|
||||
}
|
||||
if ('action' in location) {
|
||||
return readPowerLevel.action(powerLevels, location.key);
|
||||
}
|
||||
if ('notification' in location) {
|
||||
return readPowerLevel.notification(powerLevels, location.key);
|
||||
}
|
||||
if ('state' in location) {
|
||||
return readPowerLevel.state(powerLevels, location.key);
|
||||
}
|
||||
|
||||
return readPowerLevel.event(powerLevels, location.key);
|
||||
};
|
||||
|
||||
export const applyPermissionPower = (
|
||||
powerLevels: IPowerLevels,
|
||||
location: PermissionLocation,
|
||||
power: number
|
||||
): IPowerLevels => {
|
||||
if ('user' in location) {
|
||||
if (typeof location.key === 'string') {
|
||||
const users = powerLevels.users ?? {};
|
||||
users[location.key] = power;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels.users = users;
|
||||
return powerLevels;
|
||||
}
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels.users_default = power;
|
||||
return powerLevels;
|
||||
}
|
||||
if ('action' in location) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels[location.key] = power;
|
||||
return powerLevels;
|
||||
}
|
||||
if ('notification' in location) {
|
||||
const notifications = powerLevels.notifications ?? {};
|
||||
notifications[location.key] = power;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels.notifications = notifications;
|
||||
return powerLevels;
|
||||
}
|
||||
if ('state' in location) {
|
||||
if (typeof location.key === 'string') {
|
||||
const events = powerLevels.events ?? {};
|
||||
events[location.key] = power;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels.events = events;
|
||||
return powerLevels;
|
||||
}
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels.state_default = power;
|
||||
return powerLevels;
|
||||
}
|
||||
|
||||
if (typeof location.key === 'string') {
|
||||
const events = powerLevels.events ?? {};
|
||||
events[location.key] = power;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels.events = events;
|
||||
return powerLevels;
|
||||
}
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels.events_default = power;
|
||||
return powerLevels;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue