Port navigation.

This commit is contained in:
Paul
2021-06-19 15:29:04 +01:00
parent 5aa8f30e14
commit 5b77ed439f
25 changed files with 1341 additions and 42 deletions

View File

@@ -1,7 +1,7 @@
import { useContext } from "preact/hooks";
import { Hash } from "@styled-icons/feather";
import IconBase, { IconBaseProps } from "./IconBase";
import { Channels } from "revolt.js/dist/api/objects";
import { ImageIconBase, IconBaseProps } from "./IconBase";
import { AppContext } from "../../context/revoltjs/RevoltClient";
interface Props extends IconBaseProps<Channels.GroupChannel | Channels.TextChannel> {
@@ -9,10 +9,10 @@ interface Props extends IconBaseProps<Channels.GroupChannel | Channels.TextChann
}
const fallback = '/assets/group.png';
export default function ChannelIcon(props: Props & Omit<JSX.SVGAttributes<SVGSVGElement>, keyof Props>) {
export default function ChannelIcon(props: Props & Omit<JSX.HTMLAttributes<HTMLImageElement>, keyof Props>) {
const { client } = useContext(AppContext);
const { size, target, attachment, isServerChannel: server, animate, children, as, ...svgProps } = props;
const { size, target, attachment, isServerChannel: server, animate, children, as, ...imgProps } = props;
const iconURL = client.generateFileURL(target?.icon ?? attachment, { max_side: 256 }, animate);
const isServerChannel = server || target?.channel_type === 'TextChannel';
@@ -25,21 +25,18 @@ export default function ChannelIcon(props: Props & Omit<JSX.SVGAttributes<SVGSVG
}
return (
<IconBase {...svgProps}
// ! fixme: replace fallback with <picture /> + <source />
<ImageIconBase {...imgProps}
width={size}
height={size}
aria-hidden="true"
viewBox="0 0 32 32"
square={isServerChannel}>
<foreignObject x="0" y="0" width="32" height="32">
<img src={iconURL ?? fallback}
onError={ e => {
let el = e.currentTarget;
if (el.src !== fallback) {
el.src = fallback
}
} } />
</foreignObject>
</IconBase>
square={isServerChannel}
src={iconURL ?? fallback}
onError={ e => {
let el = e.currentTarget;
if (el.src !== fallback) {
el.src = fallback
}
}} />
);
}

View File

@@ -9,7 +9,11 @@ export interface IconBaseProps<T> {
animate?: boolean;
}
export default styled.svg<{ square?: boolean }>`
interface IconModifiers {
square?: boolean
}
export default styled.svg<IconModifiers>`
img {
width: 100%;
height: 100%;
@@ -20,3 +24,11 @@ export default styled.svg<{ square?: boolean }>`
` }
}
`;
export const ImageIconBase = styled.img<IconModifiers>`
object-fit: cover;
${ props => !props.square && css`
border-radius: 50%;
` }
`;

View File

@@ -1,7 +1,7 @@
import styled from "styled-components";
import { useContext } from "preact/hooks";
import { Server } from "revolt.js/dist/api/objects";
import IconBase, { IconBaseProps } from "./IconBase";
import { IconBaseProps, ImageIconBase } from "./IconBase";
import { AppContext } from "../../context/revoltjs/RevoltClient";
interface Props extends IconBaseProps<Server> {
@@ -19,10 +19,10 @@ const ServerText = styled.div`
`;
const fallback = '/assets/group.png';
export default function ServerIcon(props: Props & Omit<JSX.SVGAttributes<SVGSVGElement>, keyof Props>) {
export default function ServerIcon(props: Props & Omit<JSX.HTMLAttributes<HTMLImageElement>, keyof Props>) {
const { client } = useContext(AppContext);
const { target, attachment, size, animate, server_name, children, as, ...svgProps } = props;
const { target, attachment, size, animate, server_name, children, as, ...imgProps } = props;
const iconURL = client.generateFileURL(target?.icon ?? attachment, { max_side: 256 }, animate);
if (typeof iconURL === 'undefined') {
@@ -38,20 +38,16 @@ export default function ServerIcon(props: Props & Omit<JSX.SVGAttributes<SVGSVGE
}
return (
<IconBase {...svgProps}
<ImageIconBase {...imgProps}
width={size}
height={size}
aria-hidden="true"
viewBox="0 0 32 32">
<foreignObject x="0" y="0" width="32" height="32">
<img src={iconURL}
onError={ e => {
let el = e.currentTarget;
if (el.src !== fallback) {
el.src = fallback
}
}} />
</foreignObject>
</IconBase>
src={iconURL}
onError={ e => {
let el = e.currentTarget;
if (el.src !== fallback) {
el.src = fallback
}
}} />
);
}

View File

@@ -0,0 +1,79 @@
import { User } from "revolt.js";
import Header from "../ui/Header";
import UserIcon from "./UserIcon";
import UserStatus from './UserStatus';
import styled from "styled-components";
import { Localizer } from 'preact-i18n';
import { Settings } from "@styled-icons/feather";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
const HeaderBase = styled.div`
gap: 0;
flex-grow: 1;
min-width: 0;
display: flex;
flex-direction: column;
* {
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.username {
cursor: pointer;
font-size: 16px;
font-weight: 600;
}
.status {
cursor: pointer;
font-size: 12px;
margin-top: -2px;
}
`;
interface Props {
user: User
}
export default function UserHeader({ user }: Props) {
function openPresenceSelector() {
// openContextMenu("Status");
}
function writeClipboard(a: string) {
alert('unimplemented');
}
return (
<Header placement="secondary">
<UserIcon
target={user}
size={32}
status
onClick={openPresenceSelector}
/>
<HeaderBase>
<Localizer>
{/*<Tooltip content={<Text id="app.special.copy_username" />}>*/}
<span className="username"
onClick={() => writeClipboard(user.username)}>
@{user.username}
</span>
{/*</Tooltip>*/}
</Localizer>
<span className="status"
onClick={openPresenceSelector}>
<UserStatus user={user} />
</span>
</HeaderBase>
{ !isTouchscreenDevice && <div className="actions">
{/*<IconButton to="/settings">*/}
<Settings size={24} />
{/*</IconButton>*/}
</div> }
</Header>
)
}

View File

@@ -52,7 +52,8 @@ export default function UserIcon(props: Props & Omit<JSX.SVGAttributes<SVGSVGEle
const { client } = useContext(AppContext);
const { target, attachment, size, voice, status, animate, children, as, ...svgProps } = props;
const iconURL = client.generateFileURL(target?.avatar ?? attachment, { max_side: 256 }, animate);
const iconURL = client.generateFileURL(target?.avatar ?? attachment, { max_side: 256 }, animate)
?? client.users.getDefaultAvatarURL(target!._id);
return (
<IconBase {...svgProps}

View File

@@ -0,0 +1,31 @@
import { User } from "revolt.js";
import { Text } from "preact-i18n";
import { Users } from "revolt.js/dist/api/objects";
interface Props {
user: User;
}
export default function UserStatus({ user }: Props) {
if (user.online) {
if (user.status?.text) {
return <>{user.status?.text}</>;
}
if (user.status?.presence === Users.Presence.Busy) {
return <Text id="app.status.busy" />;
}
if (user.status?.presence === Users.Presence.Idle) {
return <Text id="app.status.idle" />;
}
if (user.status?.presence === Users.Presence.Invisible) {
return <Text id="app.status.offline" />;
}
return <Text id="app.status.online" />;
}
return <Text id="app.status.offline" />;
}