mirror of
https://github.com/mat-1/variance.git
synced 2025-08-02 15:26:04 +00:00
format everything with prettier
This commit is contained in:
parent
a080480f7a
commit
98b1edf123
195 changed files with 2419 additions and 2057 deletions
46
.eslintrc.js
46
.eslintrc.js
|
@ -4,15 +4,15 @@ module.exports = {
|
|||
es2021: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'airbnb',
|
||||
'prettier',
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
|
@ -20,40 +20,34 @@ module.exports = {
|
|||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: [
|
||||
'react',
|
||||
'@typescript-eslint'
|
||||
],
|
||||
plugins: ['react', '@typescript-eslint'],
|
||||
rules: {
|
||||
'linebreak-style': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
|
||||
"import/prefer-default-export": "off",
|
||||
"import/extensions": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
'import/prefer-default-export': 'off',
|
||||
'import/extensions': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
},
|
||||
],
|
||||
|
||||
'react/no-unstable-nested-components': [
|
||||
'react/no-unstable-nested-components': ['error', { allowAsProps: true }],
|
||||
'react/jsx-filename-extension': [
|
||||
'error',
|
||||
{ allowAsProps: true },
|
||||
],
|
||||
"react/jsx-filename-extension": [
|
||||
"error",
|
||||
{
|
||||
extensions: [".tsx", ".jsx"],
|
||||
extensions: ['.tsx', '.jsx'],
|
||||
},
|
||||
],
|
||||
|
||||
"react/require-default-props": "off",
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
'react/require-default-props': 'off',
|
||||
'react/jsx-props-no-spreading': 'off',
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
},
|
||||
};
|
||||
|
|
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
@ -16,7 +16,7 @@ body:
|
|||
attributes:
|
||||
label: "Describe the solution you'd like"
|
||||
description: A clear description of what change you would like
|
||||
placeholder: "I would like to..."
|
||||
placeholder: 'I would like to...'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
11
.github/renovate.json
vendored
11
.github/renovate.json
vendored
|
@ -1,15 +1,12 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
":dependencyDashboardApproval"
|
||||
],
|
||||
"labels": [ "Dependencies" ],
|
||||
"extends": ["config:base", ":dependencyDashboardApproval"],
|
||||
"labels": ["Dependencies"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": [ "lockFileMaintenance" ]
|
||||
"matchUpdateTypes": ["lockFileMaintenance"]
|
||||
}
|
||||
],
|
||||
"lockFileMaintenance": { "enabled": true },
|
||||
"dependencyDashboard": true
|
||||
}
|
||||
}
|
||||
|
|
10
.github/workflows/deploy-pull-request.yml
vendored
10
.github/workflows/deploy-pull-request.yml
vendored
|
@ -2,8 +2,8 @@ name: Deploy PR to Netlify
|
|||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Build pull request"]
|
||||
types: [completed]
|
||||
workflows: ['Build pull request']
|
||||
types: [completed]
|
||||
|
||||
jobs:
|
||||
deploy-pull-request:
|
||||
|
@ -35,7 +35,7 @@ jobs:
|
|||
uses: nwtgck/actions-netlify@5da65c9f74c7961c5501a3ba329b8d0912f39c03
|
||||
with:
|
||||
publish-dir: dist
|
||||
deploy-message: "Deploy PR ${{ steps.pr.outputs.id }}"
|
||||
deploy-message: 'Deploy PR ${{ steps.pr.outputs.id }}'
|
||||
alias: ${{ steps.pr.outputs.id }}
|
||||
# These don't work because we're in workflow_run
|
||||
enable-pull-request-comment: false
|
||||
|
@ -52,5 +52,5 @@ jobs:
|
|||
pr_number: ${{ steps.pr.outputs.id }}
|
||||
comment_tag: ${{ steps.pr.outputs.id }}
|
||||
message: |
|
||||
Preview: ${{ steps.netlify.outputs.deploy-url }}
|
||||
⚠️ Exercise caution. Use test accounts. ⚠️
|
||||
Preview: ${{ steps.netlify.outputs.deploy-url }}
|
||||
⚠️ Exercise caution. Use test accounts. ⚠️
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
{
|
||||
"defaultHomeserver": 3,
|
||||
"homeserverList": [
|
||||
"converser.eu",
|
||||
"envs.net",
|
||||
"halogen.city",
|
||||
"matrix.org",
|
||||
"mozilla.org"
|
||||
],
|
||||
"homeserverList": ["converser.eu", "envs.net", "halogen.city", "matrix.org", "mozilla.org"],
|
||||
"allowCustomHomeservers": true
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Variance</title>
|
||||
<meta name="name" content="Variance" />
|
||||
<meta name="author" content="Ajay Bura" />
|
||||
<meta name="author" content="Cinny and Variance contributors" />
|
||||
<meta
|
||||
name="description"
|
||||
content="A Matrix client that aims to be user-friendly and provide an experience similar to Discord."
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
.avatar__border {
|
||||
@extend .cp-fx__row--c-c;
|
||||
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -53,4 +53,4 @@
|
|||
box-shadow: var(--bs-surface-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ const Avatar = React.forwardRef(({ text, bgColor, iconSrc, iconColor, imageSrc,
|
|||
const { onlyAnimateOnHover } = settings;
|
||||
|
||||
const [activeImageSrc, setActiveImageSrc] = React.useState(
|
||||
onlyAnimateOnHover ? pausedImageSrc : imageSrc
|
||||
onlyAnimateOnHover ? pausedImageSrc : imageSrc,
|
||||
);
|
||||
|
||||
// also update when imageSrc changes
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { avatarInitials, cssVar } from '../../../util/common';
|
||||
|
||||
// renders the avatar and returns it as an URL
|
||||
export default async function renderAvatar({
|
||||
text, bgColor, imageSrc, size, borderRadius, scale,
|
||||
}) {
|
||||
export default async function renderAvatar({ text, bgColor, imageSrc, size, borderRadius, scale }) {
|
||||
try {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = size * scale;
|
||||
|
|
|
@ -18,4 +18,4 @@
|
|||
min-width: 8px;
|
||||
margin: 0 var(--sp-ultra-tight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,11 @@ function NotificationBadge({ alert, content }) {
|
|||
const notificationClass = alert ? ' notification-badge--alert' : '';
|
||||
return (
|
||||
<div className={`notification-badge${notificationClass}`}>
|
||||
{content !== null && <Text variant="b3" weight="bold">{content}</Text>}
|
||||
{content !== null && (
|
||||
<Text variant="b3" weight="bold">
|
||||
{content}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -20,10 +24,7 @@ NotificationBadge.defaultProps = {
|
|||
|
||||
NotificationBadge.propTypes = {
|
||||
alert: PropTypes.bool,
|
||||
content: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
content: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
export default NotificationBadge;
|
||||
|
|
|
@ -22,10 +22,9 @@
|
|||
& .text {
|
||||
@extend .cp-txt__ellipsis;
|
||||
}
|
||||
|
||||
|
||||
&--icon {
|
||||
@include dir.side(padding, var(--sp-tight), var(--sp-loose));
|
||||
|
||||
}
|
||||
.ic-raw {
|
||||
@include dir.side(margin, 0, var(--sp-extra-tight));
|
||||
|
@ -42,7 +41,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.btn-surface {
|
||||
box-shadow: var(--bs-surface-border);
|
||||
@include color(var(--tc-surface-high), var(--ic-surface-normal));
|
||||
|
@ -78,4 +76,4 @@
|
|||
@include state.hover(var(--bg-danger-hover));
|
||||
@include state.focus(var(--bs-danger-outline));
|
||||
@include state.active(var(--bg-danger-active));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,28 +6,27 @@ import Text from '../text/Text';
|
|||
import RawIcon from '../system-icons/RawIcon';
|
||||
import { blurOnBubbling } from './script';
|
||||
|
||||
const Button = React.forwardRef(({
|
||||
id, className, variant, iconSrc,
|
||||
type, onClick, children, disabled,
|
||||
}, ref) => {
|
||||
const iconClass = (iconSrc === null) ? '' : `btn-${variant}--icon`;
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
id={id === '' ? undefined : id}
|
||||
className={`${className ? `${className} ` : ''}btn-${variant} ${iconClass} noselect`}
|
||||
onMouseUp={(e) => blurOnBubbling(e, `.btn-${variant}`)}
|
||||
onClick={onClick}
|
||||
// eslint-disable-next-line react/button-has-type
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
>
|
||||
{iconSrc !== null && <RawIcon size="small" src={iconSrc} />}
|
||||
{typeof children === 'string' && <Text variant="b1">{ children }</Text>}
|
||||
{typeof children !== 'string' && children }
|
||||
</button>
|
||||
);
|
||||
});
|
||||
const Button = React.forwardRef(
|
||||
({ id, className, variant, iconSrc, type, onClick, children, disabled }, ref) => {
|
||||
const iconClass = iconSrc === null ? '' : `btn-${variant}--icon`;
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
id={id === '' ? undefined : id}
|
||||
className={`${className ? `${className} ` : ''}btn-${variant} ${iconClass} noselect`}
|
||||
onMouseUp={(e) => blurOnBubbling(e, `.btn-${variant}`)}
|
||||
onClick={onClick}
|
||||
// eslint-disable-next-line react/button-has-type
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
>
|
||||
{iconSrc !== null && <RawIcon size="small" src={iconSrc} />}
|
||||
{typeof children === 'string' && <Text variant="b1">{children}</Text>}
|
||||
{typeof children !== 'string' && children}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Button.defaultProps = {
|
||||
id: '',
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
&--active {
|
||||
background-color: black;
|
||||
&::before {
|
||||
content: "";
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 6px;
|
||||
|
@ -36,4 +36,4 @@
|
|||
}
|
||||
.checkbox-danger.checkbox--active {
|
||||
background-color: var(--bg-danger);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './Checkbox.scss';
|
||||
|
||||
function Checkbox({
|
||||
variant, isActive, onToggle,
|
||||
disabled, tabIndex,
|
||||
}) {
|
||||
function Checkbox({ variant, isActive, onToggle, disabled, tabIndex }) {
|
||||
const className = `checkbox checkbox-${variant}${isActive ? ' checkbox--active' : ''}`;
|
||||
if (onToggle === null) return <span className={className} />;
|
||||
return (
|
||||
|
|
|
@ -53,4 +53,4 @@
|
|||
@include state.hover(var(--bg-danger-hover));
|
||||
@include focus(var(--bg-danger-hover));
|
||||
@include state.active(var(--bg-danger-active));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,36 +7,45 @@ import Tooltip from '../tooltip/Tooltip';
|
|||
import { blurOnBubbling } from './script';
|
||||
import Text from '../text/Text';
|
||||
|
||||
const IconButton = React.forwardRef(({
|
||||
variant, size, type,
|
||||
tooltip, tooltipPlacement, src,
|
||||
onClick, tabIndex, disabled, isImage,
|
||||
className,
|
||||
}, ref) => {
|
||||
const btn = (
|
||||
<button
|
||||
ref={ref}
|
||||
className={`ic-btn ic-btn-${variant} ${className}`}
|
||||
onMouseUp={(e) => blurOnBubbling(e, `.ic-btn-${variant}`)}
|
||||
onClick={onClick}
|
||||
// eslint-disable-next-line react/button-has-type
|
||||
type={type}
|
||||
tabIndex={tabIndex}
|
||||
disabled={disabled}
|
||||
>
|
||||
<RawIcon size={size} src={src} isImage={isImage} />
|
||||
</button>
|
||||
);
|
||||
if (tooltip === null) return btn;
|
||||
return (
|
||||
<Tooltip
|
||||
placement={tooltipPlacement}
|
||||
content={<Text variant="b2">{tooltip}</Text>}
|
||||
>
|
||||
{btn}
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
const IconButton = React.forwardRef(
|
||||
(
|
||||
{
|
||||
variant,
|
||||
size,
|
||||
type,
|
||||
tooltip,
|
||||
tooltipPlacement,
|
||||
src,
|
||||
onClick,
|
||||
tabIndex,
|
||||
disabled,
|
||||
isImage,
|
||||
className,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const btn = (
|
||||
<button
|
||||
ref={ref}
|
||||
className={`ic-btn ic-btn-${variant} ${className}`}
|
||||
onMouseUp={(e) => blurOnBubbling(e, `.ic-btn-${variant}`)}
|
||||
onClick={onClick}
|
||||
// eslint-disable-next-line react/button-has-type
|
||||
type={type}
|
||||
tabIndex={tabIndex}
|
||||
disabled={disabled}
|
||||
>
|
||||
<RawIcon size={size} src={src} isImage={isImage} />
|
||||
</button>
|
||||
);
|
||||
if (tooltip === null) return btn;
|
||||
return (
|
||||
<Tooltip placement={tooltipPlacement} content={<Text variant="b2">{tooltip}</Text>}>
|
||||
{btn}
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
IconButton.defaultProps = {
|
||||
variant: 'surface',
|
||||
|
|
|
@ -25,4 +25,4 @@
|
|||
background-color: var(--bg-positive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
|
|||
import './RadioButton.scss';
|
||||
|
||||
function RadioButton({ isActive, onToggle, disabled }) {
|
||||
if (onToggle === null) return <span className={`radio-btn${isActive ? ' radio-btn--active' : ''}`} />;
|
||||
if (onToggle === null)
|
||||
return <span className={`radio-btn${isActive ? ' radio-btn--active' : ''}`} />;
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/control-has-associated-label
|
||||
<button
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
height: 16px;
|
||||
background-color: var(--tc-surface-low);
|
||||
border-radius: calc(var(--bo-radius) / 2);
|
||||
transition: transform 200ms ease-in-out,
|
||||
transition:
|
||||
transform 200ms ease-in-out,
|
||||
opacity 200ms ease-in-out;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
@ -40,4 +41,4 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
@mixin hover($color) {
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
|
@ -22,4 +21,4 @@
|
|||
opacity: 0.4;
|
||||
cursor: no-drop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
&--surface {
|
||||
border-color: var(--bg-surface-border);
|
||||
background-color: var(--bg-surface-hover);
|
||||
|
||||
}
|
||||
&--primary {
|
||||
border-color: var(--bg-primary);
|
||||
|
@ -76,4 +75,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,7 @@ import IconButton from '../button/IconButton';
|
|||
|
||||
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||
|
||||
function InfoCard({
|
||||
className, style,
|
||||
variant, iconSrc,
|
||||
title, content,
|
||||
rounded, requestClose,
|
||||
}) {
|
||||
function InfoCard({ className, style, variant, iconSrc, title, content, rounded, requestClose }) {
|
||||
const classes = [`info-card info-card--${variant}`];
|
||||
if (rounded) classes.push('info-card--rounded');
|
||||
if (className) classes.push(className);
|
||||
|
@ -28,9 +23,7 @@ function InfoCard({
|
|||
<Text>{title}</Text>
|
||||
{content}
|
||||
</div>
|
||||
{requestClose && (
|
||||
<IconButton src={CrossIC} variant={variant} onClick={requestClose} />
|
||||
)}
|
||||
{requestClose && <IconButton src={CrossIC} variant={variant} onClick={requestClose} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
.chip {
|
||||
padding: var(--sp-ultra-tight) var(--sp-extra-tight);
|
||||
|
||||
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
|
||||
background: var(--bg-surface-low);
|
||||
border-radius: var(--bo-radius);
|
||||
box-shadow: var(--bs-surface-border);
|
||||
|
@ -28,4 +28,4 @@
|
|||
height: 16px;
|
||||
@include dir.side(margin, 0, var(--sp-ultra-tight));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,11 @@ import './Chip.scss';
|
|||
import Text from '../text/Text';
|
||||
import RawIcon from '../system-icons/RawIcon';
|
||||
|
||||
function Chip({
|
||||
iconSrc, iconColor, text, children,
|
||||
onClick,
|
||||
}) {
|
||||
function Chip({ iconSrc, iconColor, text, children, onClick }) {
|
||||
return (
|
||||
<button className="chip" type="button" onClick={onClick}>
|
||||
{iconSrc != null && <RawIcon src={iconSrc} color={iconColor} size="extra-small" />}
|
||||
{(text != null && text !== '') && <Text variant="b3">{text}</Text>}
|
||||
{text != null && text !== '' && <Text variant="b3">{text}</Text>}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
.context-menu__item {
|
||||
display: flex;
|
||||
button[class^="btn"] {
|
||||
button[class^='btn'] {
|
||||
@extend .cp-fx__item-one;
|
||||
justify-content: flex-start;
|
||||
border-radius: 0;
|
||||
|
@ -59,11 +59,7 @@
|
|||
|
||||
// if item doesn't have icon
|
||||
.text:first-child {
|
||||
@include dir.side(
|
||||
margin,
|
||||
calc(var(--ic-small) + var(--sp-tight)),
|
||||
0
|
||||
);
|
||||
@include dir.side(margin, calc(var(--ic-small) + var(--sp-tight)), 0);
|
||||
}
|
||||
}
|
||||
.btn-surface:focus {
|
||||
|
@ -78,4 +74,4 @@
|
|||
.btn-danger:focus {
|
||||
background-color: var(--bg-danger-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,7 @@ import Text from '../text/Text';
|
|||
import Button from '../button/Button';
|
||||
import ScrollView from '../scroll/ScrollView';
|
||||
|
||||
function ContextMenu({
|
||||
content, placement, maxWidth, render, afterToggle,
|
||||
}) {
|
||||
function ContextMenu({ content, placement, maxWidth, render, afterToggle }) {
|
||||
const [isVisible, setVisibility] = useState(false);
|
||||
const showMenu = () => setVisibility(true);
|
||||
const hideMenu = () => setVisibility(false);
|
||||
|
@ -26,7 +24,11 @@ function ContextMenu({
|
|||
className="context-menu"
|
||||
visible={isVisible}
|
||||
onClickOutside={hideMenu}
|
||||
content={<ScrollView invisible>{typeof content === 'function' ? content(hideMenu) : content}</ScrollView>}
|
||||
content={
|
||||
<ScrollView invisible>
|
||||
{typeof content === 'function' ? content(hideMenu) : content}
|
||||
</ScrollView>
|
||||
}
|
||||
placement={placement}
|
||||
interactive
|
||||
arrow={false}
|
||||
|
@ -45,15 +47,9 @@ ContextMenu.defaultProps = {
|
|||
};
|
||||
|
||||
ContextMenu.propTypes = {
|
||||
content: PropTypes.oneOfType([
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]).isRequired,
|
||||
content: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
|
||||
placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
|
||||
maxWidth: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
maxWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
render: PropTypes.func.isRequired,
|
||||
afterToggle: PropTypes.func,
|
||||
};
|
||||
|
@ -61,7 +57,7 @@ ContextMenu.propTypes = {
|
|||
function MenuHeader({ children }) {
|
||||
return (
|
||||
<div className="context-menu__header">
|
||||
<Text variant="b3">{ children }</Text>
|
||||
<Text variant="b3">{children}</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -70,20 +66,11 @@ MenuHeader.propTypes = {
|
|||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
function MenuItem({
|
||||
variant, iconSrc, type,
|
||||
onClick, children, disabled,
|
||||
}) {
|
||||
function MenuItem({ variant, iconSrc, type, onClick, children, disabled }) {
|
||||
return (
|
||||
<div className="context-menu__item">
|
||||
<Button
|
||||
variant={variant}
|
||||
iconSrc={iconSrc}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{ children }
|
||||
<Button variant={variant} iconSrc={iconSrc} type={type} onClick={onClick} disabled={disabled}>
|
||||
{children}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
@ -110,6 +97,4 @@ function MenuBorder() {
|
|||
return <div style={{ borderBottom: '1px solid var(--bg-surface-border)' }}> </div>;
|
||||
}
|
||||
|
||||
export {
|
||||
ContextMenu as default, MenuHeader, MenuItem, MenuBorder,
|
||||
};
|
||||
export { ContextMenu as default, MenuHeader, MenuItem, MenuBorder };
|
||||
|
|
|
@ -29,7 +29,10 @@ function ReusableContextMenu() {
|
|||
return;
|
||||
}
|
||||
setData({
|
||||
placement, cords, render, afterClose,
|
||||
placement,
|
||||
cords,
|
||||
render,
|
||||
afterClose,
|
||||
});
|
||||
};
|
||||
navigation.on(cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, handleContextMenuOpen);
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
.divider--primary {
|
||||
--local-divider-color: var(--bg-primary);
|
||||
--local-divider-opacity: .8;
|
||||
--local-divider-opacity: 0.8;
|
||||
.divider__text {
|
||||
color: var(--tc-primary-high);
|
||||
background-color: var(--bg-primary);
|
||||
|
@ -44,7 +44,7 @@
|
|||
}
|
||||
.divider--positive {
|
||||
--local-divider-color: var(--bg-positive);
|
||||
--local-divider-opacity: .8;
|
||||
--local-divider-opacity: 0.8;
|
||||
.divider__text {
|
||||
color: var(--bg-surface);
|
||||
background-color: var(--bg-positive);
|
||||
|
@ -52,7 +52,7 @@
|
|||
}
|
||||
.divider--danger {
|
||||
--local-divider-color: var(--bg-danger);
|
||||
--local-divider-opacity: .8;
|
||||
--local-divider-opacity: 0.8;
|
||||
.divider__text {
|
||||
color: var(--bg-surface);
|
||||
background-color: var(--bg-danger);
|
||||
|
@ -60,7 +60,7 @@
|
|||
}
|
||||
.divider--caution {
|
||||
--local-divider-color: var(--bg-caution);
|
||||
--local-divider-opacity: .8;
|
||||
--local-divider-opacity: 0.8;
|
||||
.divider__text {
|
||||
color: var(--bg-surface);
|
||||
background-color: var(--bg-caution);
|
||||
|
|
|
@ -8,7 +8,11 @@ function Divider({ text, variant, align }) {
|
|||
const dividerClass = ` divider--${variant} divider--${align}`;
|
||||
return (
|
||||
<div className={`divider${dividerClass}`}>
|
||||
{text !== null && <Text className="divider__text" variant="b3" weight="bold">{text}</Text>}
|
||||
{text !== null && (
|
||||
<Text className="divider__text" variant="b3" weight="bold">
|
||||
{text}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 var(--sp-tight);
|
||||
|
||||
|
||||
&:first-child {
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
|||
@extend .cp-txt__ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
& > .text-b3{
|
||||
& > .text-b3 {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
|
@ -40,4 +40,4 @@
|
|||
display: -webkit-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import './Header.scss';
|
||||
|
||||
function Header({ children }) {
|
||||
return (
|
||||
<div className="header">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return <div className="header">{children}</div>;
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
|
@ -15,11 +11,7 @@ Header.propTypes = {
|
|||
};
|
||||
|
||||
function TitleWrapper({ children }) {
|
||||
return (
|
||||
<div className="header__title-wrapper">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return <div className="header__title-wrapper">{children}</div>;
|
||||
}
|
||||
|
||||
TitleWrapper.propTypes = {
|
||||
|
|
|
@ -47,6 +47,6 @@
|
|||
box-shadow: var(--bs-primary-border);
|
||||
}
|
||||
&::placeholder {
|
||||
color: var(--tc-surface-low)
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,7 @@ import 'katex/dist/katex.min.css';
|
|||
|
||||
import 'katex/dist/contrib/copy-tex';
|
||||
|
||||
const Math = React.memo(({
|
||||
content, throwOnError, errorColor, displayMode,
|
||||
}) => {
|
||||
const Math = React.memo(({ content, throwOnError, errorColor, displayMode }) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
@keyframes raw-modal--content {
|
||||
0% {
|
||||
transform: translateY(100px);
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
|
|
|
@ -9,11 +9,17 @@ import navigation from '../../../client/state/navigation';
|
|||
Modal.setAppElement('#root');
|
||||
|
||||
function RawModal({
|
||||
className, overlayClassName,
|
||||
isOpen, size, onAfterOpen, onAfterClose,
|
||||
onRequestClose, closeFromOutside, children,
|
||||
className,
|
||||
overlayClassName,
|
||||
isOpen,
|
||||
size,
|
||||
onAfterOpen,
|
||||
onAfterClose,
|
||||
onRequestClose,
|
||||
closeFromOutside,
|
||||
children,
|
||||
}) {
|
||||
let modalClass = (className !== null) ? `${className} ` : '';
|
||||
let modalClass = className !== null ? `${className} ` : '';
|
||||
switch (size) {
|
||||
case 'large':
|
||||
modalClass += 'raw-modal__large ';
|
||||
|
@ -30,7 +36,7 @@ function RawModal({
|
|||
navigation.setIsRawModalVisible(isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
const modalOverlayClass = (overlayClassName !== null) ? `${overlayClassName} ` : '';
|
||||
const modalOverlayClass = overlayClassName !== null ? `${overlayClassName} ` : '';
|
||||
return (
|
||||
<Modal
|
||||
className={`${modalClass}raw-modal`}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
@use '_scrollbar';
|
||||
|
||||
@mixin paddingForSafari($padding) {
|
||||
@media not all and (min-resolution:.001dpcm) {
|
||||
@media not all and (min-resolution: 0.001dpcm) {
|
||||
@include dir.side(padding, 0, $padding);
|
||||
}
|
||||
}
|
||||
|
@ -28,4 +28,4 @@
|
|||
@include scrollbar.scroll--invisible;
|
||||
@include paddingForSafari(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,4 +62,4 @@
|
|||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,18 +40,22 @@
|
|||
& + .segment-btn .segment-btn__base {
|
||||
border: none;
|
||||
}
|
||||
&:first-child{
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
[dir=rtl] & {
|
||||
[dir='rtl'] & {
|
||||
border-left: 1px solid var(--bg-surface-border);
|
||||
border-right: 1px solid var(--bg-surface-border);
|
||||
|
||||
&:first-child { border-right: none;}
|
||||
&:last-child { border-left: none;}
|
||||
&:first-child {
|
||||
border-right: none;
|
||||
}
|
||||
&:last-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,7 @@ import { blurOnBubbling } from '../button/script';
|
|||
import Text from '../text/Text';
|
||||
import RawIcon from '../system-icons/RawIcon';
|
||||
|
||||
function SegmentedControls({
|
||||
selected, segments, onSelect,
|
||||
}) {
|
||||
function SegmentedControls({ selected, segments, onSelect }) {
|
||||
const [select, setSelect] = useState(selected);
|
||||
|
||||
function selectSegment(segmentIndex) {
|
||||
|
@ -23,32 +21,32 @@ function SegmentedControls({
|
|||
|
||||
return (
|
||||
<div className="segmented-controls">
|
||||
{
|
||||
segments.map((segment, index) => (
|
||||
<button
|
||||
key={Math.random().toString(20).substr(2, 6)}
|
||||
className={`segment-btn${select === index ? ' segment-btn--active' : ''}`}
|
||||
type="button"
|
||||
onClick={() => selectSegment(index)}
|
||||
onMouseUp={(e) => blurOnBubbling(e, '.segment-btn')}
|
||||
>
|
||||
<div className="segment-btn__base">
|
||||
{segment.iconSrc && <RawIcon size="small" src={segment.iconSrc} />}
|
||||
{segment.text && <Text variant="b2">{segment.text}</Text>}
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
{segments.map((segment, index) => (
|
||||
<button
|
||||
key={Math.random().toString(20).substr(2, 6)}
|
||||
className={`segment-btn${select === index ? ' segment-btn--active' : ''}`}
|
||||
type="button"
|
||||
onClick={() => selectSegment(index)}
|
||||
onMouseUp={(e) => blurOnBubbling(e, '.segment-btn')}
|
||||
>
|
||||
<div className="segment-btn__base">
|
||||
{segment.iconSrc && <RawIcon size="small" src={segment.iconSrc} />}
|
||||
{segment.text && <Text variant="b2">{segment.text}</Text>}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SegmentedControls.propTypes = {
|
||||
selected: PropTypes.number.isRequired,
|
||||
segments: PropTypes.arrayOf(PropTypes.shape({
|
||||
iconSrc: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
})).isRequired,
|
||||
segments: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
iconSrc: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
}),
|
||||
).isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -19,4 +19,4 @@
|
|||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import './Spinner.scss';
|
||||
|
||||
function Spinner({ size }) {
|
||||
return (
|
||||
<div className={`donut-spinner donut-spinner--${size}`}> </div>
|
||||
);
|
||||
return <div className={`donut-spinner donut-spinner--${size}`}> </div>;
|
||||
}
|
||||
|
||||
Spinner.defaultProps = {
|
||||
|
|
|
@ -25,4 +25,4 @@
|
|||
}
|
||||
.ic-raw-extra-small {
|
||||
@include icSize(var(--ic-extra-small));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
|
||||
.tab-item {
|
||||
flex-shrink: 0;
|
||||
|
||||
|
||||
@include dir.side(padding, var(--sp-normal), 24px);
|
||||
border-radius: 0;
|
||||
height: 100%;
|
||||
box-shadow: none;
|
||||
border-radius: var(--bo-radius) var(--bo-radius) 0 0;
|
||||
border-radius: var(--bo-radius) var(--bo-radius) 0 0;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
|
@ -42,4 +42,4 @@
|
|||
box-shadow: var(--bs-tab-selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,7 @@ import './Tabs.scss';
|
|||
import Button from '../button/Button';
|
||||
import ScrollView from '../scroll/ScrollView';
|
||||
|
||||
function TabItem({
|
||||
selected, iconSrc,
|
||||
onClick, children, disabled,
|
||||
}) {
|
||||
function TabItem({ selected, iconSrc, onClick, children, disabled }) {
|
||||
const isSelected = selected ? 'tab-item--selected' : '';
|
||||
|
||||
return (
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
& img.emoji,
|
||||
& img[data-mx-emoticon] {
|
||||
height: calc(var(--lh-#{$type}) - .25rem);
|
||||
height: calc(var(--lh-#{$type}) - 0.25rem);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
|||
margin-right: 2px !important;
|
||||
padding: 0 !important;
|
||||
position: relative;
|
||||
top: -.1rem;
|
||||
top: -0.1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
@ -58,4 +58,4 @@
|
|||
.text-b3 {
|
||||
@include font(b3);
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import './Text.scss';
|
||||
|
||||
function Text({
|
||||
className, style, variant, weight,
|
||||
primary, span, children,
|
||||
}) {
|
||||
function Text({ className, style, variant, weight, primary, span, children }) {
|
||||
const classes = [];
|
||||
if (className) classes.push(className);
|
||||
|
||||
|
@ -13,11 +10,35 @@ function Text({
|
|||
if (primary) classes.push('font-primary');
|
||||
|
||||
const textClass = classes.join(' ');
|
||||
if (span) return <span className={textClass} style={style}>{ children }</span>;
|
||||
if (variant === 'h1') return <h1 className={textClass} style={style}>{ children }</h1>;
|
||||
if (variant === 'h2') return <h2 className={textClass} style={style}>{ children }</h2>;
|
||||
if (variant === 's1') return <h4 className={textClass} style={style}>{ children }</h4>;
|
||||
return <p className={textClass} style={style}>{ children }</p>;
|
||||
if (span)
|
||||
return (
|
||||
<span className={textClass} style={style}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
if (variant === 'h1')
|
||||
return (
|
||||
<h1 className={textClass} style={style}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
if (variant === 'h2')
|
||||
return (
|
||||
<h2 className={textClass} style={style}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
if (variant === 's1')
|
||||
return (
|
||||
<h4 className={textClass} style={style}>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
return (
|
||||
<p className={textClass} style={style}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
Text.defaultProps = {
|
||||
|
|
|
@ -23,10 +23,7 @@ function Time({ timestamp, fullTime }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<time
|
||||
dateTime={date.toISOString()}
|
||||
title={formattedFullTime}
|
||||
>
|
||||
<time dateTime={date.toISOString()} title={formattedFullTime}>
|
||||
{formattedDate}
|
||||
</time>
|
||||
);
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
.text {
|
||||
color: var(--tc-tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import './Tooltip.scss';
|
||||
import Tippy from '@tippyjs/react';
|
||||
|
||||
function Tooltip({
|
||||
className, placement, content, delay, children,
|
||||
}) {
|
||||
function Tooltip({ className, placement, content, delay, children }) {
|
||||
return (
|
||||
<Tippy
|
||||
content={content}
|
||||
|
|
|
@ -10,10 +10,11 @@ export function useDeviceList() {
|
|||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const updateDevices = () => mx.getDevices().then((data) => {
|
||||
if (!isMounted) return;
|
||||
setDeviceList(data.devices || []);
|
||||
});
|
||||
const updateDevices = () =>
|
||||
mx.getDevices().then((data) => {
|
||||
if (!isMounted) return;
|
||||
setDeviceList(data.devices || []);
|
||||
});
|
||||
updateDevices();
|
||||
|
||||
const handleDevicesUpdate = (users) => {
|
||||
|
|
|
@ -4,7 +4,10 @@ import { useState } from 'react';
|
|||
export function useForceUpdate() {
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
return [data, function forceUpdateHook() {
|
||||
setData({});
|
||||
}];
|
||||
return [
|
||||
data,
|
||||
function forceUpdateHook() {
|
||||
setData({});
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
display: flex;
|
||||
gap: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,14 @@ import { openReusableDialog } from '../../../client/action/navigation';
|
|||
import Text from '../../atoms/text/Text';
|
||||
import Button from '../../atoms/button/Button';
|
||||
|
||||
function ConfirmDialog({
|
||||
desc, actionTitle, actionType, onComplete,
|
||||
}) {
|
||||
function ConfirmDialog({ desc, actionTitle, actionType, onComplete }) {
|
||||
return (
|
||||
<div className="confirm-dialog">
|
||||
<Text>{desc}</Text>
|
||||
<div className="confirm-dialog__btn">
|
||||
<Button variant={actionType} onClick={() => onComplete(true)}>{actionTitle}</Button>
|
||||
<Button variant={actionType} onClick={() => onComplete(true)}>
|
||||
{actionTitle}
|
||||
</Button>
|
||||
<Button onClick={() => onComplete(false)}>Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,24 +35,27 @@ ConfirmDialog.propTypes = {
|
|||
* @return {Promise<boolean>} does it get's confirmed or not
|
||||
*/
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const confirmDialog = (title, desc, actionTitle, actionType = 'primary') => new Promise((resolve) => {
|
||||
let isCompleted = false;
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">{title}</Text>,
|
||||
(requestClose) => (
|
||||
<ConfirmDialog
|
||||
desc={desc}
|
||||
actionTitle={actionTitle}
|
||||
actionType={actionType}
|
||||
onComplete={(isConfirmed) => {
|
||||
isCompleted = true;
|
||||
resolve(isConfirmed);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
() => {
|
||||
if (!isCompleted) resolve(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
export const confirmDialog = (title, desc, actionTitle, actionType = 'primary') =>
|
||||
new Promise((resolve) => {
|
||||
let isCompleted = false;
|
||||
openReusableDialog(
|
||||
<Text variant="s1" weight="medium">
|
||||
{title}
|
||||
</Text>,
|
||||
(requestClose) => (
|
||||
<ConfirmDialog
|
||||
desc={desc}
|
||||
actionTitle={actionTitle}
|
||||
actionType={actionType}
|
||||
onComplete={(isConfirmed) => {
|
||||
isCompleted = true;
|
||||
resolve(isConfirmed);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
() => {
|
||||
if (!isCompleted) resolve(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
@ -10,8 +10,15 @@ import ScrollView from '../../atoms/scroll/ScrollView';
|
|||
import RawModal from '../../atoms/modal/RawModal';
|
||||
|
||||
function Dialog({
|
||||
className, isOpen, title, onAfterOpen, onAfterClose,
|
||||
contentOptions, onRequestClose, closeFromOutside, children,
|
||||
className,
|
||||
isOpen,
|
||||
title,
|
||||
onAfterOpen,
|
||||
onAfterClose,
|
||||
contentOptions,
|
||||
onRequestClose,
|
||||
closeFromOutside,
|
||||
children,
|
||||
invisibleScroll,
|
||||
}) {
|
||||
return (
|
||||
|
@ -28,19 +35,19 @@ function Dialog({
|
|||
<div className="dialog__content">
|
||||
<Header>
|
||||
<TitleWrapper>
|
||||
{
|
||||
typeof title === 'string'
|
||||
? <Text variant="h2" weight="medium" primary>{twemojify(title)}</Text>
|
||||
: title
|
||||
}
|
||||
{typeof title === 'string' ? (
|
||||
<Text variant="h2" weight="medium" primary>
|
||||
{twemojify(title)}
|
||||
</Text>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</TitleWrapper>
|
||||
{contentOptions}
|
||||
</Header>
|
||||
<div className="dialog__content__wrapper">
|
||||
<ScrollView autoHide={!invisibleScroll} invisible={invisibleScroll}>
|
||||
<div className="dialog__content-container">
|
||||
{children}
|
||||
</div>
|
||||
<div className="dialog__content-container">{children}</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
& .ic-raw {
|
||||
min-width: var(--ic-extra-small);
|
||||
opacity: 0.4;
|
||||
|
@ -28,4 +28,4 @@
|
|||
&:active {
|
||||
background-color: var(--bg-surface-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,18 +37,19 @@ function FollowingMembers({ roomTimeline }) {
|
|||
|
||||
const filteredM = followingMembers.filter((userId) => userId !== myUserId);
|
||||
|
||||
return filteredM.length !== 0 && (
|
||||
<button
|
||||
className="following-members"
|
||||
onClick={() => openReadReceipts(roomId, followingMembers)}
|
||||
type="button"
|
||||
>
|
||||
<RawIcon
|
||||
size="extra-small"
|
||||
src={TickMarkIC}
|
||||
/>
|
||||
<Text variant="b2">{getUsersActionJsx(roomId, filteredM, 'following the conversation.')}</Text>
|
||||
</button>
|
||||
return (
|
||||
filteredM.length !== 0 && (
|
||||
<button
|
||||
className="following-members"
|
||||
onClick={() => openReadReceipts(roomId, followingMembers)}
|
||||
type="button"
|
||||
>
|
||||
<RawIcon size="extra-small" src={TickMarkIC} />
|
||||
<Text variant="b2">
|
||||
{getUsersActionJsx(roomId, filteredM, 'following the conversation.')}
|
||||
</Text>
|
||||
</button>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -113,19 +113,15 @@ function GlobalNotification() {
|
|||
const [rulesToType, setRule] = useGlobalNotif();
|
||||
|
||||
const onSelect = (evt, rule) => {
|
||||
openReusableContextMenu(
|
||||
'bottom',
|
||||
getEventCords(evt, '.btn-surface'),
|
||||
(requestClose) => (
|
||||
<NotificationSelector
|
||||
value={rulesToType[rule]}
|
||||
onSelect={(value) => {
|
||||
if (rulesToType[rule] !== value) setRule(rule, value);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
);
|
||||
openReusableContextMenu('bottom', getEventCords(evt, '.btn-surface'), (requestClose) => (
|
||||
<NotificationSelector
|
||||
value={rulesToType[rule]}
|
||||
onSelect={(value) => {
|
||||
if (rulesToType[rule] !== value) setRule(rule, value);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -133,39 +129,43 @@ function GlobalNotification() {
|
|||
<MenuHeader>Global Notifications</MenuHeader>
|
||||
<SettingTile
|
||||
title="Direct messages"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={(evt) => onSelect(evt, DM)} iconSrc={ChevronBottomIC}>
|
||||
{ typeToLabel[rulesToType[DM]] }
|
||||
{typeToLabel[rulesToType[DM]]}
|
||||
</Button>
|
||||
)}
|
||||
}
|
||||
content={<Text variant="b3">Default notification settings for all direct message.</Text>}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Encrypted direct messages"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={(evt) => onSelect(evt, ENC_DM)} iconSrc={ChevronBottomIC}>
|
||||
{typeToLabel[rulesToType[ENC_DM]]}
|
||||
</Button>
|
||||
)}
|
||||
content={<Text variant="b3">Default notification settings for all encrypted direct message.</Text>}
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">Default notification settings for all encrypted direct message.</Text>
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Rooms messages"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={(evt) => onSelect(evt, ROOM)} iconSrc={ChevronBottomIC}>
|
||||
{typeToLabel[rulesToType[ROOM]]}
|
||||
</Button>
|
||||
)}
|
||||
}
|
||||
content={<Text variant="b3">Default notification settings for all room message.</Text>}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Encrypted rooms messages"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={(evt) => onSelect(evt, ENC_ROOM)} iconSrc={ChevronBottomIC}>
|
||||
{typeToLabel[rulesToType[ENC_ROOM]]}
|
||||
</Button>
|
||||
)}
|
||||
content={<Text variant="b3">Default notification settings for all encrypted room message.</Text>}
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">Default notification settings for all encrypted room message.</Text>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,4 +14,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,12 +34,16 @@ function IgnoreUserList() {
|
|||
<MenuHeader>Ignored users</MenuHeader>
|
||||
<SettingTile
|
||||
title="Ignore user"
|
||||
content={(
|
||||
content={
|
||||
<div className="ignore-user-list__users">
|
||||
<Text variant="b3">Ignore userId if you do not want to receive their messages or invites.</Text>
|
||||
<Text variant="b3">
|
||||
Ignore userId if you do not want to receive their messages or invites.
|
||||
</Text>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Input name="ignoreInput" required />
|
||||
<Button variant="primary" type="submit">Ignore</Button>
|
||||
<Button variant="primary" type="submit">
|
||||
Ignore
|
||||
</Button>
|
||||
</form>
|
||||
{ignoredUsers.length > 0 && (
|
||||
<div>
|
||||
|
@ -55,7 +59,7 @@ function IgnoreUserList() {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,4 +14,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,7 @@ import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.s
|
|||
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||
|
||||
import { useAccountData } from '../../hooks/useAccountData';
|
||||
import {
|
||||
notifType, typeToLabel, getActionType, getTypeActions,
|
||||
} from './GlobalNotification';
|
||||
import { notifType, typeToLabel, getActionType, getTypeActions } from './GlobalNotification';
|
||||
|
||||
const DISPLAY_NAME = '.m.rule.contains_display_name';
|
||||
const ROOM_PING = '.m.rule.roomnotif';
|
||||
|
@ -131,30 +129,20 @@ function useKeywordNotif() {
|
|||
}
|
||||
|
||||
function GlobalNotification() {
|
||||
const {
|
||||
rulesToType,
|
||||
pushRules,
|
||||
setRule,
|
||||
addKeyword,
|
||||
removeKeyword,
|
||||
} = useKeywordNotif();
|
||||
const { rulesToType, pushRules, setRule, addKeyword, removeKeyword } = useKeywordNotif();
|
||||
|
||||
const keywordRules = pushRules?.global?.content.filter((r) => r.rule_id !== USERNAME) ?? [];
|
||||
|
||||
const onSelect = (evt, rule) => {
|
||||
openReusableContextMenu(
|
||||
'bottom',
|
||||
getEventCords(evt, '.btn-surface'),
|
||||
(requestClose) => (
|
||||
<NotificationSelector
|
||||
value={rulesToType[rule]}
|
||||
onSelect={(value) => {
|
||||
if (rulesToType[rule] !== value) setRule(rule, value);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
);
|
||||
openReusableContextMenu('bottom', getEventCords(evt, '.btn-surface'), (requestClose) => (
|
||||
<NotificationSelector
|
||||
value={rulesToType[rule]}
|
||||
onSelect={(value) => {
|
||||
if (rulesToType[rule] !== value) setRule(rule, value);
|
||||
requestClose();
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const handleSubmit = (evt) => {
|
||||
|
@ -171,50 +159,66 @@ function GlobalNotification() {
|
|||
<MenuHeader>Mentions & keywords</MenuHeader>
|
||||
<SettingTile
|
||||
title="Message containing my display name"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={(evt) => onSelect(evt, DISPLAY_NAME)} iconSrc={ChevronBottomIC}>
|
||||
{ typeToLabel[rulesToType[DISPLAY_NAME]] }
|
||||
{typeToLabel[rulesToType[DISPLAY_NAME]]}
|
||||
</Button>
|
||||
)}
|
||||
content={<Text variant="b3">Default notification settings for all message containing your display name.</Text>}
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">
|
||||
Default notification settings for all message containing your display name.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Message containing my username"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={(evt) => onSelect(evt, USERNAME)} iconSrc={ChevronBottomIC}>
|
||||
{ typeToLabel[rulesToType[USERNAME]] }
|
||||
{typeToLabel[rulesToType[USERNAME]]}
|
||||
</Button>
|
||||
)}
|
||||
content={<Text variant="b3">Default notification settings for all message containing your username.</Text>}
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">
|
||||
Default notification settings for all message containing your username.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Message containing @room"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={(evt) => onSelect(evt, ROOM_PING)} iconSrc={ChevronBottomIC}>
|
||||
{typeToLabel[rulesToType[ROOM_PING]]}
|
||||
</Button>
|
||||
)}
|
||||
content={<Text variant="b3">Default notification settings for all messages containing @room.</Text>}
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">Default notification settings for all messages containing @room.</Text>
|
||||
}
|
||||
/>
|
||||
{ rulesToType[KEYWORD] && (
|
||||
{rulesToType[KEYWORD] && (
|
||||
<SettingTile
|
||||
title="Message containing keywords"
|
||||
options={(
|
||||
options={
|
||||
<Button onClick={(evt) => onSelect(evt, KEYWORD)} iconSrc={ChevronBottomIC}>
|
||||
{typeToLabel[rulesToType[KEYWORD]]}
|
||||
</Button>
|
||||
)}
|
||||
content={<Text variant="b3">Default notification settings for all message containing keywords.</Text>}
|
||||
}
|
||||
content={
|
||||
<Text variant="b3">
|
||||
Default notification settings for all message containing keywords.
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SettingTile
|
||||
title="Keywords"
|
||||
content={(
|
||||
content={
|
||||
<div className="keyword-notification__keyword">
|
||||
<Text variant="b3">Get notification when a message contains keyword.</Text>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Input name="keywordInput" required />
|
||||
<Button variant="primary" type="submit">Add</Button>
|
||||
<Button variant="primary" type="submit">
|
||||
Add
|
||||
</Button>
|
||||
</form>
|
||||
{keywordRules.length > 0 && (
|
||||
<div>
|
||||
|
@ -230,7 +234,7 @@ function GlobalNotification() {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -5,15 +5,31 @@ import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
|
|||
|
||||
import CheckIC from '../../../../public/res/ic/outlined/check.svg';
|
||||
|
||||
function NotificationSelector({
|
||||
value, onSelect,
|
||||
}) {
|
||||
function NotificationSelector({ value, onSelect }) {
|
||||
return (
|
||||
<div>
|
||||
<MenuHeader>Notification</MenuHeader>
|
||||
<MenuItem iconSrc={value === 'off' ? CheckIC : null} variant={value === 'off' ? 'positive' : 'surface'} onClick={() => onSelect('off')}>Off</MenuItem>
|
||||
<MenuItem iconSrc={value === 'on' ? CheckIC : null} variant={value === 'on' ? 'positive' : 'surface'} onClick={() => onSelect('on')}>On</MenuItem>
|
||||
<MenuItem iconSrc={value === 'noisy' ? CheckIC : null} variant={value === 'noisy' ? 'positive' : 'surface'} onClick={() => onSelect('noisy')}>Noisy</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={value === 'off' ? CheckIC : null}
|
||||
variant={value === 'off' ? 'positive' : 'surface'}
|
||||
onClick={() => onSelect('off')}
|
||||
>
|
||||
Off
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={value === 'on' ? CheckIC : null}
|
||||
variant={value === 'on' ? 'positive' : 'surface'}
|
||||
onClick={() => onSelect('on')}
|
||||
>
|
||||
On
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={value === 'noisy' ? CheckIC : null}
|
||||
variant={value === 'noisy' ? 'positive' : 'surface'}
|
||||
onClick={() => onSelect('noisy')}
|
||||
>
|
||||
Noisy
|
||||
</MenuItem>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
background-color: var(--bg-overlay-low);
|
||||
}
|
||||
|
||||
|
||||
.image-lightbox__header > *,
|
||||
.image-lightbox__content > * {
|
||||
pointer-events: all;
|
||||
|
@ -47,4 +46,4 @@
|
|||
max-height: 100%;
|
||||
border-radius: var(--bo-radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@ import IconButton from '../../atoms/button/IconButton';
|
|||
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
|
||||
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
|
||||
|
||||
function ImageLightbox({
|
||||
url, alt, isOpen, onRequestClose,
|
||||
}) {
|
||||
function ImageLightbox({ url, alt, isOpen, onRequestClose }) {
|
||||
const handleDownload = () => {
|
||||
FileSaver.saveAs(url, alt);
|
||||
};
|
||||
|
@ -26,7 +24,9 @@ function ImageLightbox({
|
|||
size="large"
|
||||
>
|
||||
<div className="image-lightbox__header">
|
||||
<Text variant="b2" weight="medium">{alt}</Text>
|
||||
<Text variant="b2" weight="medium">
|
||||
{alt}
|
||||
</Text>
|
||||
<IconButton onClick={() => window.open(url)} size="small" src={ExternalSVG} />
|
||||
<IconButton onClick={handleDownload} size="small" src={DownloadSVG} />
|
||||
</div>
|
||||
|
|
|
@ -40,4 +40,4 @@
|
|||
gap: var(--sp-ultra-tight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,23 +16,17 @@ import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.s
|
|||
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
|
||||
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
||||
|
||||
function ImagePackItem({
|
||||
url, shortcode, usage, onUsageChange, onDelete, onRename,
|
||||
}) {
|
||||
function ImagePackItem({ url, shortcode, usage, onUsageChange, onDelete, onRename }) {
|
||||
const handleUsageSelect = (event) => {
|
||||
openReusableContextMenu(
|
||||
'bottom',
|
||||
getEventCords(event, '.btn-surface'),
|
||||
(closeMenu) => (
|
||||
<ImagePackUsageSelector
|
||||
usage={usage}
|
||||
onSelect={(newUsage) => {
|
||||
onUsageChange(shortcode, newUsage);
|
||||
closeMenu();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
);
|
||||
openReusableContextMenu('bottom', getEventCords(event, '.btn-surface'), (closeMenu) => (
|
||||
<ImagePackUsageSelector
|
||||
usage={usage}
|
||||
onSelect={(newUsage) => {
|
||||
onUsageChange(shortcode, newUsage);
|
||||
closeMenu();
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -43,8 +37,22 @@ function ImagePackItem({
|
|||
</div>
|
||||
<div className="image-pack-item__usage">
|
||||
<div className="image-pack-item__btn">
|
||||
{onRename && <IconButton tooltip="Rename" size="extra-small" src={PencilIC} onClick={() => onRename(shortcode)} />}
|
||||
{onDelete && <IconButton tooltip="Delete" size="extra-small" src={BinIC} onClick={() => onDelete(shortcode)} />}
|
||||
{onRename && (
|
||||
<IconButton
|
||||
tooltip="Rename"
|
||||
size="extra-small"
|
||||
src={PencilIC}
|
||||
onClick={() => onRename(shortcode)}
|
||||
/>
|
||||
)}
|
||||
{onDelete && (
|
||||
<IconButton
|
||||
tooltip="Delete"
|
||||
size="extra-small"
|
||||
src={BinIC}
|
||||
onClick={() => onDelete(shortcode)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Button onClick={onUsageChange ? handleUsageSelect : undefined}>
|
||||
{onUsageChange && <RawIcon src={ChevronBottomIC} size="extra-small" />}
|
||||
|
|
|
@ -34,4 +34,4 @@
|
|||
margin-bottom: var(--sp-ultra-tight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,13 @@ import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.s
|
|||
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
|
||||
|
||||
function ImagePackProfile({
|
||||
avatarUrl, displayName, attribution, usage,
|
||||
onUsageChange, onAvatarChange, onEditProfile,
|
||||
avatarUrl,
|
||||
displayName,
|
||||
attribution,
|
||||
usage,
|
||||
onUsageChange,
|
||||
onAvatarChange,
|
||||
onEditProfile,
|
||||
}) {
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
|
||||
|
@ -34,59 +39,59 @@ function ImagePackProfile({
|
|||
};
|
||||
|
||||
const handleUsageSelect = (event) => {
|
||||
openReusableContextMenu(
|
||||
'bottom',
|
||||
getEventCords(event, '.btn-surface'),
|
||||
(closeMenu) => (
|
||||
<ImagePackUsageSelector
|
||||
usage={usage}
|
||||
onSelect={(newUsage) => {
|
||||
onUsageChange(newUsage);
|
||||
closeMenu();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
);
|
||||
openReusableContextMenu('bottom', getEventCords(event, '.btn-surface'), (closeMenu) => (
|
||||
<ImagePackUsageSelector
|
||||
usage={usage}
|
||||
onSelect={(newUsage) => {
|
||||
onUsageChange(newUsage);
|
||||
closeMenu();
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="image-pack-profile">
|
||||
{
|
||||
onAvatarChange
|
||||
? (
|
||||
<ImageUpload
|
||||
bgColor="#555"
|
||||
text={displayName}
|
||||
imageSrc={avatarUrl}
|
||||
size="normal"
|
||||
onUpload={onAvatarChange}
|
||||
onRequestRemove={() => onAvatarChange(undefined)}
|
||||
/>
|
||||
)
|
||||
: <Avatar bgColor="#555" text={displayName} imageSrc={avatarUrl} size="normal" />
|
||||
}
|
||||
{onAvatarChange ? (
|
||||
<ImageUpload
|
||||
bgColor="#555"
|
||||
text={displayName}
|
||||
imageSrc={avatarUrl}
|
||||
size="normal"
|
||||
onUpload={onAvatarChange}
|
||||
onRequestRemove={() => onAvatarChange(undefined)}
|
||||
/>
|
||||
) : (
|
||||
<Avatar bgColor="#555" text={displayName} imageSrc={avatarUrl} size="normal" />
|
||||
)}
|
||||
<div className="image-pack-profile__content">
|
||||
{
|
||||
isEdit
|
||||
? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Input name="nameInput" label="Name" value={displayName} required />
|
||||
<Input name="attributionInput" label="Attribution" value={attribution} resizable />
|
||||
<div>
|
||||
<Button variant="primary" type="submit">Save</Button>
|
||||
<Button onClick={() => setIsEdit(false)}>Cancel</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<Text>{displayName}</Text>
|
||||
{onEditProfile && <IconButton size="extra-small" onClick={() => setIsEdit(true)} src={PencilIC} tooltip="Edit" />}
|
||||
</div>
|
||||
{attribution && <Text variant="b3">{attribution}</Text>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{isEdit ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Input name="nameInput" label="Name" value={displayName} required />
|
||||
<Input name="attributionInput" label="Attribution" value={attribution} resizable />
|
||||
<div>
|
||||
<Button variant="primary" type="submit">
|
||||
Save
|
||||
</Button>
|
||||
<Button onClick={() => setIsEdit(false)}>Cancel</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<Text>{displayName}</Text>
|
||||
{onEditProfile && (
|
||||
<IconButton
|
||||
size="extra-small"
|
||||
onClick={() => setIsEdit(true)}
|
||||
src={PencilIC}
|
||||
tooltip="Edit"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{attribution && <Text variant="b3">{attribution}</Text>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="image-pack-profile__usage">
|
||||
<Text variant="b3">Pack usage</Text>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
background: var(--bg-surface-low);
|
||||
border-radius: var(--bo-radius);
|
||||
box-shadow: var(--bs-surface-border);
|
||||
|
||||
|
||||
& button {
|
||||
--parent-height: 40px;
|
||||
width: var(--parent-height);
|
||||
|
@ -28,16 +28,16 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
& .ic-raw {
|
||||
background-color: var(--bg-caution);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
|
||||
& .text {
|
||||
@extend .cp-txt__ellipsis;
|
||||
@include dir.side(margin, var(--sp-ultra-tight), var(--sp-normal));
|
||||
max-width: 86px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,19 +50,26 @@ function ImagePackUpload({ onUpload }) {
|
|||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="image-pack-upload">
|
||||
<input ref={inputRef} onChange={handleFileChange} style={{ display: 'none' }} type="file" accept=".png, .gif, .webp" required />
|
||||
{
|
||||
imgFile
|
||||
? (
|
||||
<div className="image-pack-upload__file">
|
||||
<IconButton onClick={handleRemove} src={CirclePlusIC} tooltip="Remove file" />
|
||||
<Text>{imgFile.name}</Text>
|
||||
</div>
|
||||
)
|
||||
: <Button onClick={() => inputRef.current.click()}>Import image</Button>
|
||||
}
|
||||
<input
|
||||
ref={inputRef}
|
||||
onChange={handleFileChange}
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
accept=".png, .gif, .webp"
|
||||
required
|
||||
/>
|
||||
{imgFile ? (
|
||||
<div className="image-pack-upload__file">
|
||||
<IconButton onClick={handleRemove} src={CirclePlusIC} tooltip="Remove file" />
|
||||
<Text>{imgFile.name}</Text>
|
||||
</div>
|
||||
) : (
|
||||
<Button onClick={() => inputRef.current.click()}>Import image</Button>
|
||||
)}
|
||||
<Input forwardRef={shortcodeRef} name="shortcodeInput" placeholder="shortcode" required />
|
||||
<Button disabled={progress} variant="primary" type="submit">{progress ? 'Uploading...' : 'Upload'}</Button>
|
||||
<Button disabled={progress} variant="primary" type="submit">
|
||||
{progress ? 'Uploading...' : 'Upload'}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,49 +1,48 @@
|
|||
.img-upload__wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.img-upload {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
&__process {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: var(--bo-radius);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
& .text {
|
||||
text-transform: uppercase;
|
||||
color: white;
|
||||
}
|
||||
&--stopped {
|
||||
display: none;
|
||||
}
|
||||
& .donut-spinner {
|
||||
border-color: rgb(255, 255, 255, .3);
|
||||
border-left-color: white;
|
||||
}
|
||||
}
|
||||
&:hover .img-upload__process--stopped {
|
||||
display: flex;
|
||||
}
|
||||
&__process {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: var(--bo-radius);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
& .text {
|
||||
text-transform: uppercase;
|
||||
color: white;
|
||||
}
|
||||
&--stopped {
|
||||
display: none;
|
||||
}
|
||||
& .donut-spinner {
|
||||
border-color: rgb(255, 255, 255, 0.3);
|
||||
border-left-color: white;
|
||||
}
|
||||
}
|
||||
&:hover .img-upload__process--stopped {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__btn-cancel {
|
||||
margin-top: var(--sp-extra-tight);
|
||||
cursor: pointer;
|
||||
& .text {
|
||||
color: var(--tc-danger-normal)
|
||||
}
|
||||
}
|
||||
&__btn-cancel {
|
||||
margin-top: var(--sp-extra-tight);
|
||||
cursor: pointer;
|
||||
& .text {
|
||||
color: var(--tc-danger-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,7 @@ import RawIcon from '../../atoms/system-icons/RawIcon';
|
|||
|
||||
import PlusIC from '../../../../public/res/ic/outlined/plus.svg';
|
||||
|
||||
function ImageUpload({
|
||||
text, bgColor, imageSrc, onUpload, onRequestRemove,
|
||||
size,
|
||||
}) {
|
||||
function ImageUpload({ text, bgColor, imageSrc, onUpload, onRequestRemove, size }) {
|
||||
const [uploadPromise, setUploadPromise] = useState(null);
|
||||
const uploadImageRef = useRef(null);
|
||||
|
||||
|
@ -50,22 +47,24 @@ function ImageUpload({
|
|||
uploadImageRef.current.click();
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
imageSrc={imageSrc}
|
||||
text={text}
|
||||
bgColor={bgColor}
|
||||
size={size}
|
||||
/>
|
||||
<div className={`img-upload__process ${uploadPromise === null ? ' img-upload__process--stopped' : ''}`}>
|
||||
{uploadPromise === null && (
|
||||
size === 'large'
|
||||
? <Text variant="b3" weight="bold">Upload</Text>
|
||||
: <RawIcon src={PlusIC} color="white" />
|
||||
)}
|
||||
<Avatar imageSrc={imageSrc} text={text} bgColor={bgColor} size={size} />
|
||||
<div
|
||||
className={`img-upload__process ${
|
||||
uploadPromise === null ? ' img-upload__process--stopped' : ''
|
||||
}`}
|
||||
>
|
||||
{uploadPromise === null &&
|
||||
(size === 'large' ? (
|
||||
<Text variant="b3" weight="bold">
|
||||
Upload
|
||||
</Text>
|
||||
) : (
|
||||
<RawIcon src={PlusIC} color="white" />
|
||||
))}
|
||||
{uploadPromise !== null && <Spinner size="small" />}
|
||||
</div>
|
||||
</button>
|
||||
{ (typeof imageSrc === 'string' || uploadPromise !== null) && (
|
||||
{(typeof imageSrc === 'string' || uploadPromise !== null) && (
|
||||
<button
|
||||
className="img-upload__btn-cancel"
|
||||
type="button"
|
||||
|
@ -74,7 +73,13 @@ function ImageUpload({
|
|||
<Text variant="b3">{uploadPromise ? 'Cancel' : 'Remove'}</Text>
|
||||
</button>
|
||||
)}
|
||||
<input onChange={uploadImage} style={{ display: 'none' }} ref={uploadImageRef} type="file" accept="image/*" />
|
||||
<input
|
||||
onChange={uploadImage}
|
||||
style={{ display: 'none' }}
|
||||
ref={uploadImageRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,5 +24,4 @@
|
|||
margin-top: var(--sp-tight);
|
||||
color: var(--tc-danger-high);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
background-color: var(--bg-caution);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
|
||||
& .text {
|
||||
@extend .cp-txt__ellipsis;
|
||||
@include dir.side(margin, var(--sp-tight), var(--sp-loose));
|
||||
|
@ -33,8 +33,7 @@
|
|||
&__form {
|
||||
display: flex;
|
||||
margin-top: var(--sp-extra-tight);
|
||||
|
||||
|
||||
|
||||
& .input-container {
|
||||
flex: 1;
|
||||
margin: 0 var(--sp-tight);
|
||||
|
@ -58,4 +57,4 @@
|
|||
margin-top: var(--sp-tight);
|
||||
color: var(--tc-positive-high);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,8 +107,14 @@ function ImportE2ERoomKeys() {
|
|||
<div className="import-e2e-room-keys">
|
||||
<input ref={inputRef} onChange={handleFileChange} style={{ display: 'none' }} type="file" />
|
||||
|
||||
<form className="import-e2e-room-keys__form" onSubmit={(e) => { e.preventDefault(); importE2ERoomKeys(); }}>
|
||||
{ keyFile !== null && (
|
||||
<form
|
||||
className="import-e2e-room-keys__form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
importE2ERoomKeys();
|
||||
}}
|
||||
>
|
||||
{keyFile !== null && (
|
||||
<div className="import-e2e-room-keys__file">
|
||||
<IconButton onClick={removeImportKeysFile} src={CirclePlusIC} tooltip="Remove file" />
|
||||
<Text>{keyFile.name}</Text>
|
||||
|
@ -116,16 +122,26 @@ function ImportE2ERoomKeys() {
|
|||
)}
|
||||
{keyFile === null && <Button onClick={() => inputRef.current.click()}>Import keys</Button>}
|
||||
<Input forwardRef={passwordRef} type="password" placeholder="Password" required />
|
||||
<Button disabled={status.isOngoing} variant="primary" type="submit">Decrypt</Button>
|
||||
<Button disabled={status.isOngoing} variant="primary" type="submit">
|
||||
Decrypt
|
||||
</Button>
|
||||
</form>
|
||||
{ status.type === cons.status.IN_FLIGHT && (
|
||||
{status.type === cons.status.IN_FLIGHT && (
|
||||
<div className="import-e2e-room-keys__process">
|
||||
<Spinner size="small" />
|
||||
<Text variant="b2">{status.msg}</Text>
|
||||
</div>
|
||||
)}
|
||||
{status.type === cons.status.SUCCESS && <Text className="import-e2e-room-keys__success" variant="b2">{status.msg}</Text>}
|
||||
{status.type === cons.status.ERROR && <Text className="import-e2e-room-keys__error" variant="b2">{status.msg}</Text>}
|
||||
{status.type === cons.status.SUCCESS && (
|
||||
<Text className="import-e2e-room-keys__success" variant="b2">
|
||||
{status.msg}
|
||||
</Text>
|
||||
)}
|
||||
{status.type === cons.status.ERROR && (
|
||||
<Text className="import-e2e-room-keys__error" variant="b2">
|
||||
{status.msg}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -102,4 +102,4 @@
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,4 +34,4 @@
|
|||
margin: 0 var(--sp-tight);
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,7 @@ import InviteArraowIC from '../../../../public/res/ic/outlined/invite-arrow.svg'
|
|||
import InviteCancelArraowIC from '../../../../public/res/ic/outlined/invite-cancel-arrow.svg';
|
||||
import UserIC from '../../../../public/res/ic/outlined/user.svg';
|
||||
|
||||
function TimelineChange({
|
||||
variant, content, timestamp, onClick,
|
||||
}) {
|
||||
function TimelineChange({ variant, content, timestamp, onClick }) {
|
||||
let iconSrc;
|
||||
|
||||
switch (variant) {
|
||||
|
@ -39,14 +37,17 @@ function TimelineChange({
|
|||
}
|
||||
|
||||
return (
|
||||
<button style={{ cursor: onClick === null ? 'default' : 'pointer' }} onClick={onClick} type="button" className="timeline-change">
|
||||
<button
|
||||
style={{ cursor: onClick === null ? 'default' : 'pointer' }}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
className="timeline-change"
|
||||
>
|
||||
<div className="timeline-change__avatar-container">
|
||||
<RawIcon src={iconSrc} size="extra-small" />
|
||||
</div>
|
||||
<div className="timeline-change__content">
|
||||
<Text variant="b2">
|
||||
{content}
|
||||
</Text>
|
||||
<Text variant="b2">{content}</Text>
|
||||
</div>
|
||||
<div className="timeline-change__time">
|
||||
<Text variant="b3">
|
||||
|
@ -63,14 +64,8 @@ TimelineChange.defaultProps = {
|
|||
};
|
||||
|
||||
TimelineChange.propTypes = {
|
||||
variant: PropTypes.oneOf([
|
||||
'join', 'leave', 'invite',
|
||||
'invite-cancel', 'avatar', 'other',
|
||||
]),
|
||||
content: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
variant: PropTypes.oneOf(['join', 'leave', 'invite', 'invite-cancel', 'avatar', 'other']),
|
||||
content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
||||
timestamp: PropTypes.number.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -34,4 +34,4 @@
|
|||
&__role {
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,7 @@ import { blurOnBubbling } from '../../atoms/button/script';
|
|||
import Text from '../../atoms/text/Text';
|
||||
import Avatar from '../../atoms/avatar/Avatar';
|
||||
|
||||
function PeopleSelector({
|
||||
avatarSrc, name, color, peopleRole, onClick,
|
||||
}) {
|
||||
function PeopleSelector({ avatarSrc, name, color, peopleRole, onClick }) {
|
||||
return (
|
||||
<div className="people-selector__container">
|
||||
<button
|
||||
|
@ -21,8 +19,14 @@ function PeopleSelector({
|
|||
type="button"
|
||||
>
|
||||
<Avatar imageSrc={avatarSrc} text={name} bgColor={color} size="extra-small" />
|
||||
<Text className="people-selector__name" variant="b1">{twemojify(name)}</Text>
|
||||
{peopleRole !== null && <Text className="people-selector__role" variant="b3">{peopleRole}</Text>}
|
||||
<Text className="people-selector__name" variant="b1">
|
||||
{twemojify(name)}
|
||||
</Text>
|
||||
{peopleRole !== null && (
|
||||
<Text className="people-selector__role" variant="b3">
|
||||
{peopleRole}
|
||||
</Text>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.pw__drawer__content,
|
||||
.pw__content-container {
|
||||
padding-top: var(--sp-extra-tight);
|
||||
|
|
|
@ -13,19 +13,11 @@ import RawModal from '../../atoms/modal/RawModal';
|
|||
|
||||
import ChevronLeftIC from '../../../../public/res/ic/outlined/chevron-left.svg';
|
||||
|
||||
function PWContentSelector({
|
||||
selected, variant, iconSrc,
|
||||
type, onClick, children,
|
||||
}) {
|
||||
function PWContentSelector({ selected, variant, iconSrc, type, onClick, children }) {
|
||||
const pwcsClass = selected ? ' pw-content-selector--selected' : '';
|
||||
return (
|
||||
<div className={`pw-content-selector${pwcsClass}`}>
|
||||
<MenuItem
|
||||
variant={variant}
|
||||
iconSrc={iconSrc}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
>
|
||||
<MenuItem variant={variant} iconSrc={iconSrc} type={type} onClick={onClick}>
|
||||
{children}
|
||||
</MenuItem>
|
||||
</div>
|
||||
|
@ -49,9 +41,16 @@ PWContentSelector.propTypes = {
|
|||
};
|
||||
|
||||
function PopupWindow({
|
||||
className, isOpen, title, contentTitle,
|
||||
drawer, drawerOptions, contentOptions,
|
||||
onAfterClose, onRequestClose, children,
|
||||
className,
|
||||
isOpen,
|
||||
title,
|
||||
contentTitle,
|
||||
drawer,
|
||||
drawerOptions,
|
||||
contentOptions,
|
||||
onAfterClose,
|
||||
onRequestClose,
|
||||
children,
|
||||
}) {
|
||||
const haveDrawer = drawer !== null;
|
||||
const cTitle = contentTitle !== null ? contentTitle : title;
|
||||
|
@ -69,21 +68,26 @@ function PopupWindow({
|
|||
{haveDrawer && (
|
||||
<div className="pw__drawer">
|
||||
<Header>
|
||||
<IconButton size="small" src={ChevronLeftIC} onClick={onRequestClose} tooltip="Back" />
|
||||
<IconButton
|
||||
size="small"
|
||||
src={ChevronLeftIC}
|
||||
onClick={onRequestClose}
|
||||
tooltip="Back"
|
||||
/>
|
||||
<TitleWrapper>
|
||||
{
|
||||
typeof title === 'string'
|
||||
? <Text variant="s1" weight="medium" primary>{twemojify(title)}</Text>
|
||||
: title
|
||||
}
|
||||
{typeof title === 'string' ? (
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{twemojify(title)}
|
||||
</Text>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</TitleWrapper>
|
||||
{drawerOptions}
|
||||
</Header>
|
||||
<div className="pw__drawer__content__wrapper">
|
||||
<ScrollView invisible>
|
||||
<div className="pw__drawer__content">
|
||||
{drawer}
|
||||
</div>
|
||||
<div className="pw__drawer__content">{drawer}</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -91,19 +95,19 @@ function PopupWindow({
|
|||
<div className="pw__content">
|
||||
<Header>
|
||||
<TitleWrapper>
|
||||
{
|
||||
typeof cTitle === 'string'
|
||||
? <Text variant="h2" weight="medium" primary>{twemojify(cTitle)}</Text>
|
||||
: cTitle
|
||||
}
|
||||
{typeof cTitle === 'string' ? (
|
||||
<Text variant="h2" weight="medium" primary>
|
||||
{twemojify(cTitle)}
|
||||
</Text>
|
||||
) : (
|
||||
cTitle
|
||||
)}
|
||||
</TitleWrapper>
|
||||
{contentOptions}
|
||||
</Header>
|
||||
<div className="pw__content__wrapper">
|
||||
<ScrollView autoHide>
|
||||
<div className="pw__content-container">
|
||||
{children}
|
||||
</div>
|
||||
<div className="pw__content-container">{children}</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
& form {
|
||||
margin: var(--sp-normal);
|
||||
display: flex;
|
||||
|
||||
|
||||
& input {
|
||||
@extend .cp-fx__item-one;
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
|
@ -17,4 +17,4 @@
|
|||
padding: 9px var(--sp-tight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,7 @@ import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
|
|||
|
||||
import CheckIC from '../../../../public/res/ic/outlined/check.svg';
|
||||
|
||||
function PowerLevelSelector({
|
||||
value, max, onSelect,
|
||||
}) {
|
||||
function PowerLevelSelector({ value, max, onSelect }) {
|
||||
const handleSubmit = (e) => {
|
||||
const powerLevel = e.target.elements['power-level']?.value;
|
||||
if (!powerLevel) return;
|
||||
|
@ -19,7 +17,12 @@ function PowerLevelSelector({
|
|||
return (
|
||||
<div className="power-level-selector">
|
||||
<MenuHeader>Power level selector</MenuHeader>
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(e); }}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleSubmit(e);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="input"
|
||||
defaultValue={value}
|
||||
|
@ -33,9 +36,21 @@ function PowerLevelSelector({
|
|||
<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>}
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
padding: var(--sp-extra-tight) var(--sp-normal);
|
||||
@extend .cp-fx__row--s-c;
|
||||
&.checkbox {
|
||||
@include dir.side(margin, 0 , var(--sp-tight));
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
}
|
||||
& .text {
|
||||
@extend .cp-fx__item-one;
|
||||
|
@ -50,7 +50,7 @@
|
|||
margin: 0 var(--sp-normal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&__form {
|
||||
padding: var(--sp-normal);
|
||||
padding-top: 0;
|
||||
|
@ -59,12 +59,12 @@
|
|||
padding: var(--sp-normal) var(--sp-normal) var(--sp-ultra-tight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&__input-wrapper {
|
||||
display: flex;
|
||||
@extend .cp-fx__item-one;
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
|
||||
|
||||
& .input-container {
|
||||
@extend .cp-fx__item-one;
|
||||
}
|
||||
|
@ -81,4 +81,4 @@
|
|||
color: var(--tc-danger-high);
|
||||
padding-bottom: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
border-radius: var(--bo-radius);
|
||||
box-shadow: var(--bs-surface-border);
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
& > .context-menu__header:first-child {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
@ -26,4 +26,4 @@
|
|||
padding: var(--sp-extra-loose) var(--sp-normal);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
& .setting-tile {
|
||||
margin: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,16 +15,22 @@ function RoomEncryption({ roomId }) {
|
|||
const room = mx.getRoom(roomId);
|
||||
const encryptionEvents = room.currentState.getStateEvents('m.room.encryption');
|
||||
const [isEncrypted, setIsEncrypted] = useState(encryptionEvents.length > 0);
|
||||
const canEnableEncryption = room.currentState.maySendStateEvent('m.room.encryption', mx.getUserId());
|
||||
const canEnableEncryption = room.currentState.maySendStateEvent(
|
||||
'm.room.encryption',
|
||||
mx.getUserId(),
|
||||
);
|
||||
|
||||
const handleEncryptionEnable = async () => {
|
||||
const joinRule = room.getJoinRule();
|
||||
const confirmMsg1 = 'It is not recommended to add encryption in public room. Anyone can find and join public rooms, so anyone can read messages in them.';
|
||||
const confirmMsg2 = 'Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly';
|
||||
const confirmMsg1 =
|
||||
'It is not recommended to add encryption in public room. Anyone can find and join public rooms, so anyone can read messages in them.';
|
||||
const confirmMsg2 =
|
||||
'Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly';
|
||||
|
||||
const isConfirmed1 = (joinRule === 'public')
|
||||
? await confirmDialog('Enable encryption', confirmMsg1, 'Continue', 'caution')
|
||||
: true;
|
||||
const isConfirmed1 =
|
||||
joinRule === 'public'
|
||||
? await confirmDialog('Enable encryption', confirmMsg1, 'Continue', 'caution')
|
||||
: true;
|
||||
if (!isConfirmed1) return;
|
||||
if (await confirmDialog('Enable encryption', confirmMsg2, 'Enable', 'caution')) {
|
||||
setIsEncrypted(true);
|
||||
|
@ -38,16 +44,14 @@ function RoomEncryption({ roomId }) {
|
|||
<div className="room-encryption">
|
||||
<SettingTile
|
||||
title="Enable room encryption"
|
||||
content={(
|
||||
<Text variant="b3">Once enabled, encryption cannot be disabled.</Text>
|
||||
)}
|
||||
options={(
|
||||
content={<Text variant="b3">Once enabled, encryption cannot be disabled.</Text>}
|
||||
options={
|
||||
<Toggle
|
||||
isActive={isEncrypted}
|
||||
onToggle={handleEncryptionEnable}
|
||||
disabled={isEncrypted || !canEnableEncryption}
|
||||
/>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
margin: 0 !important;
|
||||
@extend .cp-fx__item-one;
|
||||
@extend .cp-fx__row--s-c;
|
||||
|
||||
|
||||
& span:first-child {
|
||||
@extend .cp-fx__item-one;
|
||||
@extend .cp-txt__ellipsis;
|
||||
}
|
||||
|
||||
|
||||
& .radio-btn {
|
||||
@include dir.side(margin, var(--sp-tight), 0);
|
||||
}
|
||||
|
@ -22,4 +22,4 @@
|
|||
margin: var(--sp-normal);
|
||||
margin-top: var(--sp-ultra-tight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,4 +23,4 @@
|
|||
&__time {
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,16 +7,22 @@ import colorMXID from '../../../util/colorMXID';
|
|||
import Text from '../../atoms/text/Text';
|
||||
import Avatar from '../../atoms/avatar/Avatar';
|
||||
|
||||
function RoomIntro({
|
||||
roomId, avatarSrc, name, heading, desc, time,
|
||||
}) {
|
||||
function RoomIntro({ roomId, avatarSrc, name, heading, desc, time }) {
|
||||
return (
|
||||
<div className="room-intro">
|
||||
<Avatar imageSrc={avatarSrc} text={name} bgColor={colorMXID(roomId)} size="large" />
|
||||
<div className="room-intro__content">
|
||||
<Text className="room-intro__name" variant="h1" weight="medium" primary>{heading}</Text>
|
||||
<Text className="room-intro__desc" variant="b1">{desc}</Text>
|
||||
{ time !== null && <Text className="room-intro__time" variant="b3">{time}</Text>}
|
||||
<Text className="room-intro__name" variant="h1" weight="medium" primary>
|
||||
{heading}
|
||||
</Text>
|
||||
<Text className="room-intro__desc" variant="b1">
|
||||
{desc}
|
||||
</Text>
|
||||
{time !== null && (
|
||||
<Text className="room-intro__time" variant="b3">
|
||||
{time}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -29,10 +35,7 @@ RoomIntro.defaultProps = {
|
|||
|
||||
RoomIntro.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
avatarSrc: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.bool,
|
||||
]),
|
||||
avatarSrc: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||
name: PropTypes.string.isRequired,
|
||||
heading: PropTypes.node.isRequired,
|
||||
desc: PropTypes.node.isRequired,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
.room-members {
|
||||
& .input-container {
|
||||
margin: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
|
@ -23,7 +23,6 @@
|
|||
}
|
||||
}
|
||||
&__list {
|
||||
|
||||
& .people-selector__container:last-child {
|
||||
margin-bottom: var(--sp-extra-tight);
|
||||
}
|
||||
|
@ -32,8 +31,8 @@
|
|||
margin: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&__status {
|
||||
margin: var(--sp-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import React, {
|
||||
useState, useEffect, useCallback,
|
||||
} from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './RoomMembers.scss';
|
||||
|
||||
|
@ -45,8 +43,7 @@ function useMemberOfMembership(roomId, membership) {
|
|||
if (isLoadingMembers) return;
|
||||
if (event && event?.getRoomId() !== roomId) return;
|
||||
const memberOfMembership = normalizeMembers(
|
||||
room.getMembersWithMembership(membership)
|
||||
.sort(memberByAtoZ).sort(memberByPowerLevel),
|
||||
room.getMembersWithMembership(membership).sort(memberByAtoZ).sort(memberByPowerLevel),
|
||||
);
|
||||
setMembers(memberOfMembership);
|
||||
};
|
||||
|
@ -125,20 +122,16 @@ function RoomMembers({ roomId }) {
|
|||
return (
|
||||
<div className="room-members">
|
||||
<MenuHeader>Search member</MenuHeader>
|
||||
<Input
|
||||
onChange={handleSearch}
|
||||
placeholder="Search for name"
|
||||
autoFocus
|
||||
/>
|
||||
<Input onChange={handleSearch} placeholder="Search for name" autoFocus />
|
||||
<div className="room-members__header">
|
||||
<MenuHeader>{`${searchMembers ? `Found — ${mList.length}` : members.length} members`}</MenuHeader>
|
||||
<MenuHeader>{`${
|
||||
searchMembers ? `Found — ${mList.length}` : members.length
|
||||
} members`}</MenuHeader>
|
||||
<SegmentedControls
|
||||
selected={
|
||||
(() => {
|
||||
const getSegmentIndex = { join: 0, invite: 1, ban: 2 };
|
||||
return getSegmentIndex[membership];
|
||||
})()
|
||||
}
|
||||
selected={(() => {
|
||||
const getSegmentIndex = { join: 0, invite: 1, ban: 2 };
|
||||
return getSegmentIndex[membership];
|
||||
})()}
|
||||
segments={[{ text: 'Joined' }, { text: 'Invited' }, { text: 'Banned' }]}
|
||||
onSelect={(index) => {
|
||||
const memberships = ['join', 'invite', 'ban'];
|
||||
|
@ -157,22 +150,18 @@ function RoomMembers({ roomId }) {
|
|||
peopleRole={member.peopleRole}
|
||||
/>
|
||||
))}
|
||||
{
|
||||
(searchMembers?.data.length === 0 || members.length === 0)
|
||||
&& (
|
||||
<div className="room-members__status">
|
||||
<Text variant="b2">
|
||||
{searchMembers ? `No results found for "${searchMembers.term}"` : 'No members to display'}
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
mList.length !== 0
|
||||
&& members.length > itemCount
|
||||
&& searchMembers === null
|
||||
&& <Button onClick={loadMorePeople}>View more</Button>
|
||||
}
|
||||
{(searchMembers?.data.length === 0 || members.length === 0) && (
|
||||
<div className="room-members__status">
|
||||
<Text variant="b2">
|
||||
{searchMembers
|
||||
? `No results found for "${searchMembers.term}"`
|
||||
: 'No members to display'}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
{mList.length !== 0 && members.length > itemCount && searchMembers === null && (
|
||||
<Button onClick={loadMorePeople}>View more</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
& .context-menu__item .text {
|
||||
@extend .cp-fx__item-one;
|
||||
@extend .cp-fx__row--s-c;
|
||||
|
||||
|
||||
& span:first-child {
|
||||
@extend .cp-fx__item-one;
|
||||
@extend .cp-txt__ellipsis;
|
||||
}
|
||||
|
||||
|
||||
& .radio-btn {
|
||||
@include dir.side(margin, var(--sp-tight), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,16 +45,18 @@ function RoomOptions({ roomId, afterOptionSelect }) {
|
|||
|
||||
return (
|
||||
<div style={{ maxWidth: '256px' }}>
|
||||
<MenuHeader>{twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)}</MenuHeader>
|
||||
<MenuItem iconSrc={TickMarkIC} onClick={handleMarkAsRead}>Mark as read</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={AddUserIC}
|
||||
onClick={handleInviteClick}
|
||||
disabled={!canInvite}
|
||||
>
|
||||
<MenuHeader>
|
||||
{twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)}
|
||||
</MenuHeader>
|
||||
<MenuItem iconSrc={TickMarkIC} onClick={handleMarkAsRead}>
|
||||
Mark as read
|
||||
</MenuItem>
|
||||
<MenuItem iconSrc={AddUserIC} onClick={handleInviteClick} disabled={!canInvite}>
|
||||
Invite
|
||||
</MenuItem>
|
||||
<MenuItem iconSrc={LeaveArrowIC} variant="danger" onClick={handleLeaveClick}>Leave</MenuItem>
|
||||
<MenuItem iconSrc={LeaveArrowIC} variant="danger" onClick={handleLeaveClick}>
|
||||
Leave
|
||||
</MenuItem>
|
||||
<MenuHeader>Notification</MenuHeader>
|
||||
<RoomNotification roomId={roomId} />
|
||||
</div>
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,11 +141,28 @@ const permissionsInfo = {
|
|||
};
|
||||
|
||||
const roomPermsGroups = {
|
||||
'General Permissions': ['users_default', 'events_default', 'm.reaction', 'redact', 'notifications'],
|
||||
'General Permissions': [
|
||||
'users_default',
|
||||
'events_default',
|
||||
'm.reaction',
|
||||
'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'],
|
||||
'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 = {
|
||||
|
@ -178,7 +195,10 @@ function RoomPermissions({ roomId }) {
|
|||
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());
|
||||
const canChangePermission = room.currentState.maySendStateEvent(
|
||||
'm.room.power_levels',
|
||||
mx.getUserId(),
|
||||
);
|
||||
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel ?? 100;
|
||||
|
||||
const handlePowerSelector = (e, permKey, parentKey, powerLevel) => {
|
||||
|
@ -203,74 +223,66 @@ function RoomPermissions({ roomId }) {
|
|||
mx.sendStateEvent(roomId, 'm.room.power_levels', newPermissions);
|
||||
};
|
||||
|
||||
openReusableContextMenu(
|
||||
'bottom',
|
||||
getEventCords(e, '.btn-surface'),
|
||||
(closeMenu) => (
|
||||
<PowerLevelSelector
|
||||
value={powerLevel}
|
||||
max={myPowerLevel}
|
||||
onSelect={(pl) => {
|
||||
closeMenu();
|
||||
handlePowerLevelChange(pl);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
);
|
||||
openReusableContextMenu('bottom', getEventCords(e, '.btn-surface'), (closeMenu) => (
|
||||
<PowerLevelSelector
|
||||
value={powerLevel}
|
||||
max={myPowerLevel}
|
||||
onSelect={(pl) => {
|
||||
closeMenu();
|
||||
handlePowerLevelChange(pl);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const permsGroups = room.isSpaceRoom() ? spacePermsGroups : roomPermsGroups;
|
||||
return (
|
||||
<div className="room-permissions">
|
||||
{
|
||||
Object.keys(permsGroups).map((groupKey) => {
|
||||
const groupedPermKeys = permsGroups[groupKey];
|
||||
return (
|
||||
<div className="room-permissions__card" key={groupKey}>
|
||||
<MenuHeader>{groupKey}</MenuHeader>
|
||||
{
|
||||
groupedPermKeys.map((permKey) => {
|
||||
const permInfo = permissionsInfo[permKey];
|
||||
{Object.keys(permsGroups).map((groupKey) => {
|
||||
const groupedPermKeys = permsGroups[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];
|
||||
let powerLevel = 0;
|
||||
let permValue = permInfo.parent
|
||||
? permissions[permInfo.parent]?.[permKey]
|
||||
: permissions[permKey];
|
||||
|
||||
if (permValue === undefined) permValue = permInfo.default;
|
||||
if (permValue === undefined) 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
|
||||
onClick={
|
||||
canChangePermission
|
||||
? (e) => handlePowerSelector(e, permKey, permInfo.parent, powerLevel)
|
||||
: null
|
||||
}
|
||||
iconSrc={canChangePermission ? ChevronBottomIC : null}
|
||||
>
|
||||
<Text variant="b2">
|
||||
{`${getPowerLabel(powerLevel) || 'Member'} - ${powerLevel}`}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
if (typeof permValue === 'number') {
|
||||
powerLevel = permValue;
|
||||
} else if (permKey === 'notifications') {
|
||||
powerLevel = permValue.room ?? 50;
|
||||
}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
return (
|
||||
<SettingTile
|
||||
key={permKey}
|
||||
title={permInfo.name}
|
||||
content={<Text variant="b3">{permInfo.description}</Text>}
|
||||
options={
|
||||
<Button
|
||||
onClick={
|
||||
canChangePermission
|
||||
? (e) => handlePowerSelector(e, permKey, permInfo.parent, powerLevel)
|
||||
: null
|
||||
}
|
||||
iconSrc={canChangePermission ? ChevronBottomIC : null}
|
||||
>
|
||||
<Text variant="b2">
|
||||
{`${getPowerLabel(powerLevel) || 'Member'} - ${powerLevel}`}
|
||||
</Text>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
@use '../../partials/dir';
|
||||
|
||||
.room-profile {
|
||||
|
||||
&__content {
|
||||
@extend .cp-fx__row;
|
||||
& .avatar-container {
|
||||
|
@ -27,7 +26,7 @@
|
|||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&__edit-form {
|
||||
@extend .cp-fx__item-one;
|
||||
@include dir.side(margin, var(--sp-loose), 0);
|
||||
|
@ -39,15 +38,15 @@
|
|||
& > .text {
|
||||
margin-bottom: var(--sp-tight);
|
||||
}
|
||||
|
||||
|
||||
& > *:last-child {
|
||||
@extend .cp-fx__item-one;
|
||||
@extend .cp-fx__row;
|
||||
margin-top: var(--sp-tight);
|
||||
|
||||
|
||||
.btn-primary {
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ function RoomProfile({ roomId }) {
|
|||
'Remove avatar',
|
||||
'Are you sure that you want to remove room avatar?',
|
||||
'Remove',
|
||||
'caution'
|
||||
'caution',
|
||||
);
|
||||
if (isConfirmed) {
|
||||
await mx.sendStateEvent(roomId, 'm.room.avatar', { url }, '');
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue