From 31d8950ea1786914e952d0ade6bfa0e7db2c792d Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 19 Jun 2021 22:37:12 +0100 Subject: [PATCH] Port settings. --- package.json | 2 + .../navigation/items/ButtonItem.tsx | 38 +- .../navigation/left/HomeSidebar.tsx | 10 +- .../navigation/left/ServerListSidebar.tsx | 7 +- .../navigation/left/ServerSidebar.tsx | 31 +- src/components/ui/Checkbox.tsx | 15 +- src/components/ui/Modal.tsx | 10 +- src/components/ui/TextArea.module.scss | 31 ++ src/components/ui/TextArea.tsx | 146 +++++++ src/context/Theme.tsx | 19 +- .../intermediate/modals/Onboarding.tsx | 3 +- .../popovers/UserProfile.module.scss | 1 + src/context/revoltjs/FileUploads.module.scss | 82 ++++ src/context/revoltjs/FileUploads.tsx | 148 +++++++ src/context/revoltjs/RequiresOnline.tsx | 44 +++ src/lib/conversion.ts | 9 + src/lib/debounce.ts | 15 + src/lib/fileSize.ts | 9 + src/main.tsx | 2 +- src/pages/{App.tsx => RevoltApp.tsx} | 61 +-- src/{ => pages}/app.tsx | 10 +- src/pages/home/Home.tsx | 3 +- src/pages/login/forms/FormLogin.tsx | 4 +- src/pages/settings/ChannelSettings.tsx | 46 +++ src/pages/settings/GenericSettings.tsx | 120 ++++++ src/pages/settings/ServerSettings.tsx | 65 +++ src/pages/settings/Settings.module.scss | 209 ++++++++++ src/pages/settings/Settings.tsx | 153 +++++++ src/pages/settings/SettingsTextArea.tsx | 6 + src/pages/settings/channel/Overview.tsx | 89 +++++ src/pages/settings/channel/Panes.module.scss | 14 + src/pages/settings/panes/Account.tsx | 95 +++++ src/pages/settings/panes/Appearance.tsx | 286 ++++++++++++++ src/pages/settings/panes/Experiments.tsx | 53 +++ src/pages/settings/panes/Feedback.tsx | 95 +++++ src/pages/settings/panes/Languages.tsx | 68 ++++ src/pages/settings/panes/Notifications.tsx | 142 +++++++ src/pages/settings/panes/Panes.module.scss | 374 ++++++++++++++++++ src/pages/settings/panes/Profile.tsx | 126 ++++++ src/pages/settings/panes/Sessions.tsx | 186 +++++++++ src/pages/settings/panes/Sync.tsx | 53 +++ src/pages/settings/server/Bans.tsx | 23 ++ src/pages/settings/server/Invites.tsx | 70 ++++ src/pages/settings/server/Members.tsx | 24 ++ src/pages/settings/server/Overview.tsx | 98 +++++ src/pages/settings/server/Panes.module.scss | 48 +++ src/version.ts | 2 +- yarn.lock | 17 +- 48 files changed, 3056 insertions(+), 106 deletions(-) create mode 100644 src/components/ui/TextArea.module.scss create mode 100644 src/components/ui/TextArea.tsx create mode 100644 src/context/revoltjs/FileUploads.module.scss create mode 100644 src/context/revoltjs/FileUploads.tsx create mode 100644 src/context/revoltjs/RequiresOnline.tsx create mode 100644 src/lib/conversion.ts create mode 100644 src/lib/debounce.ts create mode 100644 src/lib/fileSize.ts rename src/pages/{App.tsx => RevoltApp.tsx} (50%) rename src/{ => pages}/app.tsx (74%) create mode 100644 src/pages/settings/ChannelSettings.tsx create mode 100644 src/pages/settings/GenericSettings.tsx create mode 100644 src/pages/settings/ServerSettings.tsx create mode 100644 src/pages/settings/Settings.module.scss create mode 100644 src/pages/settings/Settings.tsx create mode 100644 src/pages/settings/SettingsTextArea.tsx create mode 100644 src/pages/settings/channel/Overview.tsx create mode 100644 src/pages/settings/channel/Panes.module.scss create mode 100644 src/pages/settings/panes/Account.tsx create mode 100644 src/pages/settings/panes/Appearance.tsx create mode 100644 src/pages/settings/panes/Experiments.tsx create mode 100644 src/pages/settings/panes/Feedback.tsx create mode 100644 src/pages/settings/panes/Languages.tsx create mode 100644 src/pages/settings/panes/Notifications.tsx create mode 100644 src/pages/settings/panes/Panes.module.scss create mode 100644 src/pages/settings/panes/Profile.tsx create mode 100644 src/pages/settings/panes/Sessions.tsx create mode 100644 src/pages/settings/panes/Sync.tsx create mode 100644 src/pages/settings/server/Bans.tsx create mode 100644 src/pages/settings/server/Invites.tsx create mode 100644 src/pages/settings/server/Members.tsx create mode 100644 src/pages/settings/server/Overview.tsx create mode 100644 src/pages/settings/server/Panes.module.scss diff --git a/package.json b/package.json index 8862bde6..c639394e 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@preact/preset-vite": "^2.0.0", "@styled-icons/bootstrap": "^10.34.0", "@styled-icons/feather": "^10.34.0", + "@styled-icons/simple-icons": "^10.33.0", "@traptitech/markdown-it-katex": "^3.4.3", "@traptitech/markdown-it-spoiler": "^1.1.6", "@types/markdown-it": "^12.0.2", @@ -70,6 +71,7 @@ "revolt.js": "4.3.0", "rimraf": "^3.0.2", "sass": "^1.35.1", + "shade-blend-color": "^1.0.0", "styled-components": "^5.3.0", "twemoji": "^13.1.0", "typescript": "^4.3.2", diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx index 0acfbf02..bcbd59aa 100644 --- a/src/components/navigation/items/ButtonItem.tsx +++ b/src/components/navigation/items/ButtonItem.tsx @@ -1,13 +1,17 @@ import classNames from 'classnames'; import styles from "./Item.module.scss"; +import Tooltip from '../../common/Tooltip'; +import IconButton from '../../ui/IconButton'; import UserIcon from '../../common/UserIcon'; import { Localizer, Text } from "preact-i18n"; import { X, Zap } from "@styled-icons/feather"; import UserStatus from '../../common/UserStatus'; import { Children } from "../../../types/Preact"; import ChannelIcon from '../../common/ChannelIcon'; +import { attachContextMenu } from 'preact-context-menu'; import { Channels, Users } from "revolt.js/dist/api/objects"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; +import { useIntermediate } from '../../../context/intermediate/Intermediate'; interface CommonProps { active?: boolean @@ -22,7 +26,7 @@ type UserProps = CommonProps & { } export function UserButton({ active, alert, alertCount, user, context, channel }: UserProps) { - // const { openScreen } = useContext(IntermediateContext); + const { openScreen } = useIntermediate(); return (
+ })}>
@@ -56,24 +59,22 @@ export function UserButton({ active, alert, alertCount, user, context, channel } { context?.channel_type === "Group" && context.owner === user._id && ( - {/* } - >*/} + > - {/**/} + )} {alert &&
{ alertCount }
} { !isTouchscreenDevice && channel && - /* openScreen({ id: 'special_prompt', type: 'close_dm', target: channel })} - >*/ + onClick={() => openScreen({ id: 'special_prompt', type: 'close_dm', target: channel })}> - /**/ + }
@@ -93,15 +94,14 @@ export function ChannelButton({ active, alert, alertCount, channel, user, compac return } - //const { openScreen } = useContext(IntermediateContext); + const { openScreen } = useIntermediate(); return (
- > + onContextMenu={attachContextMenu('Menu', { channel: channel._id })}>
@@ -124,13 +124,11 @@ export function ChannelButton({ active, alert, alertCount, channel, user, compac
{alert &&
{ alertCount }
} {!isTouchscreenDevice && channel.channel_type === "Group" && ( - /* openScreen({ id: 'special_prompt', type: 'leave_group', target: channel })} - >*/ + onClick={() => openScreen({ id: 'special_prompt', type: 'leave_group', target: channel })}> - /**/ + )}
diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index bf1b99ce..d014de10 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -1,24 +1,20 @@ import { Localizer, Text } from "preact-i18n"; -import { useContext, useLayoutEffect } from "preact/hooks"; -import { Home, Users, Tool, Settings, Save } from "@styled-icons/feather"; +import { useContext } from "preact/hooks"; +import { Home, Users, Tool, Save } from "@styled-icons/feather"; -import { Link, Redirect, useHistory, useLocation, useParams } from "react-router-dom"; +import { Link, Redirect, useLocation, useParams } from "react-router-dom"; import { WithDispatcher } from "../../../redux/reducers"; import { Unreads } from "../../../redux/reducers/unreads"; import { connectState } from "../../../redux/connector"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { useChannels, useForceUpdate, useUsers } from "../../../context/revoltjs/hooks"; -import { User } from "revolt.js"; import { Users as UsersNS } from 'revolt.js/dist/api/objects'; import { mapChannelWithUnread, useUnreads } from "./common"; import { Channels } from "revolt.js/dist/api/objects"; -import UserIcon from '../../common/UserIcon'; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import ConnectionStatus from '../items/ConnectionStatus'; -import UserStatus from '../../common/UserStatus'; import ButtonItem, { ChannelButton } from '../items/ButtonItem'; import styled from "styled-components"; -import Header from '../../ui/Header'; import UserHeader from "../../common/UserHeader"; import Category from '../../ui/Category'; import PaintCounter from "../../../lib/PaintCounter"; diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index 7ff3e74d..f64e4fef 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -1,5 +1,3 @@ -import { useContext } from "preact/hooks"; -import { PlusCircle } from "@styled-icons/feather"; import { Channel, Servers } from "revolt.js/dist/api/objects"; import { Link, useLocation, useParams } from "react-router-dom"; import { useChannels, useForceUpdate, useServers } from "../../../context/revoltjs/hooks"; @@ -11,6 +9,7 @@ import { Children } from "../../../types/Preact"; import LineDivider from "../../ui/LineDivider"; import ServerIcon from "../../common/ServerIcon"; import PaintCounter from "../../../lib/PaintCounter"; +import { attachContextMenu } from 'preact-context-menu'; function Icon({ children, unread, size }: { children: Children, unread?: 'mention' | 'unread', size: number }) { return ( @@ -157,8 +156,7 @@ export function ServerListSidebar({ unreads }: Props) { - > + onContextMenu={attachContextMenu('Menu', { server: entry!._id })}> @@ -169,6 +167,7 @@ export function ServerListSidebar({ unreads }: Props) { + // ! FIXME: add overlay back /*
diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx index e5008550..f4cae5fd 100644 --- a/src/components/navigation/left/ServerSidebar.tsx +++ b/src/components/navigation/left/ServerSidebar.tsx @@ -12,11 +12,32 @@ import Header from '../../ui/Header'; import ConnectionStatus from '../items/ConnectionStatus'; import { connectState } from "../../../redux/connector"; import PaintCounter from "../../../lib/PaintCounter"; +import styled from "styled-components"; +import { attachContextMenu } from 'preact-context-menu'; interface Props { unreads: Unreads; } +const ServerBase = styled.div` + height: 100%; + width: 240px; + display: flex; + flex-shrink: 0; + flex-direction: column; + background: var(--secondary-background); +`; + +const ServerList = styled.div` + padding: 6px; + flex-grow: 1; + overflow-y: scroll; + + > svg { + width: 100%; + } +`; + function ServerSidebar(props: Props & WithDispatcher) { const { server: server_id, channel: channel_id } = useParams<{ server?: string, channel?: string }>(); const ctx = useForceUpdate(); @@ -33,7 +54,7 @@ function ServerSidebar(props: Props & WithDispatcher) { if (channel) useUnreads({ ...props, channel }, ctx); return ( -
+
{ server.name } @@ -45,9 +66,7 @@ function ServerSidebar(props: Props & WithDispatcher) {
}
-
- > + {channels.map(entry => { return ( @@ -61,9 +80,9 @@ function ServerSidebar(props: Props & WithDispatcher) { ); })} -
+ -
+ ) }; diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx index fb9adf31..e7cd62b9 100644 --- a/src/components/ui/Checkbox.tsx +++ b/src/components/ui/Checkbox.tsx @@ -23,6 +23,14 @@ const CheckboxBase = styled.label` input { display: none; } + + &:hover { + background: var(--secondary-background); + + .check { + background: var(--background); + } + } `; const CheckboxContent = styled.span` @@ -46,6 +54,7 @@ const Checkmark = styled.div<{ checked: boolean }>` display: grid; border-radius: 4px; place-items: center; + transition: 0.2s ease all; background: var(--secondary-background); svg { @@ -56,7 +65,7 @@ const Checkmark = styled.div<{ checked: boolean }>` ${(props) => props.checked && css` - background: var(--accent); + background: var(--accent) !important; `} `; @@ -71,7 +80,7 @@ export interface CheckboxProps { export default function Checkbox(props: CheckboxProps) { return ( - + {props.children} {props.description && ( @@ -87,7 +96,7 @@ export default function Checkbox(props: CheckboxProps) { !props.disabled && props.onChange(!props.checked) } /> - + diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index 7d80cb81..26197b2b 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -47,7 +47,7 @@ const ModalContainer = styled.div` animation-timing-function: cubic-bezier(.3,.3,.18,1.1); `; -const ModalContent = styled.div<{ [key in 'attachment' | 'noBackground' | 'border']?: boolean }>` +const ModalContent = styled.div<{ [key in 'attachment' | 'noBackground' | 'border' | 'padding']?: boolean }>` border-radius: 8px; text-overflow: ellipsis; @@ -56,10 +56,13 @@ const ModalContent = styled.div<{ [key in 'attachment' | 'noBackground' | 'borde } ${ props => !props.noBackground && css` - padding: 1.5em; background: var(--secondary-header); ` } + ${ props => props.padding && css` + padding: 1.5em; + ` } + ${ props => props.attachment && css` border-radius: 8px 8px 0 0; ` } @@ -110,7 +113,8 @@ export default function Modal(props: Props) { + border={props.border} + padding={!props.dontModal}> {props.title &&

{props.title}

} {props.children}
diff --git a/src/components/ui/TextArea.module.scss b/src/components/ui/TextArea.module.scss new file mode 100644 index 00000000..7e009fd5 --- /dev/null +++ b/src/components/ui/TextArea.module.scss @@ -0,0 +1,31 @@ +.container { + font-size: 0.875rem; + line-height: 20px; + position: relative; +} + +.textarea { + width: 100%; + white-space: pre-wrap; + + textarea::placeholder { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } +} + +.hide { + width: 100%; + overflow: hidden; + position: relative; +} + +.ghost { + width: 100%; + white-space: pre-wrap; + + top: 0; + position: absolute; + visibility: hidden; +} diff --git a/src/components/ui/TextArea.tsx b/src/components/ui/TextArea.tsx new file mode 100644 index 00000000..d88dcb7b --- /dev/null +++ b/src/components/ui/TextArea.tsx @@ -0,0 +1,146 @@ +// ! FIXME: temporarily here until re-written +// ! DO NOT IMRPOVE, JUST RE-WRITE + +import classNames from "classnames"; +import { memo } from "preact/compat"; +import styles from "./TextArea.module.scss"; +import { useState, useEffect, useRef, useLayoutEffect } from "preact/hooks"; + +export interface TextAreaProps { + id?: string; + value: string; + maxRows?: number; + padding?: number; + minHeight?: number; + disabled?: boolean; + maxLength?: number; + className?: string; + autoFocus?: boolean; + forceFocus?: boolean; + placeholder?: string; + onKeyDown?: (ev: KeyboardEvent) => void; + onKeyUp?: (ev: KeyboardEvent) => void; + onChange: ( + value: string, + ev: JSX.TargetedEvent + ) => void; + onFocus?: (current: HTMLTextAreaElement) => void; + onBlur?: () => void; +} + +const lineHeight = 20; + +export const TextArea = memo((props: TextAreaProps) => { + const padding = props.padding ? props.padding * 2 : 0; + + const [height, setHeightState] = useState( + props.minHeight ?? lineHeight + padding + ); + const ghost = useRef(); + const ref = useRef(); + + function setHeight(h: number = lineHeight) { + let newHeight = Math.min( + Math.max( + lineHeight, + props.maxRows ? Math.min(h, props.maxRows * lineHeight) : h + ), + props.minHeight ?? Infinity + ); + + if (props.padding) newHeight += padding; + if (height !== newHeight) { + setHeightState(newHeight); + } + } + + function onChange(ev: JSX.TargetedEvent) { + props.onChange(ev.currentTarget.value, ev); + } + + useLayoutEffect(() => { + setHeight(ghost.current.clientHeight); + }, [ghost, props.value]); + + useEffect(() => { + if (props.autoFocus) ref.current.focus(); + }, [props.value]); + + const inputSelected = () => + ["TEXTAREA", "INPUT"].includes(document.activeElement?.nodeName ?? ""); + + useEffect(() => { + if (props.forceFocus) { + ref.current.focus(); + } + + if (props.autoFocus && !inputSelected()) { + ref.current.focus(); + } + + // ? if you are wondering what this is + // ? it is a quick and dirty hack to fix + // ? value not setting correctly + // ? I have no clue what's going on + ref.current.value = props.value; + + if (!props.autoFocus) return; + function keyDown(e: KeyboardEvent) { + if ((e.ctrlKey && e.key !== "v") || e.altKey || e.metaKey) return; + if (e.key.length !== 1) return; + if (ref && !inputSelected()) { + ref.current.focus(); + } + } + + document.body.addEventListener("keydown", keyDown); + return () => document.body.removeEventListener("keydown", keyDown); + }, [ref]); + + useEffect(() => { + function focus(textarea_id: string) { + if (props.id === textarea_id) { + ref.current.focus(); + } + } + + // InternalEventEmitter.addListener("focus_textarea", focus); + // return () => + // InternalEventEmitter.removeListener("focus_textarea", focus); + }, [ref]); + + return ( +
+