initial commit
This commit is contained in:
40
src/app/organisms/channel/Channel.jsx
Normal file
40
src/app/organisms/channel/Channel.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './Channel.scss';
|
||||
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
|
||||
import Welcome from '../welcome/Welcome';
|
||||
import ChannelView from './ChannelView';
|
||||
import PeopleDrawer from './PeopleDrawer';
|
||||
|
||||
function Channel() {
|
||||
const [selectedRoomId, changeSelectedRoomId] = useState(null);
|
||||
const [isDrawerVisible, toggleDrawerVisiblity] = useState(navigation.isPeopleDrawerVisible);
|
||||
useEffect(() => {
|
||||
const handleRoomSelected = (roomId) => {
|
||||
changeSelectedRoomId(roomId);
|
||||
};
|
||||
const handleDrawerToggling = (visiblity) => {
|
||||
toggleDrawerVisiblity(visiblity);
|
||||
};
|
||||
navigation.on(cons.events.navigation.ROOM_SELECTED, handleRoomSelected);
|
||||
navigation.on(cons.events.navigation.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
|
||||
|
||||
return () => {
|
||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, handleRoomSelected);
|
||||
navigation.removeListener(cons.events.navigation.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (selectedRoomId === null) return <Welcome />;
|
||||
|
||||
return (
|
||||
<div className="channel-container">
|
||||
<ChannelView roomId={selectedRoomId} />
|
||||
{ isDrawerVisible && <PeopleDrawer roomId={selectedRoomId} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Channel;
|
||||
4
src/app/organisms/channel/Channel.scss
Normal file
4
src/app/organisms/channel/Channel.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.channel-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
1142
src/app/organisms/channel/ChannelView.jsx
Normal file
1142
src/app/organisms/channel/ChannelView.jsx
Normal file
File diff suppressed because it is too large
Load Diff
248
src/app/organisms/channel/ChannelView.scss
Normal file
248
src/app/organisms/channel/ChannelView.scss
Normal file
@@ -0,0 +1,248 @@
|
||||
.channel-view-flexBox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.channel-view-flexItem {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.channel-view {
|
||||
@extend .channel-view-flexItem;
|
||||
@extend .channel-view-flexBox;
|
||||
|
||||
&__content-wrapper {
|
||||
@extend .channel-view-flexItem;
|
||||
@extend .channel-view-flexBox;
|
||||
}
|
||||
|
||||
&__scrollable {
|
||||
@extend .channel-view-flexItem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__content {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
& .timeline__wrapper {
|
||||
--typing-noti-height: 28px;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
padding-bottom: var(--typing-noti-height);
|
||||
}
|
||||
}
|
||||
|
||||
&__typing {
|
||||
display: flex;
|
||||
padding: var(--sp-ultra-tight) var(--sp-normal);
|
||||
background: var(--bg-surface);
|
||||
transition: transform 200ms ease-in-out;
|
||||
|
||||
& b {
|
||||
color: var(--tc-surface-high);
|
||||
}
|
||||
|
||||
&--open {
|
||||
transform: translateY(-99%);
|
||||
}
|
||||
|
||||
& .text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0 var(--sp-tight);
|
||||
}
|
||||
}
|
||||
|
||||
.bouncingLoader {
|
||||
transform: translateY(2px);
|
||||
margin: 0 calc(var(--sp-ultra-tight) / 2);
|
||||
}
|
||||
.bouncingLoader > div,
|
||||
.bouncingLoader:before,
|
||||
.bouncingLoader:after {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--tc-surface-high);
|
||||
border-radius: 50%;
|
||||
animation: bouncing-loader 0.6s infinite alternate;
|
||||
}
|
||||
|
||||
.bouncingLoader:before,
|
||||
.bouncingLoader:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.bouncingLoader > div {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.bouncingLoader > div {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.bouncingLoader:after {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes bouncing-loader {
|
||||
to {
|
||||
opacity: 0.1;
|
||||
transform: translate3d(0, -4px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
&__STB {
|
||||
position: absolute;
|
||||
right: var(--sp-normal);
|
||||
bottom: 0;
|
||||
border-radius: var(--bo-radius);
|
||||
box-shadow: var(--bs-surface-border);
|
||||
background-color: var(--bg-surface-low);
|
||||
transition: transform 200ms ease-in-out;
|
||||
transform: translateY(100%) scale(0);
|
||||
[dir=rtl] & {
|
||||
right: unset;
|
||||
left: var(--sp-normal);
|
||||
}
|
||||
|
||||
&--open {
|
||||
transform: translateY(-28px) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&__sticky {
|
||||
min-height: 85px;
|
||||
position: relative;
|
||||
background: var(--bg-surface);
|
||||
border-top: 1px solid var(--bg-surface-border);
|
||||
}
|
||||
}
|
||||
|
||||
.channel-input {
|
||||
padding: var(--sp-extra-tight) calc(var(--sp-normal) - 2px);
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
|
||||
&__space {
|
||||
min-width: 0;
|
||||
align-self: center;
|
||||
margin: auto;
|
||||
padding: 0 var(--sp-tight);
|
||||
}
|
||||
|
||||
&__input-container {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
margin: 0 calc(var(--sp-tight) - 2px);
|
||||
background-color: var(--bg-surface-low);
|
||||
box-shadow: var(--bs-surface-border);
|
||||
border-radius: var(--bo-radius);
|
||||
|
||||
& > .ic-raw {
|
||||
transform: scale(0.8);
|
||||
margin-left: var(--sp-extra-tight);
|
||||
[dir=rtl] & {
|
||||
margin-left: 0;
|
||||
margin-right: var(--sp-extra-tight);
|
||||
}
|
||||
}
|
||||
& .scrollbar {
|
||||
max-height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
&__textarea-wrapper {
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& textarea {
|
||||
resize: none;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 100%;
|
||||
padding: var(--sp-ultra-tight) calc(var(--sp-tight) - 2px);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel-cmd-bar {
|
||||
--cmd-bar-height: 28px;
|
||||
min-height: var(--cmd-bar-height);
|
||||
|
||||
& .timeline-change {
|
||||
justify-content: flex-end;
|
||||
padding: var(--sp-ultra-tight) var(--sp-normal);
|
||||
|
||||
&__content {
|
||||
margin: 0;
|
||||
flex: unset;
|
||||
& > .text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
& b {
|
||||
color: var(--tc-surface-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel-attachment {
|
||||
--side-spacing: calc(var(--sp-normal) + var(--av-small) + var(--sp-tight));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: var(--side-spacing);
|
||||
margin-top: var(--sp-extra-tight);
|
||||
line-height: 0;
|
||||
[dir=rtl] & {
|
||||
margin-left: 0;
|
||||
margin-right: var(--side-spacing);
|
||||
}
|
||||
|
||||
&__preview > img {
|
||||
max-height: 40px;
|
||||
border-radius: var(--bo-radius);
|
||||
}
|
||||
&__icon {
|
||||
padding: var(--sp-extra-tight);
|
||||
background-color: var(--bg-surface-low);
|
||||
box-shadow: var(--bs-surface-border);
|
||||
border-radius: var(--bo-radius);
|
||||
}
|
||||
&__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0 var(--sp-tight);
|
||||
}
|
||||
|
||||
&__option button {
|
||||
transition: transform 200ms ease-in-out;
|
||||
transform: translateY(-48px);
|
||||
& .ic-raw {
|
||||
transition: transform 200ms ease-in-out;
|
||||
transform: rotate(45deg);
|
||||
background-color: var(--bg-caution);
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/app/organisms/channel/PeopleDrawer.jsx
Normal file
138
src/app/organisms/channel/PeopleDrawer.jsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './PeopleDrawer.scss';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { getUsername } from '../../../util/matrixUtil';
|
||||
import colorMXID from '../../../util/colorMXID';
|
||||
import { openInviteUser } from '../../../client/action/navigation';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import Header, { TitleWrapper } from '../../atoms/header/Header';
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
import Button from '../../atoms/button/Button';
|
||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||
import Input from '../../atoms/input/Input';
|
||||
import PeopleSelector from '../../molecules/people-selector/PeopleSelector';
|
||||
|
||||
import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg';
|
||||
|
||||
function getPowerLabel(powerLevel) {
|
||||
switch (powerLevel) {
|
||||
case 100:
|
||||
return 'Admin';
|
||||
case 50:
|
||||
return 'Mod';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function compare(m1, m2) {
|
||||
let aName = m1.name;
|
||||
let bName = m2.name;
|
||||
|
||||
// remove "#" from the room name
|
||||
// To ignore it in sorting
|
||||
aName = aName.replaceAll('#', '');
|
||||
bName = bName.replaceAll('#', '');
|
||||
|
||||
if (aName.toLowerCase() < bName.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (aName.toLowerCase() > bName.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
function sortByPowerLevel(m1, m2) {
|
||||
let pl1 = String(m1.powerLevel);
|
||||
let pl2 = String(m2.powerLevel);
|
||||
|
||||
if (pl1 === '100') pl1 = '90.9';
|
||||
if (pl2 === '100') pl2 = '90.9';
|
||||
|
||||
if (pl1.toLowerCase() > pl2.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
if (pl1.toLowerCase() < pl2.toLowerCase()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function PeopleDrawer({ roomId }) {
|
||||
const PER_PAGE_MEMBER = 50;
|
||||
const room = initMatrix.matrixClient.getRoom(roomId);
|
||||
const totalMemberList = room.getJoinedMembers().sort(compare).sort(sortByPowerLevel);
|
||||
const [memberList, updateMemberList] = useState([]);
|
||||
let isRoomChanged = false;
|
||||
|
||||
function loadMorePeople() {
|
||||
updateMemberList(totalMemberList.slice(0, memberList.length + PER_PAGE_MEMBER));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateMemberList(totalMemberList.slice(0, PER_PAGE_MEMBER));
|
||||
room.loadMembersIfNeeded().then(() => {
|
||||
if (isRoomChanged) return;
|
||||
const newTotalMemberList = room.getJoinedMembers().sort(compare).sort(sortByPowerLevel);
|
||||
updateMemberList(newTotalMemberList.slice(0, PER_PAGE_MEMBER));
|
||||
});
|
||||
|
||||
return () => {
|
||||
isRoomChanged = true;
|
||||
};
|
||||
}, [roomId]);
|
||||
|
||||
return (
|
||||
<div className="people-drawer">
|
||||
<Header>
|
||||
<TitleWrapper>
|
||||
<Text variant="s1">
|
||||
People
|
||||
<Text className="people-drawer__member-count" variant="b3">{`${room.getJoinedMemberCount()} members`}</Text>
|
||||
</Text>
|
||||
</TitleWrapper>
|
||||
<IconButton onClick={() => openInviteUser(roomId)} tooltip="Invite" src={AddUserIC} />
|
||||
</Header>
|
||||
<div className="people-drawer__content-wrapper">
|
||||
<div className="people-drawer__scrollable">
|
||||
<ScrollView autoHide>
|
||||
<div className="people-drawer__content">
|
||||
{
|
||||
memberList.map((member) => (
|
||||
<PeopleSelector
|
||||
key={member.userId}
|
||||
onClick={() => alert('Viewing profile is yet to be implemented')}
|
||||
avatarSrc={member.getAvatarUrl(initMatrix.matrixClient.baseUrl, 24, 24, 'crop')}
|
||||
name={getUsername(member.userId)}
|
||||
color={colorMXID(member.userId)}
|
||||
peopleRole={getPowerLabel(member.powerLevel)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<div className="people-drawer__load-more">
|
||||
{
|
||||
memberList.length !== totalMemberList.length && (
|
||||
<Button onClick={loadMorePeople}>View more</Button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollView>
|
||||
</div>
|
||||
<div className="people-drawer__sticky">
|
||||
<form onSubmit={(e) => e.preventDefault()} className="people-search">
|
||||
<Input type="text" placeholder="Search" required />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PeopleDrawer.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default PeopleDrawer;
|
||||
75
src/app/organisms/channel/PeopleDrawer.scss
Normal file
75
src/app/organisms/channel/PeopleDrawer.scss
Normal file
@@ -0,0 +1,75 @@
|
||||
.people-drawer-flexBox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.people-drawer-flexItem {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
|
||||
.people-drawer {
|
||||
@extend .people-drawer-flexBox;
|
||||
width: var(--people-drawer-width);
|
||||
background-color: var(--bg-surface-low);
|
||||
border-left: 1px solid var(--bg-surface-border);
|
||||
|
||||
[dir=rtl] & {
|
||||
border: {
|
||||
left: none;
|
||||
right: 1px solid var(--bg-surface-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&__member-count {
|
||||
color: var(--tc-surface-low);
|
||||
}
|
||||
|
||||
&__content-wrapper {
|
||||
@extend .people-drawer-flexItem;
|
||||
@extend .people-drawer-flexBox;
|
||||
}
|
||||
|
||||
&__scrollable {
|
||||
@extend .people-drawer-flexItem;
|
||||
}
|
||||
|
||||
&__sticky {
|
||||
display: none;
|
||||
|
||||
& .people-search {
|
||||
min-height: 48px;
|
||||
|
||||
margin: 0 var(--sp-normal);
|
||||
|
||||
position: relative;
|
||||
bottom: var(--sp-normal);
|
||||
|
||||
& .input {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.people-drawer__content {
|
||||
padding-top: var(--sp-extra-tight);
|
||||
padding-bottom: calc( var(--sp-extra-tight) + var(--sp-normal));
|
||||
}
|
||||
.people-drawer__load-more {
|
||||
padding: var(--sp-normal);
|
||||
padding: {
|
||||
bottom: 0;
|
||||
right: var(--sp-extra-tight);
|
||||
}
|
||||
|
||||
[dir=rtl] & {
|
||||
padding-right: var(--sp-normal);
|
||||
padding-left: var(--sp-extra-tight);
|
||||
}
|
||||
|
||||
& .btn-surface {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user