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 npm prune --production
|
||||
|
||||
FROM node:15-buster
|
||||
FROM node:16-buster
|
||||
WORKDIR /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;
|
||||
}>`
|
||||
height: 100%;
|
||||
width: 240px;
|
||||
width: 236px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -79,7 +79,9 @@ const ServersBase = styled.div`
|
|||
width: 56px;
|
||||
height: 100%;
|
||||
padding-left: 2px;
|
||||
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
|
||||
${isTouchscreenDevice &&
|
||||
|
|
|
@ -32,7 +32,7 @@ interface Props {
|
|||
|
||||
const ServerBase = styled.div`
|
||||
height: 100%;
|
||||
width: 240px;
|
||||
width: 236px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -24,6 +24,7 @@ export default styled.button<Props>`
|
|||
font-size: 0.875rem;
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
|
||||
transition: 0.2s ease opacity;
|
||||
transition: 0.2s ease background-color;
|
||||
|
|
|
@ -10,7 +10,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export default styled.div<Props>`
|
||||
gap: 6px;
|
||||
gap: 10px;
|
||||
height: 48px;
|
||||
flex: 0 auto;
|
||||
display: flex;
|
||||
|
|
|
@ -49,9 +49,9 @@ export const TipBase = styled.div<Props>`
|
|||
${(props) =>
|
||||
props.error &&
|
||||
css`
|
||||
color: var(--error);
|
||||
color: white;
|
||||
border: 2px solid var(--error);
|
||||
background: var(--secondary-header);
|
||||
background: var(--error);
|
||||
`}
|
||||
`;
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
> svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
|
|
|
@ -58,7 +58,7 @@ export default function App() {
|
|||
leftPanel={
|
||||
inSpecial
|
||||
? undefined
|
||||
: { width: 292, component: <LeftSidebar /> }
|
||||
: { width: 288, component: <LeftSidebar /> }
|
||||
}
|
||||
rightPanel={
|
||||
!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 { observer } from "mobx-react-lite";
|
||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||
|
@ -65,18 +65,27 @@ const Info = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
const IconConainer = styled.div`
|
||||
const IconContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: var(--secondary-foreground);
|
||||
margin-right: 5px;
|
||||
|
||||
${!isTouchscreenDevice && css`
|
||||
> svg {
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
${!isTouchscreenDevice &&
|
||||
css`
|
||||
&:hover {
|
||||
color: var(--foreground);
|
||||
}
|
||||
`}
|
||||
`
|
||||
`;
|
||||
|
||||
export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: ChannelHeaderProps) => {
|
||||
export default observer(
|
||||
({ channel, toggleSidebar, toggleChannelSidebar }: ChannelHeaderProps) => {
|
||||
const { openScreen } = useIntermediate();
|
||||
|
||||
const name = getChannelName(channel);
|
||||
|
@ -100,7 +109,10 @@ export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: Chann
|
|||
return (
|
||||
<Header placement="primary">
|
||||
<HamburgerAction />
|
||||
<IconConainer onClick={toggleChannelSidebar}>{icon}</IconConainer>
|
||||
<IconContainer onClick={toggleChannelSidebar}>
|
||||
{/*isTouchscreenDevice && <ChevronLeft size={18} /> FIXME: requires mobx merge */}
|
||||
{icon}
|
||||
</IconContainer>
|
||||
<Info>
|
||||
<span className="name">{name}</span>
|
||||
{isTouchscreenDevice &&
|
||||
|
@ -135,7 +147,9 @@ export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: Chann
|
|||
}>
|
||||
<Markdown
|
||||
content={
|
||||
channel.description.split("\n")[0] ?? ""
|
||||
channel.description.split(
|
||||
"\n",
|
||||
)[0] ?? ""
|
||||
}
|
||||
disallowBigEmoji
|
||||
/>
|
||||
|
@ -143,7 +157,11 @@ export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: Chann
|
|||
</>
|
||||
)}
|
||||
</Info>
|
||||
<HeaderActions channel={channel} toggleSidebar={toggleSidebar} />
|
||||
<HeaderActions
|
||||
channel={channel}
|
||||
toggleSidebar={toggleSidebar}
|
||||
/>
|
||||
</Header>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
.actions {
|
||||
gap: 8px;
|
||||
width: 240px;
|
||||
width: 236px;
|
||||
|
||||
margin: auto;
|
||||
display: flex;
|
||||
|
|
|
@ -130,13 +130,18 @@
|
|||
|
||||
.container {
|
||||
min-width: 218px;
|
||||
max-width: 300px;
|
||||
padding: 80px 8px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 800px) {
|
||||
.container {
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 30px;
|
||||
}
|
||||
|
@ -224,6 +229,11 @@
|
|||
font-weight: 400;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid;
|
||||
margin: 0;
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
Globe,
|
||||
LogOut,
|
||||
Desktop,
|
||||
Bot,
|
||||
} from "@styled-icons/boxicons-regular";
|
||||
import {
|
||||
Bell,
|
||||
|
@ -17,6 +16,7 @@ import {
|
|||
Megaphone,
|
||||
Speaker,
|
||||
Store,
|
||||
Bot,
|
||||
} from "@styled-icons/boxicons-solid";
|
||||
import { Route, Switch, useHistory } from "react-router-dom";
|
||||
import { LIBRARY_VERSION } from "revolt.js";
|
||||
|
|
|
@ -68,7 +68,17 @@ export const Account = observer(() => {
|
|||
onClick={() => switchPage("profile")}
|
||||
/>
|
||||
<div className={styles.userDetail}>
|
||||
<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}>
|
||||
<Tooltip
|
||||
content={
|
||||
|
@ -113,6 +123,7 @@ export const Account = observer(() => {
|
|||
<>
|
||||
{value}{" "}
|
||||
<a
|
||||
style={{ fontSize: "13px" }}
|
||||
onClick={(ev) =>
|
||||
stopPropagation(
|
||||
ev,
|
||||
|
@ -126,6 +137,7 @@ export const Account = observer(() => {
|
|||
<>
|
||||
•••••••••••@••••••.•••{" "}
|
||||
<a
|
||||
style={{ fontSize: "13px" }}
|
||||
onClick={(ev) =>
|
||||
stopPropagation(
|
||||
ev,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Reset, Import } from "@styled-icons/boxicons-regular";
|
||||
import { Pencil, Store } from "@styled-icons/boxicons-solid";
|
||||
import { Link } from "react-router-dom";
|
||||
// @ts-expect-error shade-blend-color does not have typings.
|
||||
import pSBC from "shade-blend-color";
|
||||
|
||||
|
@ -8,16 +9,13 @@ import { Text } from "preact-i18n";
|
|||
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
||||
|
||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
||||
|
||||
|
||||
import { debounce } from "../../../lib/debounce";
|
||||
|
||||
import { dispatch } from "../../../redux";
|
||||
import { connectState } from "../../../redux/connector";
|
||||
import { isExperimentEnabled } from "../../../redux/reducers/experiments";
|
||||
import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
|
||||
|
||||
|
||||
import {
|
||||
DEFAULT_FONT,
|
||||
DEFAULT_MONO_FONT,
|
||||
|
@ -40,14 +38,13 @@ import Checkbox from "../../../components/ui/Checkbox";
|
|||
import ColourSwatches from "../../../components/ui/ColourSwatches";
|
||||
import ComboBox from "../../../components/ui/ComboBox";
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
import CategoryButton from "../../../components/ui/fluent/CategoryButton";
|
||||
import darkSVG from "../assets/dark.svg";
|
||||
import lightSVG from "../assets/light.svg";
|
||||
import mutantSVG from "../assets/mutant_emoji.svg";
|
||||
import notoSVG from "../assets/noto_emoji.svg";
|
||||
import openmojiSVG from "../assets/openmoji_emoji.svg";
|
||||
import twemojiSVG from "../assets/twemoji_emoji.svg";
|
||||
import { Link } from "react-router-dom";
|
||||
import { isExperimentEnabled } from "../../../redux/reducers/experiments";
|
||||
|
||||
interface Props {
|
||||
settings: Settings;
|
||||
|
@ -112,8 +109,7 @@ export function Component(props: Props) {
|
|||
draggable={false}
|
||||
data-active={selected === "light"}
|
||||
onClick={() =>
|
||||
selected !== "light" &&
|
||||
setTheme({ base: "light" })
|
||||
selected !== "light" && setTheme({ base: "light" })
|
||||
}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
/>
|
||||
|
@ -138,11 +134,20 @@ export function Component(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{isExperimentEnabled('theme_shop') && <Link to="/settings/theme_shop" replace>
|
||||
<CategoryButton icon={<Store size={24} />} action="chevron" hover>
|
||||
{isExperimentEnabled("theme_shop") && (
|
||||
<Link
|
||||
to="/settings/theme_shop"
|
||||
replace
|
||||
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>}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<h3>
|
||||
<Text id="app.settings.pages.appearance.accent_selector" />
|
||||
|
|
|
@ -57,11 +57,11 @@ export function Component() {
|
|||
return () => {
|
||||
if (mediaStream) {
|
||||
// close microphone access on unmount
|
||||
mediaStream.getTracks().forEach(track => {
|
||||
track.stop()
|
||||
})
|
||||
}
|
||||
mediaStream.getTracks().forEach((track) => {
|
||||
track.stop();
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [mediaStream]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -90,31 +90,45 @@ export function Component() {
|
|||
return (
|
||||
<>
|
||||
<div class={styles.audio}>
|
||||
{!permission && (
|
||||
<Tip error hideSeparator>
|
||||
<Text id="app.settings.pages.audio.tip_grant_permission" />
|
||||
</Tip>
|
||||
)}
|
||||
|
||||
{error && permission === "prompt" && (
|
||||
<Tip error hideSeparator>
|
||||
<Text id="app.settings.pages.audio.tip_retry" />
|
||||
<a onClick={handleAskForPermission}>
|
||||
<Text id="app.settings.pages.audio.button_retry" />
|
||||
</a>
|
||||
.
|
||||
</Tip>
|
||||
)}
|
||||
|
||||
<div className={styles.audioRow}>
|
||||
<div className={styles.select}>
|
||||
<h3>
|
||||
<Text id="app.settings.pages.audio.input_device" />
|
||||
</h3>
|
||||
|
||||
{!permission && (
|
||||
<div className={styles.grant_permission}>
|
||||
<span className={styles.description}>
|
||||
<Text id="app.settings.pages.audio.tip_grant_permission" />
|
||||
</span>
|
||||
<Button
|
||||
compact
|
||||
onClick={(e) => handleAskForPermission(e)}
|
||||
error>
|
||||
<Text id="app.settings.pages.audio.button_grant" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class={styles.audioBox}>
|
||||
<ComboBox
|
||||
value={window.localStorage.getItem("audioInputDevice") ?? 0}
|
||||
value={
|
||||
window.localStorage.getItem(
|
||||
"audioInputDevice",
|
||||
) ?? 0
|
||||
}
|
||||
onChange={(e) =>
|
||||
changeAudioDevice(e.currentTarget.value, "input")
|
||||
changeAudioDevice(
|
||||
e.currentTarget.value,
|
||||
"input",
|
||||
)
|
||||
}>
|
||||
{mediaDevices
|
||||
?.filter((device) => device.kind === "audioinput")
|
||||
?.filter(
|
||||
(device) =>
|
||||
device.kind === "audioinput",
|
||||
)
|
||||
.map((device) => {
|
||||
return (
|
||||
<option
|
||||
|
@ -127,25 +141,59 @@ export function Component() {
|
|||
);
|
||||
})}
|
||||
</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 />
|
||||
)}
|
||||
|
||||
{error && permission === "prompt" && (
|
||||
<Tip>
|
||||
<TextReact
|
||||
id="app.settings.pages.audio.tip_retry"
|
||||
fields={{
|
||||
retryBtn: (
|
||||
<a onClick={handleAskForPermission}>
|
||||
<Text id="app.settings.pages.audio.button_retry" />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
<Overline
|
||||
error="AudioPermissionBlock"
|
||||
type="error"
|
||||
block
|
||||
/>
|
||||
</Tip>
|
||||
)}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ interface Changes {
|
|||
|
||||
const BotBadge = styled.div`
|
||||
display: inline-block;
|
||||
|
||||
flex-shrink: 0;
|
||||
height: 1.3em;
|
||||
padding: 0px 4px;
|
||||
font-size: 0.7em;
|
||||
|
@ -228,13 +228,14 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
|||
|
||||
return (
|
||||
<div key={bot._id} className={styles.botCard}>
|
||||
<div className={styles.infocontainer}>
|
||||
<div className={styles.infoheader}>
|
||||
<div className={styles.container}>
|
||||
{!editMode ? (
|
||||
<UserIcon
|
||||
className={styles.avatar}
|
||||
target={user}
|
||||
size={48}
|
||||
size={42}
|
||||
onClick={() =>
|
||||
openScreen({
|
||||
id: "profile",
|
||||
|
@ -244,8 +245,8 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
|||
/>
|
||||
) : (
|
||||
<FileUploader
|
||||
width={64}
|
||||
height={64}
|
||||
width={42}
|
||||
height={42}
|
||||
style="icon"
|
||||
fileType="avatars"
|
||||
behaviour="upload"
|
||||
|
@ -280,7 +281,9 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
|||
<HelpCircle size={16} />
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={<Text id="app.special.copy" />}>
|
||||
content={
|
||||
<Text id="app.special.copy" />
|
||||
}>
|
||||
<a
|
||||
onClick={() =>
|
||||
writeClipboard(user!._id)
|
||||
|
@ -321,6 +324,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
|||
)}
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
disabled={saving}
|
||||
onClick={() => {
|
||||
|
@ -377,7 +381,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
|||
<CollapsibleSection
|
||||
defaultValue={false}
|
||||
id={`bot_profile_${bot._id}`}
|
||||
summary="Profile">
|
||||
summary="Bot Profile">
|
||||
<h3>
|
||||
<Text id="app.settings.pages.profile.custom_background" />
|
||||
</h3>
|
||||
|
@ -473,7 +477,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
|||
<Text id="app.special.modals.actions.save" />
|
||||
</Button>
|
||||
<Button
|
||||
accent
|
||||
error
|
||||
onClick={async () => {
|
||||
setSaving(true);
|
||||
openScreen({
|
||||
|
@ -540,6 +544,16 @@ export const MyBots = observer(() => {
|
|||
action="chevron">
|
||||
<Text id="app.settings.pages.bots.create_bot" />
|
||||
</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) => {
|
||||
return (
|
||||
<BotCard
|
||||
|
|
|
@ -19,6 +19,21 @@
|
|||
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 {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
@ -48,13 +63,35 @@
|
|||
gap: 4px;
|
||||
color: var(--tertiary-foreground);
|
||||
|
||||
> :nth-child(2) {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
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 {
|
||||
display: flex;
|
||||
padding: 1em 0;
|
||||
|
@ -87,11 +124,15 @@
|
|||
}
|
||||
|
||||
.preview {
|
||||
background: var(--background);
|
||||
border-radius: var(--border-radius);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
grid-template-columns: minmax(auto, 100%);
|
||||
padding-bottom: 30px;
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
> div {
|
||||
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 {
|
||||
gap: 20px;
|
||||
display: flex;
|
||||
|
||||
.pfp {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.background {
|
||||
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 {
|
||||
.grant_permission {
|
||||
margin-bottom: 18px;
|
||||
.description {
|
||||
font-weight: 400;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
.audioRow {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
||||
.select {
|
||||
flex-direction: column;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
|
@ -541,6 +654,9 @@
|
|||
|
||||
.myBots {
|
||||
.botCard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
background: var(--secondary-background);
|
||||
margin: 8px 0;
|
||||
padding: 12px;
|
||||
|
@ -566,6 +682,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.infocontainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.infoheader {
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
@ -581,6 +704,10 @@
|
|||
align-items: center;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.userDetail {
|
||||
|
@ -593,9 +720,13 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.userName {
|
||||
.username {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
@ -619,9 +750,23 @@
|
|||
gap: 4px;
|
||||
color: var(--tertiary-foreground);
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
> :nth-child(2) {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -631,6 +776,20 @@
|
|||
flex-direction: row;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.infocontainer {
|
||||
flex-direction: column;
|
||||
|
||||
> button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonRow {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Markdown } from "@styled-icons/boxicons-logos";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Profile as ProfileI } from "revolt-api/types/Users";
|
||||
|
||||
|
@ -62,7 +63,7 @@ export const Profile = observer(() => {
|
|||
return (
|
||||
<div className={styles.user}>
|
||||
<h3>
|
||||
<Text id="app.settings.pages.profile.preview" />
|
||||
<Text id="app.special.modals.actions.preview" />
|
||||
</h3>
|
||||
<div className={styles.preview}>
|
||||
<UserProfile
|
||||
|
@ -71,6 +72,12 @@ export const Profile = observer(() => {
|
|||
dummyProfile={profile}
|
||||
/>
|
||||
</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.pfp}>
|
||||
<h3>
|
||||
|
@ -155,6 +162,19 @@ export const Profile = observer(() => {
|
|||
onFocus={onFocus}
|
||||
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>
|
||||
<Button
|
||||
contrast
|
||||
|
|
|
@ -14,6 +14,10 @@ interface Props {
|
|||
export function Component(props: Props) {
|
||||
return (
|
||||
<div className={styles.notifications}>
|
||||
{/*<h3>
|
||||
<Text id="app.settings.pages.sync.options" />
|
||||
</h3>
|
||||
<h5>Sync items automatically</h5>*/}
|
||||
<h3>
|
||||
<Text id="app.settings.pages.sync.categories" />
|
||||
</h3>
|
||||
|
@ -46,6 +50,9 @@ export function Component(props: Props) {
|
|||
<Text id={`app.settings.pages.${title}`} />
|
||||
</Checkbox>
|
||||
))}
|
||||
{/*<h5 style={{ marginTop: "20px", color: "grey" }}>
|
||||
Last sync at 12:00
|
||||
</h5>*/}
|
||||
</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 { useEffect, useState } from "preact/hooks";
|
||||
|
@ -6,6 +13,7 @@ import { dispatch } from "../../../redux";
|
|||
|
||||
import { Theme, generateVariables, ThemeOptions } from "../../../context/Theme";
|
||||
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
import Tip from "../../../components/ui/Tip";
|
||||
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
|
||||
const ThemeInfo = styled.article`
|
||||
display: grid;
|
||||
grid:
|
||||
"preview name creator" min-content
|
||||
"preview desc desc" 1fr
|
||||
/ 200px 1fr 1fr;
|
||||
|
||||
gap: 0.5rem 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--secondary-background);
|
||||
|
@ -93,6 +97,7 @@ const ThemeInfo = styled.article`
|
|||
}
|
||||
|
||||
.name {
|
||||
margin-top: 5px !important;
|
||||
grid-area: name;
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -104,15 +109,115 @@ const ThemeInfo = styled.article`
|
|||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 5px;
|
||||
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`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
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 }>`
|
||||
${(props) => props.theme && generateVariables(props.theme)}
|
||||
`;
|
||||
|
@ -140,6 +245,10 @@ const ThemePreview = ({ theme, ...props }: ThemePreviewProps) => {
|
|||
const ThemeShopRoot = styled.div`
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
|
||||
h5 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export function ThemeShop() {
|
||||
|
@ -175,18 +284,71 @@ export function ThemeShop() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
This section is under construction.
|
||||
The Theme Shop is currently under construction.
|
||||
</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?.map(([slug, theme]) => (
|
||||
<ThemeInfo
|
||||
key={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
|
||||
class="preview"
|
||||
onClick={() => {
|
||||
|
@ -195,17 +357,27 @@ export function ThemeShop() {
|
|||
theme: {
|
||||
slug,
|
||||
meta: theme,
|
||||
theme: themeData[slug]
|
||||
}
|
||||
})
|
||||
theme: themeData[slug],
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "SETTINGS_SET_THEME",
|
||||
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>
|
||||
<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>
|
||||
))}
|
||||
</ThemeList>
|
||||
|
|
|
@ -17,9 +17,9 @@ self.addEventListener("push", (event) => {
|
|||
icon: data.icon,
|
||||
image: data.image,
|
||||
body: data.body,
|
||||
timestamp: data.timestamp,
|
||||
timestamp: data.timestamp * 1000,
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue