mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-21 09:52:31 +01:00
start implementing proper settings modal
This commit is contained in:
parent
34db3f9561
commit
1750b22af7
@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Primary Meta Tags -->
|
||||
|
26
src/components/SectionTitle.tsx
Normal file
26
src/components/SectionTitle.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Text = styled.h2`
|
||||
color: var(--text);
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
interface Props {}
|
||||
|
||||
function SectionTitle({ children }: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<Container>
|
||||
<Text>{children}</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default SectionTitle;
|
@ -27,6 +27,7 @@ interface ModalProps {
|
||||
disabled?: boolean;
|
||||
withEmptyActionBar?: boolean;
|
||||
withoutCloseButton?: boolean;
|
||||
fullScreen?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,7 +49,7 @@ export const ModalBase = styled.div<{ closing?: boolean }>`
|
||||
animation-fill-mode: forwards;
|
||||
|
||||
display: grid;
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
place-items: center;
|
||||
|
||||
color: var(--text);
|
||||
@ -72,12 +73,8 @@ export const ModalBase = styled.div<{ closing?: boolean }>`
|
||||
* Wrapper for modal content, handles the sizing and positioning
|
||||
*/
|
||||
export const ModalWrapper = styled.div<
|
||||
Pick<ModalProps, "transparent" | "maxWidth" | "maxHeight"> & { actions: boolean }
|
||||
Pick<ModalProps, "transparent" | "maxWidth" | "maxHeight"> & { actions: boolean; fullScreen?: boolean }
|
||||
>`
|
||||
min-height: 200px;
|
||||
max-width: min(calc(100vw - 20px), ${(props) => props.maxWidth ?? "450px"});
|
||||
max-height: min(calc(100vh - 20px), ${(props) => props.maxHeight ?? "650px"});
|
||||
|
||||
margin: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -86,17 +83,28 @@ export const ModalWrapper = styled.div<
|
||||
animation-duration: 0.25s;
|
||||
animation-timing-function: cubic-bezier(0.3, 0.3, 0.18, 1.1);
|
||||
|
||||
overflow: hidden;
|
||||
background: var(--background-tertiary);
|
||||
|
||||
${(props) =>
|
||||
!props.maxWidth &&
|
||||
!props.fullScreen &&
|
||||
css`
|
||||
min-height: 200px;
|
||||
max-width: min(calc(100vw - 20px), ${props.maxWidth ?? "450px"});
|
||||
max-height: min(calc(100vh - 20px), ${props.maxHeight ?? "650px"});
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.fullScreen &&
|
||||
css`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
!props.transparent &&
|
||||
!props.fullScreen &&
|
||||
css`
|
||||
overflow: hidden;
|
||||
background: var(--background-primary);
|
||||
border-radius: 8px;
|
||||
`}
|
||||
`;
|
||||
@ -122,12 +130,6 @@ export const ModalContentContainer = styled.div<Pick<ModalProps, "transparent" |
|
||||
|
||||
overflow-y: auto;
|
||||
font-size: 0.9375rem;
|
||||
|
||||
${(props) =>
|
||||
!props.transparent &&
|
||||
css`
|
||||
background: var(--background-primary);
|
||||
`}
|
||||
`;
|
||||
|
||||
const Actions = styled.div`
|
||||
|
@ -1,105 +1,264 @@
|
||||
import { FormControlLabel, FormGroup, Switch } from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import styled from "styled-components";
|
||||
import { useState } from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { ModalProps } from "../../controllers/modals";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import { isTauri } from "../../utils/Utils";
|
||||
import { GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../utils/revison";
|
||||
import Button from "../Button";
|
||||
import { APP_VERSION, GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../utils/revison";
|
||||
import Icon from "../Icon";
|
||||
import Link from "../Link";
|
||||
import { Modal } from "./ModalComponents";
|
||||
import AccountSettingsPage from "./SettingsPages/AccountSettingsPage";
|
||||
import DeveloperSettingsPage from "./SettingsPages/DeveloperSettingsPage";
|
||||
import ExperimentsPage from "./SettingsPages/ExperimentsPage";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding: 16px 0;
|
||||
gap: 8px;
|
||||
const SidebarView = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const Sidebar = styled.div`
|
||||
display: flex;
|
||||
flex: 1 0 220px;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const SidebarInner = styled.div`
|
||||
overflow: hidden scroll;
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
background: var(--background-secondary);
|
||||
`;
|
||||
|
||||
const SidebarNav = styled.nav`
|
||||
width: 220px;
|
||||
padding: 60px 6px 20px;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const SidebarNavWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const ActionWrapper = styled.div`
|
||||
margin-top: 20px;
|
||||
gap: 8px;
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
flex: 1 1 800px;
|
||||
align-items: flex-start;
|
||||
background: var(--background-primary);
|
||||
`;
|
||||
|
||||
const VersionWrapper = styled.div`
|
||||
const ContentInner = styled.div`
|
||||
overflow: hidden scroll;
|
||||
justify-content: flex-start;
|
||||
position: static;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: text;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
background: var(--background-primary);
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
& > span {
|
||||
color: var(--text-secondary);
|
||||
const ContentColumn = styled.div`
|
||||
padding: 60px 40px 80px;
|
||||
flex: 1 1 auto;
|
||||
max-width: 740px;
|
||||
min-width: 460px;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
padding: 6px 10px;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
font-weight: var(--font-weight-bold);
|
||||
letter-spacing: 0.5px;
|
||||
`;
|
||||
|
||||
const Item = styled.div<{ selected?: boolean; textColor?: string }>`
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
font-weight: var(--font-weight-regular);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
color: ${(props) => props.textColor ?? "var(--text-secondary);"};
|
||||
|
||||
&:hover {
|
||||
background-color: hsl(var(--background-primary-hsl) / 0.6);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.selected &&
|
||||
css`
|
||||
background-color: var(--background-primary);
|
||||
color: var(--text);
|
||||
`}
|
||||
`;
|
||||
|
||||
const Divider = styled.div`
|
||||
margin: 8px 10px;
|
||||
height: 1px;
|
||||
background-color: var(--text-disabled);
|
||||
`;
|
||||
|
||||
const VersionInfo = styled.div`
|
||||
padding: 8px 10px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
`;
|
||||
|
||||
const CloseContainer = styled.div`
|
||||
margin-right: 20px;
|
||||
flex: 0 0 36px;
|
||||
width: 60px;
|
||||
padding-top: 60px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const CloseContainerInner = styled.div`
|
||||
position: fixed;
|
||||
`;
|
||||
|
||||
const CloseContainerWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const CloseButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 0 0 40px;
|
||||
border: solid 1px;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
`;
|
||||
|
||||
export const SettingsModal = observer(({ ...props }: ModalProps<"settings">) => {
|
||||
const app = useAppStore();
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const value = e.currentTarget.getAttribute("data-value");
|
||||
if (value) {
|
||||
setIndex(parseInt(value));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal {...props}>
|
||||
<Wrapper>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={app.fpsShown} onChange={(e) => app.setFpsShown(e.target.checked)} />}
|
||||
label="Show FPS Graph"
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{isTauri && (
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={app.updaterStore?.enabled}
|
||||
onChange={(e) => app.updaterStore?.setEnabled(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Enabled auto updater"
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
<VersionWrapper>
|
||||
<span>
|
||||
Client Version:{" "}
|
||||
<Link href={`${REPO_URL}/commit/${GIT_REVISION}`} target="_blank" rel="noreferrer">
|
||||
{GIT_REVISION.substring(0, 7)}
|
||||
</Link>
|
||||
{` `}
|
||||
<Link
|
||||
href={GIT_BRANCH !== "DETACHED" ? `${REPO_URL}/tree/${GIT_BRANCH}` : undefined}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
({GIT_BRANCH})
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
{isTauri && (
|
||||
<>
|
||||
<span>App Version: {window.globals.appVersion ?? "Fetching version information..."}</span>
|
||||
<span>
|
||||
Tauri Version: {window.globals.tauriVersion ?? "Fetching version information..."}
|
||||
</span>
|
||||
<span>Platform: {window.globals.platform.name}</span>
|
||||
<span>Arch: {window.globals.platform.arch}</span>
|
||||
<span>OS Version: {window.globals.platform.version}</span>
|
||||
<span>Locale: {window.globals.platform.locale ?? "Unknown"}</span>
|
||||
</>
|
||||
)}
|
||||
</VersionWrapper>
|
||||
|
||||
<ActionWrapper>
|
||||
<Button
|
||||
palette="danger"
|
||||
onClick={() => {
|
||||
app.logout();
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</ActionWrapper>
|
||||
</Wrapper>
|
||||
<Modal {...props} fullScreen withoutCloseButton withEmptyActionBar padding="0">
|
||||
<SidebarView>
|
||||
<Sidebar>
|
||||
<SidebarInner>
|
||||
<SidebarNav>
|
||||
<SidebarNavWrapper>
|
||||
<Header>User Settings</Header>
|
||||
<Item data-value="0" onClick={onClick}>
|
||||
Account
|
||||
</Item>
|
||||
<Divider />
|
||||
<Item data-value="1" onClick={onClick}>
|
||||
Developer Options
|
||||
</Item>
|
||||
<Item data-value="2" onClick={onClick}>
|
||||
Experiments
|
||||
</Item>
|
||||
<Divider />
|
||||
<Item onClick={app.logout}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
color: "var(--error)",
|
||||
}}
|
||||
>
|
||||
Log Out
|
||||
<Icon icon="mdiLogout" size="16px" color="var(--error)" />
|
||||
</div>
|
||||
</Item>
|
||||
<Divider />
|
||||
<VersionInfo>
|
||||
<span>
|
||||
{GIT_BRANCH} {APP_VERSION} (
|
||||
<Link
|
||||
href={`${REPO_URL}/commit/${GIT_REVISION}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{GIT_REVISION.substring(0, 7)}
|
||||
</Link>
|
||||
)
|
||||
</span>
|
||||
{isTauri && (
|
||||
<>
|
||||
{/* <span>
|
||||
{window.globals.appVersion
|
||||
? `${window.globals.appVersion} (${(
|
||||
<Link
|
||||
href={`${REPO_URL}/commit/${GIT_REVISION}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{GIT_REVISION.substring(0, 7)}
|
||||
</Link>
|
||||
)})`
|
||||
: "Fetching version information..."}
|
||||
</span> */}
|
||||
<span>
|
||||
Tauri {window.globals.tauriVersion ?? "Fetching version information..."}
|
||||
</span>
|
||||
<span>{`${window.globals.platform.name} ${window.globals.platform.arch} (${window.globals.platform.version})`}</span>
|
||||
<span>{window.globals.platform.locale ?? "Unknown"}</span>
|
||||
</>
|
||||
)}
|
||||
</VersionInfo>
|
||||
</SidebarNavWrapper>
|
||||
</SidebarNav>
|
||||
</SidebarInner>
|
||||
</Sidebar>
|
||||
<Content>
|
||||
<ContentInner>
|
||||
<ContentColumn>
|
||||
{index === 0 && <AccountSettingsPage />}
|
||||
{index === 1 && <DeveloperSettingsPage />}
|
||||
{index === 2 && <ExperimentsPage />}
|
||||
</ContentColumn>
|
||||
<CloseContainer>
|
||||
<CloseContainerInner></CloseContainerInner>
|
||||
<CloseContainerWrapper>
|
||||
<CloseButtonWrapper
|
||||
onClick={() => {
|
||||
console.log("Close modal");
|
||||
}}
|
||||
>
|
||||
<Icon icon="mdiClose" size="18px" />
|
||||
</CloseButtonWrapper>
|
||||
</CloseContainerWrapper>
|
||||
</CloseContainer>
|
||||
</ContentInner>
|
||||
</Content>
|
||||
</SidebarView>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
146
src/components/modals/SettingsPages/AccountSettingsPage.tsx
Normal file
146
src/components/modals/SettingsPages/AccountSettingsPage.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useState } from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { useAppStore } from "../../../stores/AppStore";
|
||||
import SectionTitle from "../../SectionTitle";
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const UserInfoContainer = styled.div`
|
||||
border-radius: 8px;
|
||||
background-color: var(--background-secondary);
|
||||
padding: 16px;
|
||||
`;
|
||||
|
||||
const Field = styled.div<{ spacerTop?: boolean; spacerBottom?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
${(props) =>
|
||||
props.spacerTop &&
|
||||
css`
|
||||
margin-top: 24px;
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.spacerBottom &&
|
||||
css`
|
||||
margin-bottom: 24px;
|
||||
`}
|
||||
`;
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
margin-right: 16px;
|
||||
`;
|
||||
|
||||
const FieldTitle = styled.span`
|
||||
margin-bottom: 4px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
letter-spacing: 0.5px;
|
||||
`;
|
||||
|
||||
const FieldValue = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const FieldValueText = styled.span`
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
`;
|
||||
|
||||
const FieldValueToggle = styled.button`
|
||||
color: var(--text-link);
|
||||
cursor: pointer;
|
||||
width: auto;
|
||||
display: inline;
|
||||
height: auto;
|
||||
padding: 2px 4px;
|
||||
position: relative;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
user-select: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
`;
|
||||
|
||||
function AccountSettingsPage() {
|
||||
const app = useAppStore();
|
||||
const [shouldRedactEmail, setShouldRedactEmail] = useState(true);
|
||||
|
||||
const redactEmail = (email: string) => {
|
||||
const [username, domain] = email.split("@");
|
||||
return `${"*".repeat(username.length)}@${domain}`;
|
||||
};
|
||||
|
||||
const refactPhoneNumber = (phoneNumber: string) => {
|
||||
const lastFour = phoneNumber.slice(-4);
|
||||
return "*".repeat(phoneNumber.length - 4) + lastFour;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SectionTitle>Account</SectionTitle>
|
||||
<Content>
|
||||
<UserInfoContainer>
|
||||
<Field spacerBottom>
|
||||
<Row>
|
||||
<FieldTitle>Username</FieldTitle>
|
||||
|
||||
<FieldValue>
|
||||
<FieldValueText>
|
||||
{app.account?.username}#{app.account?.discriminator}
|
||||
</FieldValueText>
|
||||
</FieldValue>
|
||||
</Row>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Row>
|
||||
<FieldTitle>Email</FieldTitle>
|
||||
|
||||
<FieldValue>
|
||||
<FieldValueText>
|
||||
{app.account?.email
|
||||
? shouldRedactEmail
|
||||
? redactEmail(app.account.email)
|
||||
: app.account.email
|
||||
: "No email added."}
|
||||
|
||||
<FieldValueToggle onClick={() => setShouldRedactEmail(!shouldRedactEmail)}>
|
||||
{shouldRedactEmail ? "Reveal" : "Hide"}
|
||||
</FieldValueToggle>
|
||||
</FieldValueText>
|
||||
</FieldValue>
|
||||
</Row>
|
||||
</Field>
|
||||
|
||||
<Field spacerTop>
|
||||
<Row>
|
||||
<FieldTitle>Phone Number</FieldTitle>
|
||||
|
||||
<FieldValue>
|
||||
<FieldValueText>No phone number added.</FieldValueText>
|
||||
</FieldValue>
|
||||
</Row>
|
||||
</Field>
|
||||
</UserInfoContainer>
|
||||
</Content>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(AccountSettingsPage);
|
@ -0,0 +1,19 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import styled from "styled-components";
|
||||
import SectionTitle from "../../SectionTitle";
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
function DeveloperSettingsPage() {
|
||||
return (
|
||||
<div>
|
||||
<SectionTitle>Developer Options</SectionTitle>
|
||||
<Content></Content>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(DeveloperSettingsPage);
|
118
src/components/modals/SettingsPages/ExperimentsPage.tsx
Normal file
118
src/components/modals/SettingsPages/ExperimentsPage.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useAppStore } from "../../../stores/AppStore";
|
||||
import { EXPERIMENT_LIST, Experiment as ExperimentType } from "../../../stores/ExperimentsStore";
|
||||
import SectionTitle from "../../SectionTitle";
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const ExperimentList = styled.ul`
|
||||
display: grid;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
const Experiment = styled.li`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const Title = styled.span`
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text);
|
||||
`;
|
||||
|
||||
const Subtitle = styled.div`
|
||||
color: var(--text-disabled);
|
||||
font-size: 14px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
`;
|
||||
|
||||
const OverrideText = styled.div`
|
||||
color: var(--text.muted);
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-weight-bold);
|
||||
`;
|
||||
|
||||
const Select = styled.select`
|
||||
appearance: none;
|
||||
/* safari */
|
||||
-webkit-appearance: none;
|
||||
background-color: var(--background-tertiary);
|
||||
border-color: var(--background-tertiary);
|
||||
color: var(--text);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border: 1px solid transparent;
|
||||
padding: 8px 8px 8px 12px;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
function ExperimentItem({ experiment }: { experiment: ExperimentType }) {
|
||||
const app = useAppStore();
|
||||
const isActive = app.experiments.isExperimentEnabled(experiment.id);
|
||||
const activeTreatment = app.experiments.getTreatment(experiment.id);
|
||||
const [isExpanded, setExpanded] = useState(isActive);
|
||||
|
||||
const toggle = () => setExpanded(!isExpanded);
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = Number.parseInt(e.target.value);
|
||||
app.experiments.setTreatment(experiment.id, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Experiment key={experiment.id}>
|
||||
<div style={{ marginBottom: "10px", cursor: "pointer" }} onClick={toggle}>
|
||||
<Title>{experiment.name}</Title>
|
||||
<Subtitle>{experiment.description}</Subtitle>
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<OverrideText>Treatment Override</OverrideText>
|
||||
<Select onChange={onChange}>
|
||||
{experiment.treatments.map((treatment) => (
|
||||
<option
|
||||
key={treatment.id}
|
||||
value={treatment.id}
|
||||
selected={(!isActive && treatment.id === 0) || activeTreatment?.id === treatment.id}
|
||||
>
|
||||
{`${treatment.name}${treatment.description ? ": " + treatment.description : ""}`}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</Experiment>
|
||||
);
|
||||
}
|
||||
|
||||
function ExperimentsPage() {
|
||||
const app = useAppStore();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SectionTitle>Experiments</SectionTitle>
|
||||
<Content>
|
||||
<ExperimentList>
|
||||
{EXPERIMENT_LIST.map((experiment) => (
|
||||
<ExperimentItem experiment={experiment} />
|
||||
))}
|
||||
</ExperimentList>
|
||||
</Content>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(ExperimentsPage);
|
@ -1,6 +1,6 @@
|
||||
import { action, computed, makeAutoObservable, ObservableMap } from "mobx";
|
||||
|
||||
export type ExperimentType = "test" | "message_queue";
|
||||
export type ExperimentType = "test" | "message_queue" | "presence_rings";
|
||||
|
||||
export interface ExperimentTreatment {
|
||||
id: number;
|
||||
@ -56,6 +56,27 @@ export const EXPERIMENT_LIST: Experiment[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "presence_rings",
|
||||
name: "Presence Rings",
|
||||
description: "Use rings for presence status instead of dots",
|
||||
treatments: [
|
||||
{
|
||||
id: 0,
|
||||
name: "Control",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: "Treatment 1",
|
||||
description: "Use presence dots",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Treatment 2",
|
||||
description: "Use presence rings",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export interface Data {
|
||||
@ -76,7 +97,7 @@ export default class ExperimentsStore {
|
||||
}
|
||||
|
||||
@computed
|
||||
getTreatment(id: ExperimentType) {
|
||||
getTreatment(id: ExperimentType): ExperimentTreatment | undefined {
|
||||
const treatment = this.experiments.get(id);
|
||||
const experiment = EXPERIMENT_LIST.find((x) => x.id === id);
|
||||
return experiment?.treatments.find((x) => x.id === treatment);
|
||||
|
Loading…
Reference in New Issue
Block a user