mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-21 14:40:58 -05:00
feat(settings): UI improvements (#448)
* Fixed CSS for Settings.tsx + new Theme Shop design * reformat * More changes to UI CSS * Small CSS fixes for Settings.tsx, Account, Bots * Updated theme shop, settings pages, cleanup * chore: force sync language submodule * fix(sidebar): prevent items from shrinking * fix(push): fix timestamp and icon for push notifications * fix(voice): hide grant permission button after grant * chore: hide new shop / chevron before merge * chore(ci): bump node to v16 in dockerfile * fix(sidebar): change width of channel sidebar Co-authored-by: trashtemp <96388163+trashtemp@users.noreply.github.com>
This commit is contained in:
parent
535a40df0c
commit
9298f205fc
23 changed files with 744 additions and 272 deletions
|
@ -12,7 +12,7 @@ RUN yarn typecheck
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
RUN npm prune --production
|
RUN npm prune --production
|
||||||
|
|
||||||
FROM node:15-buster
|
FROM node:16-buster
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
COPY --from=builder /usr/src/app .
|
COPY --from=builder /usr/src/app .
|
||||||
|
|
||||||
|
|
2
external/lang
vendored
2
external/lang
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 40afd0527defb106142c50b979bbfc6283e9e48d
|
Subproject commit 9b9858c64503783364dffba18b2a5356d11cdc30
|
|
@ -14,7 +14,7 @@ export const GenericSidebarBase = styled.div<{
|
||||||
mobilePadding?: boolean;
|
mobilePadding?: boolean;
|
||||||
}>`
|
}>`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 240px;
|
width: 236px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -79,7 +79,9 @@ const ServersBase = styled.div`
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
${isTouchscreenDevice &&
|
${isTouchscreenDevice &&
|
||||||
|
|
|
@ -32,7 +32,7 @@ interface Props {
|
||||||
|
|
||||||
const ServerBase = styled.div`
|
const ServerBase = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 240px;
|
width: 236px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default styled.button<Props>`
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
transition: 0.2s ease opacity;
|
transition: 0.2s ease opacity;
|
||||||
transition: 0.2s ease background-color;
|
transition: 0.2s ease background-color;
|
||||||
|
|
|
@ -10,7 +10,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default styled.div<Props>`
|
export default styled.div<Props>`
|
||||||
gap: 6px;
|
gap: 10px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
flex: 0 auto;
|
flex: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -49,9 +49,9 @@ export const TipBase = styled.div<Props>`
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.error &&
|
props.error &&
|
||||||
css`
|
css`
|
||||||
color: var(--error);
|
color: white;
|
||||||
border: 2px solid var(--error);
|
border: 2px solid var(--error);
|
||||||
background: var(--secondary-header);
|
background: var(--error);
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,10 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
|
@ -58,7 +58,7 @@ export default function App() {
|
||||||
leftPanel={
|
leftPanel={
|
||||||
inSpecial
|
inSpecial
|
||||||
? undefined
|
? undefined
|
||||||
: { width: 292, component: <LeftSidebar /> }
|
: { width: 288, component: <LeftSidebar /> }
|
||||||
}
|
}
|
||||||
rightPanel={
|
rightPanel={
|
||||||
!inSpecial && inChannel
|
!inSpecial && inChannel
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { At, Hash, Menu } from "@styled-icons/boxicons-regular";
|
import { At, Hash, Menu, ChevronLeft } from "@styled-icons/boxicons-regular";
|
||||||
import { Notepad, Group } from "@styled-icons/boxicons-solid";
|
import { Notepad, Group } from "@styled-icons/boxicons-solid";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||||
|
@ -65,85 +65,103 @@ const Info = styled.div`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const IconConainer = styled.div`
|
const IconContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--secondary-foreground);
|
color: var(--secondary-foreground);
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
${!isTouchscreenDevice && css`
|
> svg {
|
||||||
|
margin-right: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${!isTouchscreenDevice &&
|
||||||
|
css`
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
`
|
`;
|
||||||
|
|
||||||
export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: ChannelHeaderProps) => {
|
export default observer(
|
||||||
const { openScreen } = useIntermediate();
|
({ channel, toggleSidebar, toggleChannelSidebar }: ChannelHeaderProps) => {
|
||||||
|
const { openScreen } = useIntermediate();
|
||||||
|
|
||||||
const name = getChannelName(channel);
|
const name = getChannelName(channel);
|
||||||
let icon, recipient: User | undefined;
|
let icon, recipient: User | undefined;
|
||||||
switch (channel.channel_type) {
|
switch (channel.channel_type) {
|
||||||
case "SavedMessages":
|
case "SavedMessages":
|
||||||
icon = <Notepad size={24} />;
|
icon = <Notepad size={24} />;
|
||||||
break;
|
break;
|
||||||
case "DirectMessage":
|
case "DirectMessage":
|
||||||
icon = <At size={24} />;
|
icon = <At size={24} />;
|
||||||
recipient = channel.recipient;
|
recipient = channel.recipient;
|
||||||
break;
|
break;
|
||||||
case "Group":
|
case "Group":
|
||||||
icon = <Group size={24} />;
|
icon = <Group size={24} />;
|
||||||
break;
|
break;
|
||||||
case "TextChannel":
|
case "TextChannel":
|
||||||
icon = <Hash size={24} />;
|
icon = <Hash size={24} />;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header placement="primary">
|
<Header placement="primary">
|
||||||
<HamburgerAction />
|
<HamburgerAction />
|
||||||
<IconConainer onClick={toggleChannelSidebar}>{icon}</IconConainer>
|
<IconContainer onClick={toggleChannelSidebar}>
|
||||||
<Info>
|
{/*isTouchscreenDevice && <ChevronLeft size={18} /> FIXME: requires mobx merge */}
|
||||||
<span className="name">{name}</span>
|
{icon}
|
||||||
{isTouchscreenDevice &&
|
</IconContainer>
|
||||||
channel.channel_type === "DirectMessage" && (
|
<Info>
|
||||||
<>
|
<span className="name">{name}</span>
|
||||||
<div className="divider" />
|
{isTouchscreenDevice &&
|
||||||
<span className="desc">
|
channel.channel_type === "DirectMessage" && (
|
||||||
<div
|
<>
|
||||||
className="status"
|
<div className="divider" />
|
||||||
style={{
|
<span className="desc">
|
||||||
backgroundColor:
|
<div
|
||||||
useStatusColour(recipient),
|
className="status"
|
||||||
}}
|
style={{
|
||||||
/>
|
backgroundColor:
|
||||||
<UserStatus user={recipient} />
|
useStatusColour(recipient),
|
||||||
</span>
|
}}
|
||||||
</>
|
/>
|
||||||
)}
|
<UserStatus user={recipient} />
|
||||||
{!isTouchscreenDevice &&
|
</span>
|
||||||
(channel.channel_type === "Group" ||
|
</>
|
||||||
channel.channel_type === "TextChannel") &&
|
)}
|
||||||
channel.description && (
|
{!isTouchscreenDevice &&
|
||||||
<>
|
(channel.channel_type === "Group" ||
|
||||||
<div className="divider" />
|
channel.channel_type === "TextChannel") &&
|
||||||
<span
|
channel.description && (
|
||||||
className="desc"
|
<>
|
||||||
onClick={() =>
|
<div className="divider" />
|
||||||
openScreen({
|
<span
|
||||||
id: "channel_info",
|
className="desc"
|
||||||
channel,
|
onClick={() =>
|
||||||
})
|
openScreen({
|
||||||
}>
|
id: "channel_info",
|
||||||
<Markdown
|
channel,
|
||||||
content={
|
})
|
||||||
channel.description.split("\n")[0] ?? ""
|
}>
|
||||||
}
|
<Markdown
|
||||||
disallowBigEmoji
|
content={
|
||||||
/>
|
channel.description.split(
|
||||||
</span>
|
"\n",
|
||||||
</>
|
)[0] ?? ""
|
||||||
)}
|
}
|
||||||
</Info>
|
disallowBigEmoji
|
||||||
<HeaderActions channel={channel} toggleSidebar={toggleSidebar} />
|
/>
|
||||||
</Header>
|
</span>
|
||||||
);
|
</>
|
||||||
});
|
)}
|
||||||
|
</Info>
|
||||||
|
<HeaderActions
|
||||||
|
channel={channel}
|
||||||
|
toggleSidebar={toggleSidebar}
|
||||||
|
/>
|
||||||
|
</Header>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 240px;
|
width: 236px;
|
||||||
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -130,13 +130,18 @@
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
min-width: 218px;
|
min-width: 218px;
|
||||||
max-width: 300px;
|
|
||||||
padding: 80px 8px;
|
padding: 80px 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 800px) {
|
||||||
|
.container {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
@ -224,6 +229,11 @@
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
border-top: 1px solid;
|
border-top: 1px solid;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {
|
||||||
Globe,
|
Globe,
|
||||||
LogOut,
|
LogOut,
|
||||||
Desktop,
|
Desktop,
|
||||||
Bot,
|
|
||||||
} from "@styled-icons/boxicons-regular";
|
} from "@styled-icons/boxicons-regular";
|
||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
|
@ -17,6 +16,7 @@ import {
|
||||||
Megaphone,
|
Megaphone,
|
||||||
Speaker,
|
Speaker,
|
||||||
Store,
|
Store,
|
||||||
|
Bot,
|
||||||
} from "@styled-icons/boxicons-solid";
|
} from "@styled-icons/boxicons-solid";
|
||||||
import { Route, Switch, useHistory } from "react-router-dom";
|
import { Route, Switch, useHistory } from "react-router-dom";
|
||||||
import { LIBRARY_VERSION } from "revolt.js";
|
import { LIBRARY_VERSION } from "revolt.js";
|
||||||
|
|
|
@ -68,7 +68,17 @@ export const Account = observer(() => {
|
||||||
onClick={() => switchPage("profile")}
|
onClick={() => switchPage("profile")}
|
||||||
/>
|
/>
|
||||||
<div className={styles.userDetail}>
|
<div className={styles.userDetail}>
|
||||||
@{client.user!.username}
|
<div className={styles.userContainer}>
|
||||||
|
<UserIcon
|
||||||
|
className={styles.tinyavatar}
|
||||||
|
target={client.user!}
|
||||||
|
size={25}
|
||||||
|
onClick={() => switchPage("profile")}
|
||||||
|
/>
|
||||||
|
<div className={styles.username}>
|
||||||
|
@{client.user!.username}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className={styles.userid}>
|
<div className={styles.userid}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
|
@ -113,6 +123,7 @@ export const Account = observer(() => {
|
||||||
<>
|
<>
|
||||||
{value}{" "}
|
{value}{" "}
|
||||||
<a
|
<a
|
||||||
|
style={{ fontSize: "13px" }}
|
||||||
onClick={(ev) =>
|
onClick={(ev) =>
|
||||||
stopPropagation(
|
stopPropagation(
|
||||||
ev,
|
ev,
|
||||||
|
@ -126,6 +137,7 @@ export const Account = observer(() => {
|
||||||
<>
|
<>
|
||||||
•••••••••••@••••••.•••{" "}
|
•••••••••••@••••••.•••{" "}
|
||||||
<a
|
<a
|
||||||
|
style={{ fontSize: "13px" }}
|
||||||
onClick={(ev) =>
|
onClick={(ev) =>
|
||||||
stopPropagation(
|
stopPropagation(
|
||||||
ev,
|
ev,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Reset, Import } from "@styled-icons/boxicons-regular";
|
import { Reset, Import } from "@styled-icons/boxicons-regular";
|
||||||
import { Pencil, Store } from "@styled-icons/boxicons-solid";
|
import { Pencil, Store } from "@styled-icons/boxicons-solid";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
// @ts-expect-error shade-blend-color does not have typings.
|
// @ts-expect-error shade-blend-color does not have typings.
|
||||||
import pSBC from "shade-blend-color";
|
import pSBC from "shade-blend-color";
|
||||||
|
|
||||||
|
@ -8,16 +9,13 @@ import { Text } from "preact-i18n";
|
||||||
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||||
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
|
||||||
|
|
||||||
|
|
||||||
import { debounce } from "../../../lib/debounce";
|
import { debounce } from "../../../lib/debounce";
|
||||||
|
|
||||||
import { dispatch } from "../../../redux";
|
import { dispatch } from "../../../redux";
|
||||||
import { connectState } from "../../../redux/connector";
|
import { connectState } from "../../../redux/connector";
|
||||||
|
import { isExperimentEnabled } from "../../../redux/reducers/experiments";
|
||||||
import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
|
import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_FONT,
|
DEFAULT_FONT,
|
||||||
DEFAULT_MONO_FONT,
|
DEFAULT_MONO_FONT,
|
||||||
|
@ -40,14 +38,13 @@ import Checkbox from "../../../components/ui/Checkbox";
|
||||||
import ColourSwatches from "../../../components/ui/ColourSwatches";
|
import ColourSwatches from "../../../components/ui/ColourSwatches";
|
||||||
import ComboBox from "../../../components/ui/ComboBox";
|
import ComboBox from "../../../components/ui/ComboBox";
|
||||||
import InputBox from "../../../components/ui/InputBox";
|
import InputBox from "../../../components/ui/InputBox";
|
||||||
|
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
||||||
import darkSVG from "../assets/dark.svg";
|
import darkSVG from "../assets/dark.svg";
|
||||||
import lightSVG from "../assets/light.svg";
|
import lightSVG from "../assets/light.svg";
|
||||||
import mutantSVG from "../assets/mutant_emoji.svg";
|
import mutantSVG from "../assets/mutant_emoji.svg";
|
||||||
import notoSVG from "../assets/noto_emoji.svg";
|
import notoSVG from "../assets/noto_emoji.svg";
|
||||||
import openmojiSVG from "../assets/openmoji_emoji.svg";
|
import openmojiSVG from "../assets/openmoji_emoji.svg";
|
||||||
import twemojiSVG from "../assets/twemoji_emoji.svg";
|
import twemojiSVG from "../assets/twemoji_emoji.svg";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { isExperimentEnabled } from "../../../redux/reducers/experiments";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
|
@ -112,8 +109,7 @@ export function Component(props: Props) {
|
||||||
draggable={false}
|
draggable={false}
|
||||||
data-active={selected === "light"}
|
data-active={selected === "light"}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
selected !== "light" &&
|
selected !== "light" && setTheme({ base: "light" })
|
||||||
setTheme({ base: "light" })
|
|
||||||
}
|
}
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
/>
|
/>
|
||||||
|
@ -138,11 +134,20 @@ export function Component(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isExperimentEnabled('theme_shop') && <Link to="/settings/theme_shop" replace>
|
{isExperimentEnabled("theme_shop") && (
|
||||||
<CategoryButton icon={<Store size={24} />} action="chevron" hover>
|
<Link
|
||||||
<Text id="app.settings.pages.theme_shop.title" />
|
to="/settings/theme_shop"
|
||||||
</CategoryButton>
|
replace
|
||||||
</Link>}
|
className={styles.focus}>
|
||||||
|
<CategoryButton
|
||||||
|
icon={<Store size={24} />}
|
||||||
|
action="chevron"
|
||||||
|
description={"Browse themes made by the community"}
|
||||||
|
hover>
|
||||||
|
<Text id="app.settings.pages.theme_shop.title" />
|
||||||
|
</CategoryButton>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<Text id="app.settings.pages.appearance.accent_selector" />
|
<Text id="app.settings.pages.appearance.accent_selector" />
|
||||||
|
|
|
@ -57,11 +57,11 @@ export function Component() {
|
||||||
return () => {
|
return () => {
|
||||||
if (mediaStream) {
|
if (mediaStream) {
|
||||||
// close microphone access on unmount
|
// close microphone access on unmount
|
||||||
mediaStream.getTracks().forEach(track => {
|
mediaStream.getTracks().forEach((track) => {
|
||||||
track.stop()
|
track.stop();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}, [mediaStream]);
|
}, [mediaStream]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -90,61 +90,109 @@ export function Component() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class={styles.audio}>
|
<div class={styles.audio}>
|
||||||
<h3>
|
|
||||||
<Text id="app.settings.pages.audio.input_device" />
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{!permission && (
|
{!permission && (
|
||||||
<div className={styles.grant_permission}>
|
<Tip error hideSeparator>
|
||||||
<span className={styles.description}>
|
<Text id="app.settings.pages.audio.tip_grant_permission" />
|
||||||
<Text id="app.settings.pages.audio.tip_grant_permission" />
|
</Tip>
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
compact
|
|
||||||
onClick={(e) => handleAskForPermission(e)}
|
|
||||||
error>
|
|
||||||
<Text id="app.settings.pages.audio.button_grant" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ComboBox
|
|
||||||
value={window.localStorage.getItem("audioInputDevice") ?? 0}
|
|
||||||
onChange={(e) =>
|
|
||||||
changeAudioDevice(e.currentTarget.value, "input")
|
|
||||||
}>
|
|
||||||
{mediaDevices
|
|
||||||
?.filter((device) => device.kind === "audioinput")
|
|
||||||
.map((device) => {
|
|
||||||
return (
|
|
||||||
<option
|
|
||||||
value={device.deviceId}
|
|
||||||
key={device.deviceId}>
|
|
||||||
{device.label || (
|
|
||||||
<Text id="app.settings.pages.audio.device_label_NA" />
|
|
||||||
)}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ComboBox>
|
|
||||||
{error && error.name === "NotAllowedError" && (
|
|
||||||
<Overline error="AudioPermissionBlock" type="error" block />
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{error && permission === "prompt" && (
|
{error && permission === "prompt" && (
|
||||||
<Tip>
|
<Tip error hideSeparator>
|
||||||
<TextReact
|
<Text id="app.settings.pages.audio.tip_retry" />
|
||||||
id="app.settings.pages.audio.tip_retry"
|
<a onClick={handleAskForPermission}>
|
||||||
fields={{
|
<Text id="app.settings.pages.audio.button_retry" />
|
||||||
retryBtn: (
|
</a>
|
||||||
<a onClick={handleAskForPermission}>
|
.
|
||||||
<Text id="app.settings.pages.audio.button_retry" />
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tip>
|
</Tip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className={styles.audioRow}>
|
||||||
|
<div className={styles.select}>
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.audio.input_device" />
|
||||||
|
</h3>
|
||||||
|
<div class={styles.audioBox}>
|
||||||
|
<ComboBox
|
||||||
|
value={
|
||||||
|
window.localStorage.getItem(
|
||||||
|
"audioInputDevice",
|
||||||
|
) ?? 0
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
changeAudioDevice(
|
||||||
|
e.currentTarget.value,
|
||||||
|
"input",
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
{mediaDevices
|
||||||
|
?.filter(
|
||||||
|
(device) =>
|
||||||
|
device.kind === "audioinput",
|
||||||
|
)
|
||||||
|
.map((device) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
value={device.deviceId}
|
||||||
|
key={device.deviceId}>
|
||||||
|
{device.label || (
|
||||||
|
<Text id="app.settings.pages.audio.device_label_NA" />
|
||||||
|
)}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ComboBox>
|
||||||
|
{!permission && (
|
||||||
|
<Button
|
||||||
|
compact
|
||||||
|
onClick={(e) => handleAskForPermission(e)}
|
||||||
|
error>
|
||||||
|
<Text id="app.settings.pages.audio.button_grant" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{error && error.name === "NotAllowedError" && (
|
||||||
|
<Overline
|
||||||
|
error="AudioPermissionBlock"
|
||||||
|
type="error"
|
||||||
|
block
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.select}>
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.audio.output_device" />
|
||||||
|
</h3>
|
||||||
|
{/* TOFIX: create audio output combobox*/}
|
||||||
|
<ComboBox
|
||||||
|
value={
|
||||||
|
window.localStorage.getItem(
|
||||||
|
"audioOutputDevice",
|
||||||
|
) ?? 0
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
changeAudioDevice(
|
||||||
|
e.currentTarget.value,
|
||||||
|
"output",
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
{mediaDevices
|
||||||
|
?.filter(
|
||||||
|
(device) => device.kind === "audiooutput",
|
||||||
|
)
|
||||||
|
.map((device) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
value={device.deviceId}
|
||||||
|
key={device.deviceId}>
|
||||||
|
{device.label || (
|
||||||
|
<Text id="app.settings.pages.audio.device_label_NA" />
|
||||||
|
)}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ComboBox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -48,7 +48,7 @@ interface Changes {
|
||||||
|
|
||||||
const BotBadge = styled.div`
|
const BotBadge = styled.div`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
flex-shrink: 0;
|
||||||
height: 1.3em;
|
height: 1.3em;
|
||||||
padding: 0px 4px;
|
padding: 0px 4px;
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
|
@ -228,99 +228,103 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={bot._id} className={styles.botCard}>
|
<div key={bot._id} className={styles.botCard}>
|
||||||
<div className={styles.infoheader}>
|
<div className={styles.infocontainer}>
|
||||||
<div className={styles.container}>
|
<div className={styles.infoheader}>
|
||||||
{!editMode ? (
|
<div className={styles.container}>
|
||||||
<UserIcon
|
{!editMode ? (
|
||||||
className={styles.avatar}
|
<UserIcon
|
||||||
target={user}
|
className={styles.avatar}
|
||||||
size={48}
|
target={user}
|
||||||
onClick={() =>
|
size={42}
|
||||||
openScreen({
|
onClick={() =>
|
||||||
id: "profile",
|
openScreen({
|
||||||
user_id: user._id,
|
id: "profile",
|
||||||
})
|
user_id: user._id,
|
||||||
}
|
})
|
||||||
/>
|
}
|
||||||
) : (
|
/>
|
||||||
<FileUploader
|
) : (
|
||||||
width={64}
|
<FileUploader
|
||||||
height={64}
|
width={42}
|
||||||
style="icon"
|
height={42}
|
||||||
fileType="avatars"
|
style="icon"
|
||||||
behaviour="upload"
|
fileType="avatars"
|
||||||
maxFileSize={4_000_000}
|
behaviour="upload"
|
||||||
onUpload={(avatar) => editBotAvatar(avatar)}
|
maxFileSize={4_000_000}
|
||||||
remove={() => editBotAvatar()}
|
onUpload={(avatar) => editBotAvatar(avatar)}
|
||||||
defaultPreview={user.generateAvatarURL(
|
remove={() => editBotAvatar()}
|
||||||
{ max_side: 256 },
|
defaultPreview={user.generateAvatarURL(
|
||||||
true,
|
{ max_side: 256 },
|
||||||
)}
|
true,
|
||||||
previewURL={user.generateAvatarURL(
|
)}
|
||||||
{ max_side: 256 },
|
previewURL={user.generateAvatarURL(
|
||||||
true,
|
{ max_side: 256 },
|
||||||
)}
|
true,
|
||||||
/>
|
)}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{!editMode ? (
|
{!editMode ? (
|
||||||
<div className={styles.userDetail}>
|
<div className={styles.userDetail}>
|
||||||
<div className={styles.userName}>
|
<div className={styles.userName}>
|
||||||
{user!.username}{" "}
|
{user!.username}{" "}
|
||||||
<BotBadge>
|
<BotBadge>
|
||||||
<Text id="app.main.channel.bot" />
|
<Text id="app.main.channel.bot" />
|
||||||
</BotBadge>
|
</BotBadge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.userid}>
|
<div className={styles.userid}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
<Text id="app.settings.pages.bots.unique_id" />
|
<Text id="app.settings.pages.bots.unique_id" />
|
||||||
}>
|
|
||||||
<HelpCircle size={16} />
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip
|
|
||||||
content={<Text id="app.special.copy" />}>
|
|
||||||
<a
|
|
||||||
onClick={() =>
|
|
||||||
writeClipboard(user!._id)
|
|
||||||
}>
|
}>
|
||||||
{user!._id}
|
<HelpCircle size={16} />
|
||||||
</a>
|
</Tooltip>
|
||||||
</Tooltip>
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<Text id="app.special.copy" />
|
||||||
|
}>
|
||||||
|
<a
|
||||||
|
onClick={() =>
|
||||||
|
writeClipboard(user!._id)
|
||||||
|
}>
|
||||||
|
{user!._id}
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<InputBox
|
||||||
<InputBox
|
ref={setUsernameRef}
|
||||||
ref={setUsernameRef}
|
value={data.username}
|
||||||
value={data.username}
|
disabled={saving}
|
||||||
disabled={saving}
|
onChange={(e) =>
|
||||||
onChange={(e) =>
|
setData({
|
||||||
setData({
|
...data,
|
||||||
...data,
|
username: e.currentTarget.value,
|
||||||
username: e.currentTarget.value,
|
})
|
||||||
})
|
}
|
||||||
}
|
/>
|
||||||
/>
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!editMode && (
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<Text
|
||||||
|
id={`app.settings.pages.bots.${
|
||||||
|
bot.public ? "public" : "private"
|
||||||
|
}_bot_tip`}
|
||||||
|
/>
|
||||||
|
}>
|
||||||
|
{bot.public ? (
|
||||||
|
<Globe size={24} />
|
||||||
|
) : (
|
||||||
|
<LockAlt size={24} />
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!editMode && (
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
<Text
|
|
||||||
id={`app.settings.pages.bots.${
|
|
||||||
bot.public ? "public" : "private"
|
|
||||||
}_bot_tip`}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
{bot.public ? (
|
|
||||||
<Globe size={24} />
|
|
||||||
) : (
|
|
||||||
<LockAlt size={24} />
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -377,7 +381,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
id={`bot_profile_${bot._id}`}
|
id={`bot_profile_${bot._id}`}
|
||||||
summary="Profile">
|
summary="Bot Profile">
|
||||||
<h3>
|
<h3>
|
||||||
<Text id="app.settings.pages.profile.custom_background" />
|
<Text id="app.settings.pages.profile.custom_background" />
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -473,7 +477,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||||
<Text id="app.special.modals.actions.save" />
|
<Text id="app.special.modals.actions.save" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
accent
|
error
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
openScreen({
|
openScreen({
|
||||||
|
@ -540,6 +544,16 @@ export const MyBots = observer(() => {
|
||||||
action="chevron">
|
action="chevron">
|
||||||
<Text id="app.settings.pages.bots.create_bot" />
|
<Text id="app.settings.pages.bots.create_bot" />
|
||||||
</CategoryButton>
|
</CategoryButton>
|
||||||
|
<h5>
|
||||||
|
By creating a bot, you are agreeing to the {` `}
|
||||||
|
<a
|
||||||
|
href="https://revolt.chat/aup"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer">
|
||||||
|
Acceptable Usage Policy
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</h5>
|
||||||
{bots?.map((bot) => {
|
{bots?.map((bot) => {
|
||||||
return (
|
return (
|
||||||
<BotCard
|
<BotCard
|
||||||
|
|
|
@ -19,6 +19,21 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.userContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.username {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.userDetail {
|
.userDetail {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@ -48,13 +63,35 @@
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
color: var(--tertiary-foreground);
|
color: var(--tertiary-foreground);
|
||||||
|
|
||||||
|
> :nth-child(2) {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 300px) {
|
||||||
|
.avatar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 300px) {
|
||||||
|
.tinyavatar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
|
@ -87,11 +124,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
|
background: var(--background);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
grid-template-columns: minmax(auto, 100%);
|
grid-template-columns: minmax(auto, 100%);
|
||||||
padding-bottom: 30px;
|
padding: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -99,18 +140,71 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.preview {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badgePicker {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.check {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
background: var(--secondary-background);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
border: 3px solid var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.pfp {
|
.pfp {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background {
|
.background {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.row {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,16 +229,34 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.audio {
|
.audio {
|
||||||
.grant_permission {
|
.audioRow {
|
||||||
margin-bottom: 18px;
|
margin-top: 20px;
|
||||||
.description {
|
display: flex;
|
||||||
font-weight: 400;
|
gap: 20px;
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
.select {
|
||||||
-webkit-line-clamp: 3;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
width: 50%;
|
||||||
font-size: 12px;
|
}
|
||||||
margin-bottom: 8px;
|
}
|
||||||
|
.audioBox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
.audioRow {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,6 +272,7 @@
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -541,6 +654,9 @@
|
||||||
|
|
||||||
.myBots {
|
.myBots {
|
||||||
.botCard {
|
.botCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
background: var(--secondary-background);
|
background: var(--secondary-background);
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
@ -566,6 +682,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infocontainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.infoheader {
|
.infoheader {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -581,6 +704,10 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userDetail {
|
.userDetail {
|
||||||
|
@ -593,9 +720,13 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userName {
|
.username {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -619,9 +750,23 @@
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
color: var(--tertiary-foreground);
|
color: var(--tertiary-foreground);
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> :nth-child(2) {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -631,6 +776,20 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
.infocontainer {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonRow {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Markdown } from "@styled-icons/boxicons-logos";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Profile as ProfileI } from "revolt-api/types/Users";
|
import { Profile as ProfileI } from "revolt-api/types/Users";
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ export const Profile = observer(() => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.user}>
|
<div className={styles.user}>
|
||||||
<h3>
|
<h3>
|
||||||
<Text id="app.settings.pages.profile.preview" />
|
<Text id="app.special.modals.actions.preview" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className={styles.preview}>
|
<div className={styles.preview}>
|
||||||
<UserProfile
|
<UserProfile
|
||||||
|
@ -71,6 +72,12 @@ export const Profile = observer(() => {
|
||||||
dummyProfile={profile}
|
dummyProfile={profile}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<h3>Badges</h3>
|
||||||
|
<div className={styles.badgePicker}>
|
||||||
|
<div className={styles.check}>a</div>
|
||||||
|
<div className={styles.check}>b</div>
|
||||||
|
<div className={styles.check}>c</div>
|
||||||
|
</div>
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<div className={styles.pfp}>
|
<div className={styles.pfp}>
|
||||||
<h3>
|
<h3>
|
||||||
|
@ -155,6 +162,19 @@ export const Profile = observer(() => {
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
/>
|
/>
|
||||||
|
<div className={styles.markdown}>
|
||||||
|
<Markdown size="24" />
|
||||||
|
<h5>
|
||||||
|
Descriptions support Markdown formatting,{" "}
|
||||||
|
<a
|
||||||
|
href="https://developers.revolt.chat/markdown"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer">
|
||||||
|
learn more here
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<Button
|
<Button
|
||||||
contrast
|
contrast
|
||||||
|
|
|
@ -14,6 +14,10 @@ interface Props {
|
||||||
export function Component(props: Props) {
|
export function Component(props: Props) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.notifications}>
|
<div className={styles.notifications}>
|
||||||
|
{/*<h3>
|
||||||
|
<Text id="app.settings.pages.sync.options" />
|
||||||
|
</h3>
|
||||||
|
<h5>Sync items automatically</h5>*/}
|
||||||
<h3>
|
<h3>
|
||||||
<Text id="app.settings.pages.sync.categories" />
|
<Text id="app.settings.pages.sync.categories" />
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -46,6 +50,9 @@ export function Component(props: Props) {
|
||||||
<Text id={`app.settings.pages.${title}`} />
|
<Text id={`app.settings.pages.${title}`} />
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
))}
|
))}
|
||||||
|
{/*<h5 style={{ marginTop: "20px", color: "grey" }}>
|
||||||
|
Last sync at 12:00
|
||||||
|
</h5>*/}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
import { Plus, Check } from "@styled-icons/boxicons-regular";
|
||||||
|
import {
|
||||||
|
Star,
|
||||||
|
BarChartAlt2,
|
||||||
|
Brush,
|
||||||
|
Bookmark,
|
||||||
|
} from "@styled-icons/boxicons-solid";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
@ -6,6 +13,7 @@ import { dispatch } from "../../../redux";
|
||||||
|
|
||||||
import { Theme, generateVariables, ThemeOptions } from "../../../context/Theme";
|
import { Theme, generateVariables, ThemeOptions } from "../../../context/Theme";
|
||||||
|
|
||||||
|
import InputBox from "../../../components/ui/InputBox";
|
||||||
import Tip from "../../../components/ui/Tip";
|
import Tip from "../../../components/ui/Tip";
|
||||||
import previewPath from "../assets/preview.svg";
|
import previewPath from "../assets/preview.svg";
|
||||||
|
|
||||||
|
@ -35,13 +43,9 @@ export type Manifest = {
|
||||||
|
|
||||||
// TODO: ability to preview / display the settings set like in the appearance pane
|
// TODO: ability to preview / display the settings set like in the appearance pane
|
||||||
const ThemeInfo = styled.article`
|
const ThemeInfo = styled.article`
|
||||||
display: grid;
|
display: flex;
|
||||||
grid:
|
flex-direction: column;
|
||||||
"preview name creator" min-content
|
gap: 10px;
|
||||||
"preview desc desc" 1fr
|
|
||||||
/ 200px 1fr 1fr;
|
|
||||||
|
|
||||||
gap: 0.5rem 1rem;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
background: var(--secondary-background);
|
background: var(--secondary-background);
|
||||||
|
@ -93,6 +97,7 @@ const ThemeInfo = styled.article`
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
|
margin-top: 5px !important;
|
||||||
grid-area: name;
|
grid-area: name;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -104,15 +109,115 @@ const ThemeInfo = styled.article`
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
|
margin-bottom: 5px;
|
||||||
grid-area: desc;
|
grid-area: desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.previewBox {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.hover {
|
||||||
|
opacity: 0;
|
||||||
|
font-family: var(--font), sans-serif;
|
||||||
|
font-variant-ligatures: var(--ligatures);
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ThemeList = styled.div`
|
const ThemeList = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const Banner = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Category = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ActiveTheme = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--secondary-background);
|
||||||
|
padding: 0;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
gap: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.active-indicator {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--accent);
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme {
|
||||||
|
width: 124px;
|
||||||
|
height: 80px;
|
||||||
|
background: var(--tertiary-background);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 10px 16px 16px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const ThemedSVG = styled.svg<{ theme: Theme }>`
|
const ThemedSVG = styled.svg<{ theme: Theme }>`
|
||||||
${(props) => props.theme && generateVariables(props.theme)}
|
${(props) => props.theme && generateVariables(props.theme)}
|
||||||
`;
|
`;
|
||||||
|
@ -140,6 +245,10 @@ const ThemePreview = ({ theme, ...props }: ThemePreviewProps) => {
|
||||||
const ThemeShopRoot = styled.div`
|
const ThemeShopRoot = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function ThemeShop() {
|
export function ThemeShop() {
|
||||||
|
@ -175,18 +284,71 @@ export function ThemeShop() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeShopRoot>
|
<ThemeShopRoot>
|
||||||
|
<h5>
|
||||||
|
Browse hundreds of themes, created and curated by the community.
|
||||||
|
</h5>
|
||||||
|
{/*<LoadFail>
|
||||||
|
<h5>
|
||||||
|
Oops! Couldn't load the theme shop. Make sure you're
|
||||||
|
connected to the internet and try again.
|
||||||
|
</h5>
|
||||||
|
</LoadFail>*/}
|
||||||
<Tip warning hideSeparator>
|
<Tip warning hideSeparator>
|
||||||
This section is under construction.
|
The Theme Shop is currently under construction.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
{/* FIXME INTEGRATE WITH MOBX */}
|
||||||
|
{/*<ActiveTheme>
|
||||||
|
<div class="active-indicator">
|
||||||
|
<Check size="16" />
|
||||||
|
Currently active
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="theme">theme svg goes here</div>
|
||||||
|
<div class="info">
|
||||||
|
<div class="title">Theme Title</div>
|
||||||
|
<div class="author">by Author</div>
|
||||||
|
<h5>This is a theme description.</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ActiveTheme>
|
||||||
|
<InputBox placeholder="Search themes..." contrast />
|
||||||
|
<Category>
|
||||||
|
<div class="title">
|
||||||
|
<Bookmark size={16} />
|
||||||
|
Saved
|
||||||
|
</div>
|
||||||
|
<a class="view">Manage installed</a>
|
||||||
|
</Category>
|
||||||
|
|
||||||
|
<Category>
|
||||||
|
<div class="title">
|
||||||
|
<Star size={16} />
|
||||||
|
New this week
|
||||||
|
</div>
|
||||||
|
<a class="view">View all</a>
|
||||||
|
</Category>
|
||||||
|
|
||||||
|
<Category>
|
||||||
|
<div class="title">
|
||||||
|
<Brush size={16} />
|
||||||
|
Default themes
|
||||||
|
</div>
|
||||||
|
<a class="view">View all</a>
|
||||||
|
</Category>
|
||||||
|
|
||||||
|
<Category>
|
||||||
|
<div class="title">
|
||||||
|
<BarChartAlt2 size={16} />
|
||||||
|
Highest rated
|
||||||
|
</div>
|
||||||
|
<a class="view">View all</a>
|
||||||
|
</Category>*/}
|
||||||
|
|
||||||
<ThemeList>
|
<ThemeList>
|
||||||
{themeList?.map(([slug, theme]) => (
|
{themeList?.map(([slug, theme]) => (
|
||||||
<ThemeInfo
|
<ThemeInfo
|
||||||
key={slug}
|
key={slug}
|
||||||
data-loaded={Reflect.has(themeData, slug)}>
|
data-loaded={Reflect.has(themeData, slug)}>
|
||||||
<h2 class="name">{theme.name}</h2>
|
|
||||||
{/* Maybe id's of the users should be included as well / instead? */}
|
|
||||||
<div class="creator">by {theme.creator}</div>
|
|
||||||
<div class="description">{theme.description}</div>
|
|
||||||
<button
|
<button
|
||||||
class="preview"
|
class="preview"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -195,17 +357,27 @@ export function ThemeShop() {
|
||||||
theme: {
|
theme: {
|
||||||
slug,
|
slug,
|
||||||
meta: theme,
|
meta: theme,
|
||||||
theme: themeData[slug]
|
theme: themeData[slug],
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "SETTINGS_SET_THEME",
|
type: "SETTINGS_SET_THEME",
|
||||||
theme: { base: slug },
|
theme: { base: slug },
|
||||||
});
|
});
|
||||||
}}>
|
}}>
|
||||||
<ThemePreview slug={slug} theme={themeData[slug]} />
|
<div class="previewBox">
|
||||||
|
<div class="hover">Use theme</div>
|
||||||
|
<ThemePreview
|
||||||
|
slug={slug}
|
||||||
|
theme={themeData[slug]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<h1 class="name">{theme.name}</h1>
|
||||||
|
{/* Maybe id's of the users should be included as well / instead? */}
|
||||||
|
<div class="creator">by {theme.creator}</div>
|
||||||
|
<h5 class="description">{theme.description}</h5>
|
||||||
</ThemeInfo>
|
</ThemeInfo>
|
||||||
))}
|
))}
|
||||||
</ThemeList>
|
</ThemeList>
|
||||||
|
|
|
@ -17,9 +17,9 @@ self.addEventListener("push", (event) => {
|
||||||
icon: data.icon,
|
icon: data.icon,
|
||||||
image: data.image,
|
image: data.image,
|
||||||
body: data.body,
|
body: data.body,
|
||||||
timestamp: data.timestamp,
|
timestamp: data.timestamp * 1000,
|
||||||
tag: data.tag,
|
tag: data.tag,
|
||||||
badge: "https://app.revolt.chat/assets/icons/android-chrome-512x512.png",
|
badge: "https://app.revolt.chat/assets/icons/monochrome.svg",
|
||||||
data: data.url,
|
data: data.url,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue