mirror of
https://github.com/revoltchat/revite.git
synced 2024-12-24 22:52:09 -05:00
Port friends menu over.
This commit is contained in:
parent
0ff78787a8
commit
0a0c00fe58
18 changed files with 452 additions and 30 deletions
|
@ -5,7 +5,8 @@
|
|||
"build": "rimraf build && tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
||||
"fmt": "prettier --write 'src/**/*.{js,jsx,ts,tsx}'"
|
||||
"fmt": "prettier --write 'src/**/*.{js,jsx,ts,tsx}'",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
|
@ -54,6 +55,7 @@
|
|||
"markdown-it-emoji": "^2.0.0",
|
||||
"markdown-it-sub": "^1.0.0",
|
||||
"markdown-it-sup": "^1.0.0",
|
||||
"preact-context-menu": "^0.1.5",
|
||||
"preact-i18n": "^2.4.0-preactx",
|
||||
"prettier": "^2.3.1",
|
||||
"prismjs": "^1.23.0",
|
||||
|
|
|
@ -17,7 +17,7 @@ export default styled.div<Props>`
|
|||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
|
||||
background-color: var(--primary-background);
|
||||
background-color: var(--primary-header);
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
|
||||
|
@ -27,6 +27,7 @@ export default styled.div<Props>`
|
|||
` }
|
||||
|
||||
${ props => props.placement === 'secondary' && css`
|
||||
background-color: var(--secondary-header);
|
||||
padding: 14px;
|
||||
` }
|
||||
`;
|
||||
|
|
43
src/components/ui/IconButton.tsx
Normal file
43
src/components/ui/IconButton.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import styled, { css } from "styled-components";
|
||||
|
||||
interface Props {
|
||||
type?: 'default' | 'circle'
|
||||
}
|
||||
|
||||
const normal = `var(--secondary-foreground)`;
|
||||
const hover = `var(--foreground)`;
|
||||
|
||||
export default styled.div<Props>`
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
cursor: pointer;
|
||||
place-items: center;
|
||||
|
||||
fill: ${normal};
|
||||
color: ${normal};
|
||||
stroke: ${normal};
|
||||
|
||||
a {
|
||||
color: ${normal};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
fill: ${hover};
|
||||
color: ${hover};
|
||||
stroke: ${hover};
|
||||
|
||||
a {
|
||||
color: ${hover};
|
||||
}
|
||||
}
|
||||
|
||||
${ props => props.type === 'circle' && css`
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--secondary-header);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-header);
|
||||
}
|
||||
` }
|
||||
`;
|
|
@ -2,7 +2,7 @@ import Button from "./Button";
|
|||
import classNames from "classnames";
|
||||
import { Children } from "../../types/Preact";
|
||||
import { createPortal, useEffect } from "preact/compat";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
import styled, { css, keyframes } from "styled-components";
|
||||
|
||||
const open = keyframes`
|
||||
0% {opacity: 0;}
|
||||
|
@ -48,6 +48,26 @@ const ModalContainer = styled.div`
|
|||
`;
|
||||
|
||||
const ModalContent = styled.div<{ [key in 'attachment' | 'noBackground' | 'border']?: boolean }>`
|
||||
border-radius: 8px;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
${ props => !props.noBackground && css`
|
||||
padding: 1.5em;
|
||||
background: var(--secondary-header);
|
||||
` }
|
||||
|
||||
${ props => props.attachment && css`
|
||||
border-radius: 8px 8px 0 0;
|
||||
` }
|
||||
|
||||
${ props => props.border && css`
|
||||
border-radius: 10px;
|
||||
border: 2px solid var(--secondary-background);
|
||||
` }
|
||||
`;
|
||||
|
||||
const ModalActions = styled.div`
|
||||
|
@ -64,7 +84,8 @@ export interface Action {
|
|||
text: Children;
|
||||
onClick: () => void;
|
||||
confirmation?: boolean;
|
||||
style?: 'default' | 'contrast' | 'error' | 'contrast-error';
|
||||
contrast?: boolean;
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
@ -123,7 +144,9 @@ export default function Modal(props: Props) {
|
|||
{props.actions && (
|
||||
<ModalActions>
|
||||
{props.actions.map(x => (
|
||||
<Button style={x.style ?? "contrast"}
|
||||
<Button
|
||||
contrast={x.contrast ?? true}
|
||||
error={x.error ?? false}
|
||||
onClick={x.onClick}
|
||||
disabled={props.disabled}>
|
||||
{x.text}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import styled, { css } from "styled-components";
|
||||
import { Children } from "../../types/Preact";
|
||||
import { Text } from 'preact-i18n';
|
||||
|
||||
interface Props {
|
||||
error?: string;
|
||||
block?: boolean;
|
||||
error?: Children;
|
||||
children?: Children;
|
||||
type?: "default" | "subtle" | "error";
|
||||
}
|
||||
|
@ -45,7 +46,9 @@ export default function Overline(props: Props) {
|
|||
<OverlineBase {...props}>
|
||||
{props.children}
|
||||
{props.children && props.error && <> · </>}
|
||||
{props.error && <Overline type="error">{props.error}</Overline>}
|
||||
{props.error && <Overline type="error">
|
||||
<Text id={`error.${props.error}`}>{props.error}</Text>
|
||||
</Overline>}
|
||||
</OverlineBase>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export type Screen =
|
|||
{ type: "ban_member", target: Servers.Server, user: string }
|
||||
)) |
|
||||
({ id: "special_input" } & (
|
||||
{ type: "create_group" | "create_server" | "set_custom_status" } |
|
||||
{ type: "create_group" | "create_server" | "set_custom_status" | "add_friend" } |
|
||||
{ type: "create_channel", server: string }
|
||||
))
|
||||
| {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Screen } from "./Intermediate";
|
||||
|
||||
import { ErrorModal } from "./modals/Error";
|
||||
import { InputModal } from "./modals/Input";
|
||||
import { PromptModal } from "./modals/Prompt";
|
||||
import { SignedOutModal } from "./modals/SignedOut";
|
||||
import { ClipboardModal } from "./modals/Clipboard";
|
||||
import { OnboardingModal } from "./modals/Onboarding";
|
||||
import { ModifyAccountModal } from "./modals/ModifyAccount";
|
||||
import { InputModal, SpecialInputModal } from "./modals/Input";
|
||||
import { PromptModal, SpecialPromptModal } from "./modals/Prompt";
|
||||
|
||||
export interface Props {
|
||||
screen: Screen;
|
||||
|
@ -19,12 +19,8 @@ export default function Modals({ screen, openScreen }: Props) {
|
|||
switch (screen.id) {
|
||||
case "_prompt":
|
||||
return <PromptModal onClose={onClose} {...screen} />;
|
||||
case "special_prompt":
|
||||
return <SpecialPromptModal onClose={onClose} {...screen} />;
|
||||
case "_input":
|
||||
return <InputModal onClose={onClose} {...screen} />;
|
||||
case "special_input":
|
||||
return <SpecialInputModal onClose={onClose} {...screen} />;
|
||||
case "error":
|
||||
return <ErrorModal onClose={onClose} {...screen} />;
|
||||
case "signed_out":
|
||||
|
|
|
@ -2,6 +2,8 @@ import { IntermediateContext, useIntermediate } from "./Intermediate";
|
|||
import { useContext } from "preact/hooks";
|
||||
|
||||
import { UserPicker } from "./popovers/UserPicker";
|
||||
import { SpecialInputModal } from "./modals/Input";
|
||||
import { SpecialPromptModal } from "./modals/Prompt";
|
||||
import { UserProfile } from "./popovers/UserProfile";
|
||||
import { ImageViewer } from "./popovers/ImageViewer";
|
||||
import { ChannelInfo } from "./popovers/ChannelInfo";
|
||||
|
@ -21,6 +23,10 @@ export default function Popovers() {
|
|||
return <ImageViewer {...screen} onClose={onClose} />;
|
||||
case "channel_info":
|
||||
return <ChannelInfo {...screen} onClose={onClose} />;
|
||||
case "special_prompt":
|
||||
return <SpecialPromptModal onClose={onClose} {...screen} />;
|
||||
case "special_input":
|
||||
return <SpecialInputModal onClose={onClose} {...screen} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -12,7 +12,7 @@ import { AppContext } from "../../revoltjs/RevoltClient";
|
|||
interface Props {
|
||||
onClose: () => void;
|
||||
question: Children;
|
||||
field: Children;
|
||||
field?: Children;
|
||||
defaultValue?: string;
|
||||
callback: (value: string) => Promise<void>;
|
||||
}
|
||||
|
@ -53,9 +53,9 @@ export function InputModal({
|
|||
]}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Overline error={error} block>
|
||||
{ field ? <Overline error={error} block>
|
||||
{field}
|
||||
</Overline>
|
||||
</Overline> : (error && <Overline error={error} type="error" block />) }
|
||||
<InputBox
|
||||
value={value}
|
||||
onChange={e => setValue(e.currentTarget.value)}
|
||||
|
@ -65,7 +65,7 @@ export function InputModal({
|
|||
}
|
||||
|
||||
type SpecialProps = { onClose: () => void } & (
|
||||
{ type: "create_group" | "create_server" | "set_custom_status" } |
|
||||
{ type: "create_group" | "create_server" | "set_custom_status" | "add_friend" } |
|
||||
{ type: "create_channel", server: string }
|
||||
)
|
||||
|
||||
|
@ -144,6 +144,15 @@ export function SpecialInputModal(props: SpecialProps) {
|
|||
}
|
||||
/>;
|
||||
}
|
||||
case "add_friend": {
|
||||
return <InputModal
|
||||
onClose={onClose}
|
||||
question={"Add Friend"}
|
||||
callback={username =>
|
||||
client.users.addFriend(username)
|
||||
}
|
||||
/>;
|
||||
}
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Text } from "preact-i18n";
|
||||
import styles from './Prompt.module.scss';
|
||||
import { Children } from "../../../types/Preact";
|
||||
import { IntermediateContext, useIntermediate } from "../Intermediate";
|
||||
import { useIntermediate } from "../Intermediate";
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
import Overline from "../../../components/ui/Overline";
|
||||
import UserIcon from "../../../components/common/UserIcon";
|
||||
|
@ -82,7 +82,8 @@ export function SpecialPromptModal(props: SpecialProps) {
|
|||
actions={[
|
||||
{
|
||||
confirmation: true,
|
||||
style: 'contrast-error',
|
||||
contrast: true,
|
||||
error: true,
|
||||
text: <Text id="app.special.modals.actions.delete" />,
|
||||
onClick: async () => {
|
||||
setProcessing(true);
|
||||
|
@ -162,7 +163,8 @@ export function SpecialPromptModal(props: SpecialProps) {
|
|||
actions={[
|
||||
{
|
||||
text: <Text id="app.special.modals.actions.kick" />,
|
||||
style: 'contrast-error',
|
||||
contrast: true,
|
||||
error: true,
|
||||
confirmation: true,
|
||||
onClick: async () => {
|
||||
setProcessing(true);
|
||||
|
@ -200,7 +202,8 @@ export function SpecialPromptModal(props: SpecialProps) {
|
|||
actions={[
|
||||
{
|
||||
text: <Text id="app.special.modals.actions.ban" />,
|
||||
style: 'contrast-error',
|
||||
contrast: true,
|
||||
error: true,
|
||||
confirmation: true,
|
||||
onClick: async () => {
|
||||
setProcessing(true);
|
||||
|
|
4
src/lib/stopPropagation.ts
Normal file
4
src/lib/stopPropagation.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const stopPropagation = (ev: JSX.TargetedMouseEvent<HTMLDivElement>, _consume?: any) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
};
|
|
@ -1,12 +1,22 @@
|
|||
import { Docked, OverlappingPanels } from "react-overlapping-panels";
|
||||
import { isTouchscreenDevice } from "../lib/isTouchscreenDevice";
|
||||
import Popovers from "../context/intermediate/Popovers";
|
||||
import { Switch, Route } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
|
||||
import LeftSidebar from "../components/navigation/LeftSidebar";
|
||||
import RightSidebar from "../components/navigation/RightSidebar";
|
||||
|
||||
import Home from './home/Home';
|
||||
import Popovers from "../context/intermediate/Popovers";
|
||||
import Friends from "./friends/Friends";
|
||||
|
||||
const Routes = styled.div`
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
background: var(--primary-background);
|
||||
`;
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
@ -16,12 +26,76 @@ export default function App() {
|
|||
leftPanel={{ width: 292, component: <LeftSidebar /> }}
|
||||
rightPanel={{ width: 240, component: <RightSidebar /> }}
|
||||
docked={isTouchscreenDevice ? Docked.None : Docked.Left}>
|
||||
<Switch>
|
||||
<Route path="/">
|
||||
<Home />
|
||||
</Route>
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Switch>
|
||||
<Route path="/friends">
|
||||
<Friends />
|
||||
</Route>
|
||||
|
||||
<Route path="/">
|
||||
<Home />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Routes>
|
||||
<Popovers />
|
||||
</OverlappingPanels>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* <Route path="/channel/:channel/message/:message">
|
||||
<ChannelWrapper />
|
||||
</Route>
|
||||
<Route path="/server/:server/channel/:channel/settings/:page">
|
||||
<ChannelSettings key="channel_settings" />
|
||||
</Route>
|
||||
<Route path="/server/:server/channel/:channel/settings">
|
||||
<ChannelSettings key="channel_settings" />
|
||||
</Route>
|
||||
<Route path="/server/:server/settings/:page">
|
||||
<ServerSettings key="channel_settings" />
|
||||
</Route>
|
||||
<Route path="/server/:server/settings">
|
||||
<ServerSettings key="channel_settings" />
|
||||
</Route>
|
||||
<Route path="/channel/:channel/settings/:page">
|
||||
<ChannelSettings key="channel_settings" />
|
||||
</Route>
|
||||
<Route path="/channel/:channel/settings">
|
||||
<ChannelSettings key="channel_settings" />
|
||||
</Route>
|
||||
|
||||
<Route path="/settings/:page">
|
||||
<Settings key="settings" />
|
||||
</Route>
|
||||
<Route path="/settings">
|
||||
<Settings key="settings" />
|
||||
</Route>
|
||||
|
||||
<Route path="/server/:server/channel/:channel">
|
||||
<ChannelWrapper />
|
||||
</Route>
|
||||
<Route path="/server/:server" />
|
||||
<Route path="/channel/:channel">
|
||||
<ChannelWrapper />
|
||||
</Route>
|
||||
|
||||
<Route path="/friends">
|
||||
<Friends />
|
||||
</Route>
|
||||
<Route path="/dev">
|
||||
<Developer />
|
||||
</Route>
|
||||
|
||||
<Route path="/open/:id">
|
||||
<Open />
|
||||
</Route>
|
||||
{/*<Route path="/invite/:code">
|
||||
<OpenInvite />
|
||||
</Route>
|
||||
|
||||
<Route path="/">
|
||||
<Home />
|
||||
</Route>
|
||||
*/
|
||||
|
|
71
src/pages/friends/Friend.module.scss
Normal file
71
src/pages/friends/Friend.module.scss
Normal file
|
@ -0,0 +1,71 @@
|
|||
.list {
|
||||
padding: 16px;
|
||||
user-select: none;
|
||||
overflow-y: scroll;
|
||||
|
||||
&[data-empty="true"] {
|
||||
img {
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.friend {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: var(--secondary-background);
|
||||
|
||||
:global(.button) {
|
||||
background-color: var(--primary-background);
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
margin: 0 12px;
|
||||
font-size: 16px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.subtext {
|
||||
font-size: 12px;
|
||||
color: var(--tertiary-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
> div {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! FIXME: Move this to the Header component, do this:
|
||||
// 1. Check if header has topic, if yes, flex-grow: 0 on the title.
|
||||
// 2. If header has no topic (example: friends page), flex-grow 1 on the header title.
|
||||
.title {
|
||||
flex-grow: 1;
|
||||
}
|
90
src/pages/friends/Friend.tsx
Normal file
90
src/pages/friends/Friend.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { Text } from "preact-i18n";
|
||||
import { Link } from "react-router-dom";
|
||||
import styles from "./Friend.module.scss";
|
||||
import { useContext } from "preact/hooks";
|
||||
import { Children } from "../../types/Preact";
|
||||
import { X, Plus, Mail } from "@styled-icons/feather";
|
||||
import UserIcon from "../../components/common/UserIcon";
|
||||
import IconButton from "../../components/ui/IconButton";
|
||||
import { attachContextMenu } from "preact-context-menu";
|
||||
import { User, Users } from "revolt.js/dist/api/objects";
|
||||
import UserStatus from '../../components/common/UserStatus';
|
||||
import { stopPropagation } from "../../lib/stopPropagation";
|
||||
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
||||
import { useIntermediate } from "../../context/intermediate/Intermediate";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export function Friend({ user }: Props) {
|
||||
const client = useContext(AppContext);
|
||||
const { openScreen } = useIntermediate();
|
||||
|
||||
const actions: Children[] = [];
|
||||
let subtext: Children = null;
|
||||
|
||||
if (user.relationship === Users.Relationship.Friend) {
|
||||
subtext = <UserStatus user={user} />
|
||||
actions.push(
|
||||
<IconButton type="circle"
|
||||
onClick={stopPropagation}>
|
||||
<Link to={'/open/' + user._id}>
|
||||
<Mail size={20} />
|
||||
</Link>
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (user.relationship === Users.Relationship.Incoming) {
|
||||
actions.push(
|
||||
<IconButton type="circle"
|
||||
onClick={ev => stopPropagation(ev, client.users.addFriend(user.username))}>
|
||||
<Plus size={24} />
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
subtext = <Text id="app.special.friends.incoming" />;
|
||||
}
|
||||
|
||||
if (user.relationship === Users.Relationship.Outgoing) {
|
||||
subtext = <Text id="app.special.friends.outgoing" />;
|
||||
}
|
||||
|
||||
if (
|
||||
user.relationship === Users.Relationship.Friend ||
|
||||
user.relationship === Users.Relationship.Outgoing ||
|
||||
user.relationship === Users.Relationship.Incoming
|
||||
) {
|
||||
actions.push(
|
||||
<IconButton type="circle"
|
||||
onClick={ev => stopPropagation(ev, client.users.removeFriend(user._id))}>
|
||||
<X size={24} />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (user.relationship === Users.Relationship.Blocked) {
|
||||
actions.push(
|
||||
<IconButton type="circle"
|
||||
onClick={ev => stopPropagation(ev, client.users.unblockUser(user._id))}>
|
||||
<X size={24} />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.friend}
|
||||
onClick={() => openScreen({ id: 'profile', user_id: user._id })}
|
||||
onContextMenu={attachContextMenu('Menu', { user: user._id })}>
|
||||
<UserIcon target={user} size={32} status />
|
||||
<div className={styles.name}>
|
||||
<span>@{user.username}</span>
|
||||
{subtext && (
|
||||
<span className={styles.subtext}>{subtext}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.actions}>{actions}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
85
src/pages/friends/Friends.tsx
Normal file
85
src/pages/friends/Friends.tsx
Normal file
|
@ -0,0 +1,85 @@
|
|||
import styles from "./Friend.module.scss";
|
||||
import { UserPlus } from "@styled-icons/feather";
|
||||
|
||||
import { Friend } from "./Friend";
|
||||
import { Text } from "preact-i18n";
|
||||
import Header from "../../components/ui/Header";
|
||||
import Overline from "../../components/ui/Overline";
|
||||
import IconButton from "../../components/ui/IconButton";
|
||||
import { useUsers } from "../../context/revoltjs/hooks";
|
||||
import { User, Users } from "revolt.js/dist/api/objects";
|
||||
import { useIntermediate } from "../../context/intermediate/Intermediate";
|
||||
|
||||
export default function Friends() {
|
||||
const { openScreen } = useIntermediate();
|
||||
|
||||
const users = useUsers() as User[];
|
||||
users.sort((a, b) => a.username.localeCompare(b.username));
|
||||
|
||||
const pending = users.filter(
|
||||
x =>
|
||||
x.relationship === Users.Relationship.Incoming ||
|
||||
x.relationship === Users.Relationship.Outgoing
|
||||
);
|
||||
const friends = users.filter(
|
||||
x => x.relationship === Users.Relationship.Friend
|
||||
);
|
||||
const blocked = users.filter(
|
||||
x => x.relationship === Users.Relationship.Blocked
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header placement="primary">
|
||||
<div className={styles.title}>
|
||||
<Text id="app.navigation.tabs.friends" />
|
||||
</div>
|
||||
<div className="actions">
|
||||
<IconButton onClick={() => openScreen({ id: 'special_input', type: 'add_friend' })}>
|
||||
<UserPlus size={24} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Header>
|
||||
<div
|
||||
className={styles.list}
|
||||
data-empty={
|
||||
pending.length + friends.length + blocked.length === 0
|
||||
}
|
||||
>
|
||||
{pending.length + friends.length + blocked.length === 0 && (
|
||||
<>
|
||||
<img src="https://img.insrt.uk/xexu7/XOPoBUTI47.png/raw" />
|
||||
<Text id="app.special.friends.nobody" />
|
||||
</>
|
||||
)}
|
||||
{pending.length > 0 && (
|
||||
<Overline type="subtle">
|
||||
<Text id="app.special.friends.pending" /> –{" "}
|
||||
{pending.length}
|
||||
</Overline>
|
||||
)}
|
||||
{pending.map(y => (
|
||||
<Friend key={y._id} user={y} />
|
||||
))}
|
||||
{friends.length > 0 && (
|
||||
<Overline type="subtle">
|
||||
<Text id="app.navigation.tabs.friends" /> –{" "}
|
||||
{friends.length}
|
||||
</Overline>
|
||||
)}
|
||||
{friends.map(y => (
|
||||
<Friend key={y._id} user={y} />
|
||||
))}
|
||||
{blocked.length > 0 && (
|
||||
<Overline type="subtle">
|
||||
<Text id="app.special.friends.blocked" /> –{" "}
|
||||
{blocked.length}
|
||||
</Overline>
|
||||
)}
|
||||
{blocked.map(y => (
|
||||
<Friend key={y._id} user={y} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,3 +1,8 @@
|
|||
:disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { VNode } from "preact";
|
||||
|
||||
export type Child = VNode | string | false | undefined;
|
||||
export type Child = VNode | string | number | boolean | undefined | null;
|
||||
export type Children = Child | Child[] | Children[];
|
||||
|
|
|
@ -3038,6 +3038,13 @@ postcss@^8.3.0:
|
|||
nanoid "^3.1.23"
|
||||
source-map-js "^0.6.2"
|
||||
|
||||
preact-context-menu@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/preact-context-menu/-/preact-context-menu-0.1.5.tgz#51d13b0eceed8bd53493f2bdbce36e7c74ff8575"
|
||||
integrity sha512-cQxcOf4w8MZAQtLpQeX80TQ2TzSKD/z6rnV+654xiHzqQp2pSV+qE0IEZLkUNz88ZmtpIy6GnB/6pCnCGjS4Ww==
|
||||
dependencies:
|
||||
preact "^10.4.6"
|
||||
|
||||
preact-i18n@^2.4.0-preactx:
|
||||
version "2.4.0-preactx"
|
||||
resolved "https://registry.yarnpkg.com/preact-i18n/-/preact-i18n-2.4.0-preactx.tgz#fbcb2e3ae22744c7fef5a102db2ef7506057d082"
|
||||
|
@ -3051,7 +3058,7 @@ preact-markup@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/preact-markup/-/preact-markup-2.1.1.tgz#0451e7eed1dac732d7194c34a7f16ff45a2cfdd7"
|
||||
integrity sha512-8JL2p36mzK8XkspOyhBxUSPjYwMxDM0L5BWBZWxsZMVW8WsGQrYQDgVuDKkRspt2hwrle+Cxr/053hpc9BJwfw==
|
||||
|
||||
preact@^10.0.0, preact@^10.5.13:
|
||||
preact@^10.0.0, preact@^10.4.6, preact@^10.5.13:
|
||||
version "10.5.13"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.5.13.tgz#85f6c9197ecd736ce8e3bec044d08fd1330fa019"
|
||||
integrity sha512-q/vlKIGNwzTLu+jCcvywgGrt+H/1P/oIRSD6mV4ln3hmlC+Aa34C7yfPI4+5bzW8pONyVXYS7SvXosy2dKKtWQ==
|
||||
|
|
Loading…
Reference in a new issue