Merge branch 'dev' into voicemail
This commit is contained in:
commit
c22e9b008d
19 changed files with 485 additions and 74 deletions
7
public/res/ic/outlined/check.svg
Normal file
7
public/res/ic/outlined/check.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<polygon points="9.5,14.1 6,10.6 4.6,12 8.1,15.5 9.5,16.9 19.4,7 18,5.6 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 520 B |
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './Avatar.scss';
|
||||
|
||||
|
@ -10,22 +10,16 @@ import RawIcon from '../system-icons/RawIcon';
|
|||
function Avatar({
|
||||
text, bgColor, iconSrc, iconColor, imageSrc, size,
|
||||
}) {
|
||||
const [image, updateImage] = useState(imageSrc);
|
||||
let textSize = 's1';
|
||||
if (size === 'large') textSize = 'h1';
|
||||
if (size === 'small') textSize = 'b1';
|
||||
if (size === 'extra-small') textSize = 'b3';
|
||||
|
||||
useEffect(() => {
|
||||
updateImage(imageSrc);
|
||||
return () => updateImage(null);
|
||||
}, [imageSrc]);
|
||||
|
||||
return (
|
||||
<div className={`avatar-container avatar-container__${size} noselect`}>
|
||||
{
|
||||
image !== null
|
||||
? <img draggable="false" src={image} onError={() => updateImage(null)} alt="avatar" />
|
||||
imageSrc !== null
|
||||
? <img draggable="false" src={imageSrc} alt="avatar" />
|
||||
: (
|
||||
<span
|
||||
style={{ backgroundColor: iconSrc === null ? bgColor : 'transparent' }}
|
||||
|
|
84
src/app/atoms/context-menu/ReusableContextMenu.jsx
Normal file
84
src/app/atoms/context-menu/ReusableContextMenu.jsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
|
||||
import ContextMenu from './ContextMenu';
|
||||
|
||||
let key = null;
|
||||
function ReusableContextMenu() {
|
||||
const [data, setData] = useState(null);
|
||||
const openerRef = useRef(null);
|
||||
|
||||
const closeMenu = () => {
|
||||
key = null;
|
||||
if (data) openerRef.current.click();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const { cords } = data;
|
||||
openerRef.current.style.transform = `translate(${cords.x}px, ${cords.y}px)`;
|
||||
openerRef.current.style.width = `${cords.width}px`;
|
||||
openerRef.current.style.height = `${cords.height}px`;
|
||||
openerRef.current.click();
|
||||
}
|
||||
const handleContextMenuOpen = (placement, cords, render) => {
|
||||
if (key) {
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
setData({ placement, cords, render });
|
||||
};
|
||||
navigation.on(cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, handleContextMenuOpen);
|
||||
return () => {
|
||||
navigation.removeListener(
|
||||
cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED,
|
||||
handleContextMenuOpen,
|
||||
);
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
const handleAfterToggle = (isVisible) => {
|
||||
if (isVisible) {
|
||||
key = Math.random();
|
||||
return;
|
||||
}
|
||||
if (setData) setData(null);
|
||||
|
||||
if (key === null) return;
|
||||
const copyKey = key;
|
||||
setTimeout(() => {
|
||||
if (key === copyKey) key = null;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return (
|
||||
<ContextMenu
|
||||
afterToggle={handleAfterToggle}
|
||||
placement={data?.placement || 'right'}
|
||||
content={data?.render(closeMenu) ?? ''}
|
||||
render={(toggleMenu) => (
|
||||
<input
|
||||
ref={openerRef}
|
||||
onClick={toggleMenu}
|
||||
type="button"
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
backgroundColor: 'transparent',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
padding: 0,
|
||||
border: 'none',
|
||||
visibility: 'hidden',
|
||||
appearance: 'none',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReusableContextMenu;
|
|
@ -0,0 +1,49 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './PowerLevelSelector.scss';
|
||||
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
|
||||
|
||||
import CheckIC from '../../../../public/res/ic/outlined/check.svg';
|
||||
|
||||
function PowerLevelSelector({
|
||||
value, max, onSelect,
|
||||
}) {
|
||||
const handleSubmit = (e) => {
|
||||
const powerLevel = e.target.elements['power-level'];
|
||||
if (!powerLevel) return;
|
||||
onSelect(Number(powerLevel));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="power-level-selector">
|
||||
<MenuHeader>Power level selector</MenuHeader>
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(e); }}>
|
||||
<input
|
||||
className="input"
|
||||
defaultValue={value}
|
||||
type="number"
|
||||
name="power-level"
|
||||
placeholder="Power level"
|
||||
max={max}
|
||||
autoComplete="off"
|
||||
required
|
||||
/>
|
||||
<IconButton variant="primary" src={CheckIC} type="submit" />
|
||||
</form>
|
||||
{max >= 0 && <MenuHeader>Presets</MenuHeader>}
|
||||
{max >= 100 && <MenuItem variant={value === 100 ? 'positive' : 'surface'} onClick={() => onSelect(100)}>Admin - 100</MenuItem>}
|
||||
{max >= 50 && <MenuItem variant={value === 50 ? 'positive' : 'surface'} onClick={() => onSelect(50)}>Mod - 50</MenuItem>}
|
||||
{max >= 0 && <MenuItem variant={value === 0 ? 'positive' : 'surface'} onClick={() => onSelect(0)}>Member - 0</MenuItem>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PowerLevelSelector.propTypes = {
|
||||
value: PropTypes.number.isRequired,
|
||||
max: PropTypes.number.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default PowerLevelSelector;
|
|
@ -0,0 +1,20 @@
|
|||
@use '../../partials/flex';
|
||||
@use '../../partials/dir';
|
||||
|
||||
.power-level-selector {
|
||||
& .context-menu__item .text {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
& form {
|
||||
margin: var(--sp-normal);
|
||||
display: flex;
|
||||
|
||||
& input {
|
||||
@extend .cp-fx__item-one;
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
width: 148px;
|
||||
padding: 9px var(--sp-tight);
|
||||
}
|
||||
}
|
||||
}
|
208
src/app/molecules/room-permissions/RoomPermissions.jsx
Normal file
208
src/app/molecules/room-permissions/RoomPermissions.jsx
Normal file
|
@ -0,0 +1,208 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './RoomPermissions.scss';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { getPowerLabel } from '../../../util/matrixUtil';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import Button from '../../atoms/button/Button';
|
||||
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
|
||||
import SettingTile from '../setting-tile/SettingTile';
|
||||
|
||||
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
|
||||
|
||||
const permissionsInfo = {
|
||||
users_default: {
|
||||
name: 'Default role',
|
||||
description: 'Set default role for all members.',
|
||||
default: 0,
|
||||
},
|
||||
events_default: {
|
||||
name: 'Send messages',
|
||||
description: 'Set minimum power level to send messages in room.',
|
||||
default: 0,
|
||||
},
|
||||
redact: {
|
||||
name: 'Delete messages sent by others',
|
||||
description: 'Set minimum power level to delete messages in room.',
|
||||
default: 50,
|
||||
},
|
||||
notifications: {
|
||||
name: 'Ping room',
|
||||
description: 'Set minimum power level to ping room.',
|
||||
default: {
|
||||
room: 50,
|
||||
},
|
||||
},
|
||||
'm.space.child': {
|
||||
parent: 'events',
|
||||
name: 'Manage rooms in space',
|
||||
description: 'Set minimum power level to manage rooms in space.',
|
||||
default: 50,
|
||||
},
|
||||
invite: {
|
||||
name: 'Invite',
|
||||
description: 'Set minimum power level to invite members.',
|
||||
default: 50,
|
||||
},
|
||||
kick: {
|
||||
name: 'Kick',
|
||||
description: 'Set minimum power level to kick members.',
|
||||
default: 50,
|
||||
},
|
||||
ban: {
|
||||
name: 'Ban',
|
||||
description: 'Set minimum power level to ban members.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.avatar': {
|
||||
parent: 'events',
|
||||
name: 'Change avatar',
|
||||
description: 'Set minimum power level to change room/space avatar.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.name': {
|
||||
parent: 'events',
|
||||
name: 'Change name',
|
||||
description: 'Set minimum power level to change room/space name.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.topic': {
|
||||
parent: 'events',
|
||||
name: 'Change topic',
|
||||
description: 'Set minimum power level to change room/space topic.',
|
||||
default: 50,
|
||||
},
|
||||
state_default: {
|
||||
name: 'Change settings',
|
||||
description: 'Set minimum power level to change settings.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.canonical_alias': {
|
||||
parent: 'events',
|
||||
name: 'Change published address',
|
||||
description: 'Set minimum power level to publish and set main address.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.power_levels': {
|
||||
parent: 'events',
|
||||
name: 'Change permissions',
|
||||
description: 'Set minimum power level to change permissions.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.encryption': {
|
||||
parent: 'events',
|
||||
name: 'Enable room encryption',
|
||||
description: 'Set minimum power level to enable room encryption.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.history_visibility': {
|
||||
parent: 'events',
|
||||
name: 'Change history visibility',
|
||||
description: 'Set minimum power level to change room messages history visibility.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.tombstone': {
|
||||
parent: 'events',
|
||||
name: 'Upgrade room',
|
||||
description: 'Set minimum power level to upgrade room.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.pinned_events': {
|
||||
parent: 'events',
|
||||
name: 'Pin messages',
|
||||
description: 'Set minimum power level to pin messages in room.',
|
||||
default: 50,
|
||||
},
|
||||
'm.room.server_acl': {
|
||||
parent: 'events',
|
||||
name: 'Change server ACLs',
|
||||
description: 'Set minimum power level to change server ACLs.',
|
||||
default: 50,
|
||||
},
|
||||
'im.vector.modular.widgets': {
|
||||
parent: 'events',
|
||||
name: 'Modify widgets',
|
||||
description: 'Set minimum power level to modify room widgets.',
|
||||
default: 50,
|
||||
},
|
||||
};
|
||||
|
||||
const roomPermsGroups = {
|
||||
'General Permissions': ['users_default', 'events_default', 'redact', 'notifications'],
|
||||
'Manage members permissions': ['invite', 'kick', 'ban'],
|
||||
'Room profile permissions': ['m.room.avatar', 'm.room.name', 'm.room.topic'],
|
||||
'Settings permissions': ['state_default', 'm.room.canonical_alias', 'm.room.power_levels', 'm.room.encryption', 'm.room.history_visibility'],
|
||||
'Other permissions': ['m.room.tombstone', 'm.room.pinned_events', 'm.room.server_acl', 'im.vector.modular.widgets'],
|
||||
};
|
||||
|
||||
const spacePermsGroups = {
|
||||
'General Permissions': ['users_default', 'm.space.child'],
|
||||
'Manage members permissions': ['invite', 'kick', 'ban'],
|
||||
'Space profile permissions': ['m.room.avatar', 'm.room.name', 'm.room.topic'],
|
||||
'Settings permissions': ['state_default', 'm.room.canonical_alias', 'm.room.power_levels'],
|
||||
};
|
||||
|
||||
function RoomPermissions({ roomId }) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const room = mx.getRoom(roomId);
|
||||
const pLEvent = room.currentState.getStateEvents('m.room.power_levels')[0];
|
||||
const permissions = pLEvent.getContent();
|
||||
const canChangePermission = room.currentState.maySendStateEvent('m.room.power_levels', mx.getUserId());
|
||||
|
||||
return (
|
||||
<div className="room-permissions">
|
||||
{
|
||||
Object.keys(roomPermsGroups).map((groupKey) => {
|
||||
const groupedPermKeys = roomPermsGroups[groupKey];
|
||||
return (
|
||||
<div className="room-permissions__card" key={groupKey}>
|
||||
<MenuHeader>{groupKey}</MenuHeader>
|
||||
{
|
||||
groupedPermKeys.map((permKey) => {
|
||||
const permInfo = permissionsInfo[permKey];
|
||||
|
||||
let powerLevel = 0;
|
||||
let permValue = permInfo.parent
|
||||
? permissions[permInfo.parent][permKey]
|
||||
: permissions[permKey];
|
||||
|
||||
if (!permValue) permValue = permInfo.default;
|
||||
|
||||
if (typeof permValue === 'number') {
|
||||
powerLevel = permValue;
|
||||
} else if (permKey === 'notifications') {
|
||||
powerLevel = permValue.room || 50;
|
||||
}
|
||||
return (
|
||||
<SettingTile
|
||||
key={permKey}
|
||||
title={permInfo.name}
|
||||
content={<Text variant="b3">{permInfo.description}</Text>}
|
||||
options={(
|
||||
<Button
|
||||
iconSrc={canChangePermission ? ChevronBottomIC : null}
|
||||
>
|
||||
<Text variant="b2">
|
||||
{`${getPowerLabel(powerLevel) || 'Member'} - ${powerLevel}`}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
RoomPermissions.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default RoomPermissions;
|
11
src/app/molecules/room-permissions/RoomPermissions.scss
Normal file
11
src/app/molecules/room-permissions/RoomPermissions.scss
Normal file
|
@ -0,0 +1,11 @@
|
|||
.room-permissions {
|
||||
& .setting-tile {
|
||||
margin: 0 var(--sp-normal);
|
||||
margin-top: var(--sp-tight);
|
||||
padding-bottom: var(--sp-tight);
|
||||
border-bottom: 1px solid var(--bg-surface-border);
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,13 +7,13 @@ import Text from '../../atoms/text/Text';
|
|||
function SettingTile({ title, options, content }) {
|
||||
return (
|
||||
<div className="setting-tile">
|
||||
<div className="setting-tile__title__wrapper">
|
||||
<div className="setting-tile__content">
|
||||
<div className="setting-tile__title">
|
||||
<Text variant="b1">{title}</Text>
|
||||
</div>
|
||||
{options !== null && <div className="setting-tile__options">{options}</div>}
|
||||
{content}
|
||||
</div>
|
||||
{content !== null && <div className="setting-tile__content">{content}</div>}
|
||||
{options !== null && <div className="setting-tile__options">{options}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
@use '../../partials/dir';
|
||||
|
||||
.setting-tile {
|
||||
&__title__wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&__title {
|
||||
display: flex;
|
||||
&__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@include dir.side(margin, 0, var(--sp-normal));
|
||||
}
|
||||
&__title {
|
||||
margin-bottom: var(--sp-ultra-tight);
|
||||
}
|
||||
&__options {
|
||||
@include dir.side(margin, var(--sp-tight), 0);
|
||||
}
|
||||
}
|
|
@ -188,29 +188,29 @@ function EmojiBoard({ onSelect }) {
|
|||
|
||||
const [availableEmojis, setAvailableEmojis] = useState([]);
|
||||
|
||||
// This should be called whenever the room changes, so that we can switch out the emoji
|
||||
// for whatever packs are relevant to this room
|
||||
function updateAvailableEmoji(selectedRoomId) {
|
||||
// Retrieve the packs for the new room
|
||||
const packs = getRelevantPacks(
|
||||
initMatrix.matrixClient.getRoom(selectedRoomId),
|
||||
)
|
||||
// Remove packs that aren't marked as emoji packs
|
||||
.filter((pack) => pack.usage.indexOf('emoticon') !== -1)
|
||||
// Remove packs without emojis
|
||||
.filter((pack) => pack.getEmojis().length !== 0);
|
||||
|
||||
// Set an index for each pack so that we know where to jump when the user uses the nav
|
||||
for (let i = 0; i < packs.length; i += 1) {
|
||||
packs[i].packIndex = i;
|
||||
}
|
||||
|
||||
// Update the component state
|
||||
setAvailableEmojis(packs);
|
||||
}
|
||||
|
||||
// Register the above function as an event listener
|
||||
useEffect(() => {
|
||||
const updateAvailableEmoji = (selectedRoomId) => {
|
||||
if (!selectedRoomId) {
|
||||
setAvailableEmojis([]);
|
||||
return;
|
||||
}
|
||||
// Retrieve the packs for the new room
|
||||
// Remove packs that aren't marked as emoji packs
|
||||
// Remove packs without emojis
|
||||
const packs = getRelevantPacks(
|
||||
initMatrix.matrixClient.getRoom(selectedRoomId),
|
||||
)
|
||||
.filter((pack) => pack.usage.indexOf('emoticon') !== -1)
|
||||
.filter((pack) => pack.getEmojis().length !== 0);
|
||||
|
||||
// Set an index for each pack so that we know where to jump when the user uses the nav
|
||||
for (let i = 0; i < packs.length; i += 1) {
|
||||
packs[i].packIndex = i;
|
||||
}
|
||||
|
||||
setAvailableEmojis(packs);
|
||||
};
|
||||
|
||||
navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji);
|
||||
return () => {
|
||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji);
|
||||
|
|
|
@ -261,6 +261,7 @@ function ProfileViewer() {
|
|||
function renderProfile() {
|
||||
const member = room.getMember(userId) || mx.getUser(userId) || {};
|
||||
const avatarMxc = member.getMxcAvatarUrl?.() || member.avatarUrl;
|
||||
const powerLevel = member.powerLevel || 0;
|
||||
const canChangeRole = room.currentState.maySendEvent('m.room.power_levels', mx.getUserId());
|
||||
|
||||
return (
|
||||
|
@ -278,7 +279,9 @@ function ProfileViewer() {
|
|||
</div>
|
||||
<div className="profile-viewer__user__role">
|
||||
<Text variant="b3">Role</Text>
|
||||
<Button iconSrc={canChangeRole ? ChevronBottomIC : null}>{getPowerLabel(member.powerLevel) || 'Member'}</Button>
|
||||
<Button iconSrc={canChangeRole ? ChevronBottomIC : null}>
|
||||
{`${getPowerLabel(powerLevel) || 'Member'} - ${powerLevel}`}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<SessionInfo userId={userId} />
|
||||
|
|
|
@ -19,12 +19,12 @@ import RoomVisibility from '../../molecules/room-visibility/RoomVisibility';
|
|||
import RoomAliases from '../../molecules/room-aliases/RoomAliases';
|
||||
import RoomHistoryVisibility from '../../molecules/room-history-visibility/RoomHistoryVisibility';
|
||||
import RoomEncryption from '../../molecules/room-encryption/RoomEncryption';
|
||||
import RoomPermissions from '../../molecules/room-permissions/RoomPermissions';
|
||||
|
||||
import SettingsIC from '../../../../public/res/ic/outlined/settings.svg';
|
||||
import SearchIC from '../../../../public/res/ic/outlined/search.svg';
|
||||
import ShieldUserIC from '../../../../public/res/ic/outlined/shield-user.svg';
|
||||
import LockIC from '../../../../public/res/ic/outlined/lock.svg';
|
||||
import InfoIC from '../../../../public/res/ic/outlined/info.svg';
|
||||
import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg';
|
||||
import LeaveArrowIC from '../../../../public/res/ic/outlined/leave-arrow.svg';
|
||||
|
||||
|
@ -35,7 +35,6 @@ const tabText = {
|
|||
SEARCH: 'Search',
|
||||
PERMISSIONS: 'Permissions',
|
||||
SECURITY: 'Security',
|
||||
ADVANCED: 'Advanced',
|
||||
};
|
||||
|
||||
const tabItems = [{
|
||||
|
@ -54,10 +53,6 @@ const tabItems = [{
|
|||
iconSrc: LockIC,
|
||||
text: tabText.SECURITY,
|
||||
disabled: false,
|
||||
}, {
|
||||
iconSrc: InfoIC,
|
||||
text: tabText.ADVANCED,
|
||||
disabled: false,
|
||||
}];
|
||||
|
||||
function GeneralSettings({ roomId }) {
|
||||
|
@ -156,6 +151,7 @@ function RoomSettings({ roomId }) {
|
|||
/>
|
||||
<div className="room-settings__cards-wrapper">
|
||||
{selectedTab.text === tabText.GENERAL && <GeneralSettings roomId={roomId} />}
|
||||
{selectedTab.text === tabText.PERMISSIONS && <RoomPermissions roomId={roomId} />}
|
||||
{selectedTab.text === tabText.SECURITY && <SecuritySettings roomId={roomId} />}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -32,16 +32,20 @@
|
|||
padding: 0 var(--sp-normal);
|
||||
@include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
|
||||
}
|
||||
}
|
||||
|
||||
&__card {
|
||||
margin: var(--sp-normal) 0;
|
||||
background-color: var(--bg-surface);
|
||||
border-radius: var(--bo-radius);
|
||||
box-shadow: var(--bs-surface-border);
|
||||
overflow: hidden;
|
||||
.room-settings__card {
|
||||
margin: var(--sp-normal) 0;
|
||||
background-color: var(--bg-surface);
|
||||
border-radius: var(--bo-radius);
|
||||
box-shadow: var(--bs-surface-border);
|
||||
overflow: hidden;
|
||||
|
||||
& > .context-menu__header:first-child {
|
||||
margin-top: 2px;
|
||||
}
|
||||
& > .context-menu__header:first-child {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.room-settings .room-permissions__card {
|
||||
@extend .room-settings__card;
|
||||
}
|
|
@ -23,9 +23,6 @@
|
|||
margin-top: var(--sp-normal);
|
||||
border-bottom: 1px solid var(--bg-surface-border);
|
||||
padding-bottom: 16px;
|
||||
&__title__wrapper {
|
||||
margin-bottom: var(--sp-ultra-tight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -117,3 +117,12 @@ export function openSearch(term) {
|
|||
term,
|
||||
});
|
||||
}
|
||||
|
||||
export function openReusableContextMenu(placement, cords, render) {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.navigation.OPEN_REUSABLE_CONTEXT_MENU,
|
||||
placement,
|
||||
cords,
|
||||
render,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ const cons = {
|
|||
CLICK_REPLY_TO: 'CLICK_REPLY_TO',
|
||||
OPEN_SEARCH: 'OPEN_SEARCH',
|
||||
OPEN_ATTACHMENT_TYPE_SELECTOR: 'OPEN_ATTACHMENT_TYPE_SELECTOR',
|
||||
OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU',
|
||||
},
|
||||
room: {
|
||||
JOIN: 'JOIN',
|
||||
|
@ -80,6 +81,7 @@ const cons = {
|
|||
REPLY_TO_CLICKED: 'REPLY_TO_CLICKED',
|
||||
OPEN_ATTACHMENT_TYPE_SELECTOR: 'OPEN_ATTACHMENT_TYPE_SELECTOR',
|
||||
SEARCH_OPENED: 'SEARCH_OPENED',
|
||||
REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED',
|
||||
},
|
||||
roomList: {
|
||||
ROOMLIST_UPDATED: 'ROOMLIST_UPDATED',
|
||||
|
|
|
@ -73,6 +73,8 @@ class Navigation extends EventEmitter {
|
|||
this.emit(cons.events.navigation.SPACE_SELECTED, this.selectedSpaceId);
|
||||
},
|
||||
[cons.actions.navigation.SELECT_ROOM]: () => {
|
||||
if (this.selectedRoomId === action.roomId) return;
|
||||
|
||||
const prevSelectedRoomId = this.selectedRoomId;
|
||||
this.selectedRoomId = action.roomId;
|
||||
this.removeRecentRoom(prevSelectedRoomId);
|
||||
|
@ -152,6 +154,14 @@ class Navigation extends EventEmitter {
|
|||
action.term,
|
||||
);
|
||||
},
|
||||
[cons.actions.navigation.OPEN_REUSABLE_CONTEXT_MENU]: () => {
|
||||
this.emit(
|
||||
cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED,
|
||||
action.placement,
|
||||
action.cords,
|
||||
action.render,
|
||||
);
|
||||
},
|
||||
};
|
||||
actions[action.type]?.();
|
||||
}
|
||||
|
|
|
@ -48,11 +48,19 @@ class Settings extends EventEmitter {
|
|||
|
||||
setTheme(themeIndex) {
|
||||
const appBody = document.getElementById('appBody');
|
||||
|
||||
appBody.classList.remove('system-theme');
|
||||
this.themes.forEach((themeName) => {
|
||||
if (themeName === '') return;
|
||||
appBody.classList.remove(themeName);
|
||||
});
|
||||
if (this.themes[themeIndex] !== '') appBody.classList.add(this.themes[themeIndex]);
|
||||
// If use system theme is enabled
|
||||
// we will override current theme choice with system theme
|
||||
if (this.useSystemTheme) {
|
||||
appBody.classList.add('system-theme');
|
||||
} else if (this.themes[themeIndex] !== '') {
|
||||
appBody.classList.add(this.themes[themeIndex]);
|
||||
}
|
||||
setSettings('themeIndex', themeIndex);
|
||||
this.themeIndex = themeIndex;
|
||||
}
|
||||
|
@ -106,19 +114,9 @@ class Settings extends EventEmitter {
|
|||
const actions = {
|
||||
[cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => {
|
||||
this.useSystemTheme = !this.useSystemTheme;
|
||||
setSettings('useSystemTheme', this.useSystemTheme);
|
||||
const appBody = document.getElementById('appBody');
|
||||
|
||||
if (this.useSystemTheme) {
|
||||
appBody.classList.add('system-theme');
|
||||
this.themes.forEach((themeName) => {
|
||||
if (themeName === '') return;
|
||||
appBody.classList.remove(themeName);
|
||||
});
|
||||
} else {
|
||||
appBody.classList.remove('system-theme');
|
||||
this.setTheme(this.themeIndex);
|
||||
}
|
||||
setSettings('useSystemTheme', this.useSystemTheme);
|
||||
this.setTheme(this.themeIndex);
|
||||
|
||||
this.emit(cons.events.settings.SYSTEM_THEME_TOGGLED, this.useSystemTheme);
|
||||
},
|
||||
|
|
|
@ -21,11 +21,28 @@ export function isInSameDay(dt2, dt1) {
|
|||
);
|
||||
}
|
||||
|
||||
export function getEventCords(ev) {
|
||||
const boxInfo = ev.target.getBoundingClientRect();
|
||||
/**
|
||||
* @param {Event} ev
|
||||
* @param {string} [targetSelector] element selector for Element.matches([selector])
|
||||
*/
|
||||
export function getEventCords(ev, targetSelector) {
|
||||
let boxInfo;
|
||||
|
||||
const path = ev.nativeEvent.composedPath();
|
||||
const target = targetSelector
|
||||
? path.find((element) => element.matches?.(targetSelector))
|
||||
: null;
|
||||
if (target) {
|
||||
boxInfo = target.getBoundingClientRect();
|
||||
} else {
|
||||
boxInfo = ev.target.getBoundingClientRect();
|
||||
}
|
||||
|
||||
return {
|
||||
x: boxInfo.x,
|
||||
y: boxInfo.y,
|
||||
width: boxInfo.width,
|
||||
height: boxInfo.height,
|
||||
detail: ev.detail,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue