mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-15 10:02:47 +03:00
profile: add identity form
This commit is contained in:
parent
e9c6322ff4
commit
6085752f21
@ -1,5 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
|
||||||
|
|
||||||
export function resourceAsPath(resource) {
|
export function resourceAsPath(resource) {
|
||||||
const { name, ship } = resource;
|
const { name, ship } = resource;
|
||||||
return `/ship/~${ship}/${name}`;
|
return `/ship/~${ship}/${name}`;
|
||||||
@ -75,6 +77,14 @@ export function uxToHex(ux) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hexToUx(hex) {
|
||||||
|
const ux = _.chain(hex.split(""))
|
||||||
|
.chunk(4)
|
||||||
|
.map((x) => _.dropWhile(x, (y) => y === 0).join(""))
|
||||||
|
.join(".");
|
||||||
|
return `0x${ux}`;
|
||||||
|
}
|
||||||
|
|
||||||
function hexToDec(hex) {
|
function hexToDec(hex) {
|
||||||
const alphabet = '0123456789ABCDEF'.split('');
|
const alphabet = '0123456789ABCDEF'.split('');
|
||||||
return hex.reverse().reduce((acc, digit, idx) => {
|
return hex.reverse().reduce((acc, digit, idx) => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { UnControlled as CodeEditor } from 'react-codemirror2';
|
import { UnControlled as CodeEditor } from 'react-codemirror2';
|
||||||
|
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
|
||||||
import CodeMirror from 'codemirror';
|
import CodeMirror from 'codemirror';
|
||||||
|
|
||||||
import 'codemirror/mode/markdown/markdown';
|
import 'codemirror/mode/markdown/markdown';
|
||||||
@ -132,7 +133,7 @@ export default class ChatEditor extends Component {
|
|||||||
onChange={(e, d, v) => this.messageChange(e, d, v)}
|
onChange={(e, d, v) => this.messageChange(e, d, v)}
|
||||||
editorDidMount={(editor) => {
|
editorDidMount={(editor) => {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
if (!BROWSER_REGEX.test(navigator.userAgent)) {
|
if (!MOBILE_BROWSER_REGEX.test(navigator.userAgent)) {
|
||||||
editor.focus();
|
editor.focus();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -333,33 +333,6 @@ export default class GroupsApp extends Component<GroupsAppProps, {}> {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Route exact path="/~groups/me"
|
|
||||||
render={(props) => {
|
|
||||||
const me = defaultContacts[window.ship] || {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Skeleton
|
|
||||||
history={props.history}
|
|
||||||
api={api}
|
|
||||||
contacts={contacts}
|
|
||||||
groups={groups}
|
|
||||||
invites={invites}
|
|
||||||
activeDrawer="rightPanel"
|
|
||||||
selected="me"
|
|
||||||
associations={associations}
|
|
||||||
>
|
|
||||||
<ContactCard
|
|
||||||
api={api}
|
|
||||||
history={props.history}
|
|
||||||
path="/~/default"
|
|
||||||
contact={me}
|
|
||||||
s3={s3}
|
|
||||||
ship={window.ship}
|
|
||||||
/>
|
|
||||||
</Skeleton>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Switch>
|
</Switch>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import { Sigil } from "~/logic/lib/sigil";
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { EditElement } from "./edit-element";
|
||||||
|
import { Spinner } from "~/views/components/Spinner";
|
||||||
|
import { uxToHex } from "~/logic/lib/util";
|
||||||
|
import { Col, Input, Box, Text, Row } from "@tlon/indigo-react";
|
||||||
|
import { Formik, Form, FormikHelpers } from "formik";
|
||||||
|
import { Contact } from "~/types/contact-update";
|
||||||
|
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||||
|
import { ColorInput } from "~/views/components/ColorInput";
|
||||||
|
import GlobalApi from "~/logic/api/global";
|
||||||
|
import { ImageInput } from "~/views/components/ImageInput";
|
||||||
|
import { S3State } from "~/types";
|
||||||
|
|
||||||
|
interface ContactCardProps {
|
||||||
|
contact: Contact;
|
||||||
|
path: string;
|
||||||
|
api: GlobalApi;
|
||||||
|
s3: S3State;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formSchema = Yup.object({
|
||||||
|
color: Yup.string(),
|
||||||
|
nickname: Yup.string(),
|
||||||
|
email: Yup.string().matches(
|
||||||
|
new RegExp(
|
||||||
|
String(
|
||||||
|
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*/.source
|
||||||
|
) +
|
||||||
|
/@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/
|
||||||
|
.source
|
||||||
|
),
|
||||||
|
"Not a valid email"
|
||||||
|
),
|
||||||
|
phone: Yup.string().matches(
|
||||||
|
new RegExp(
|
||||||
|
String(/^\s*(?:\+?(\d{1,3}))?/.source) +
|
||||||
|
/([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$/
|
||||||
|
.source
|
||||||
|
),
|
||||||
|
"Not a valid phone"
|
||||||
|
),
|
||||||
|
|
||||||
|
website: Yup.string().matches(
|
||||||
|
new RegExp(
|
||||||
|
String(/[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}/.source) +
|
||||||
|
/\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/.source
|
||||||
|
),
|
||||||
|
"Not a valid website"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function ContactCard(props: ContactCardProps) {
|
||||||
|
const us = `~${window.ship}`;
|
||||||
|
const { contact } = props;
|
||||||
|
const onSubmit = async (values: Contact, actions: FormikHelpers<Contact>) => {
|
||||||
|
try {
|
||||||
|
await Object.keys(values).reduce((acc, key) => {
|
||||||
|
const newValue = key !== "color" ? values[key] : uxToHex(values[key]);
|
||||||
|
if (newValue !== contact[key]) {
|
||||||
|
if (key === "avatar") {
|
||||||
|
return acc.then(() =>
|
||||||
|
props.api.contacts.edit(props.path, us, {
|
||||||
|
avatar: { url: newValue },
|
||||||
|
} as any)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc.then(() =>
|
||||||
|
props.api.contacts.edit(props.path, us, {
|
||||||
|
[key]: newValue,
|
||||||
|
} as any)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, Promise.resolve());
|
||||||
|
actions.setStatus({ success: null });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
actions.setStatus({ error: e.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hexColor = contact.color ? `#${uxToHex(contact.color)}` : "#000000";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box p={4} height="100%" overflowY="auto">
|
||||||
|
<Formik
|
||||||
|
validationSchema={formSchema}
|
||||||
|
initialValues={contact}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<Col>
|
||||||
|
<Row
|
||||||
|
borderBottom={1}
|
||||||
|
borderBottomColor="washedGray"
|
||||||
|
pb={3}
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Sigil size={32} classes="" color={hexColor} ship={us} />
|
||||||
|
<Box ml={2}>
|
||||||
|
<Text fontFamily="mono">{us}</Text>
|
||||||
|
</Box>
|
||||||
|
</Row>
|
||||||
|
<ImageInput mt={3} id="avatar" label="Avatar" s3={props.s3} />
|
||||||
|
<ColorInput id="color" label="Sigil Color" />
|
||||||
|
<Input id="nickname" label="Nickname" />
|
||||||
|
<Input id="email" label="Email" />
|
||||||
|
<Input id="phone" label="Phone" />
|
||||||
|
<Input id="website" label="Website" />
|
||||||
|
<Input id="notes" label="Notes" />
|
||||||
|
<AsyncButton primary loadingText="Updating..." border>
|
||||||
|
Save
|
||||||
|
</AsyncButton>
|
||||||
|
</Col>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -18,30 +18,6 @@ export class GroupSidebar extends Component {
|
|||||||
|
|
||||||
const selectedClass = (props.selected === 'me') ? 'bg-gray4 bg-gray1-d' : 'bg-white bg-gray0-d';
|
const selectedClass = (props.selected === 'me') ? 'bg-gray4 bg-gray1-d' : 'bg-white bg-gray0-d';
|
||||||
|
|
||||||
const rootIdentity = <Link
|
|
||||||
key={1}
|
|
||||||
to={'/~groups/me'}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
'w-100 pl4 pt1 pb1 f9 flex justify-start content-center ' +
|
|
||||||
selectedClass}
|
|
||||||
>
|
|
||||||
<Sigil
|
|
||||||
ship={window.ship}
|
|
||||||
color="#000000"
|
|
||||||
classes="mix-blend-diff"
|
|
||||||
size={32}
|
|
||||||
/>
|
|
||||||
<p
|
|
||||||
className="f9 w-70 dib v-mid ml2 nowrap mono"
|
|
||||||
style={{ paddingTop: 6 }}
|
|
||||||
>
|
|
||||||
{cite(window.ship)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Link>;
|
|
||||||
|
|
||||||
const inviteItems =
|
const inviteItems =
|
||||||
Object.keys(props.invites)
|
Object.keys(props.invites)
|
||||||
.map((uid) => {
|
.map((uid) => {
|
||||||
@ -127,8 +103,6 @@ export class GroupSidebar extends Component {
|
|||||||
<p className="f9 pt4 pl4 green2 bn">Join Group</p>
|
<p className="f9 pt4 pl4 green2 bn">Join Group</p>
|
||||||
</Link>
|
</Link>
|
||||||
<Welcome contacts={props.contacts} />
|
<Welcome contacts={props.contacts} />
|
||||||
<h2 className="f9 pt4 pr4 pb2 pl4 gray2 c-default">Your Identity</h2>
|
|
||||||
{rootIdentity}
|
|
||||||
{inviteItems}
|
{inviteItems}
|
||||||
<h2 className="f9 pt4 pr4 pb2 pl4 gray2 c-default">Groups</h2>
|
<h2 className="f9 pt4 pr4 pb2 pl4 gray2 c-default">Groups</h2>
|
||||||
{groupItems}
|
{groupItems}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, InputLabel, Radio, Input } from '@tlon/indigo-react';
|
import { Box, InputLabel, Radio, Input } from '@tlon/indigo-react';
|
||||||
|
|
||||||
import GlobalApi from '../../../../api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { S3State } from '../../../../types';
|
import { S3State } from '~/types';
|
||||||
import { ImageInput } from './ImageInput';
|
import { ImageInput } from '~/views/components/ImageInput';
|
||||||
|
|
||||||
export type BgType = "none" | "url" | "color";
|
export type BgType = "none" | "url" | "color";
|
||||||
|
|
||||||
|
@ -1,10 +1,26 @@
|
|||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useDrag } from "react-dnd";
|
import { useDrag } from "react-dnd";
|
||||||
import { usePreview } from "react-dnd-multi-backend";
|
import { usePreview } from "react-dnd-multi-backend";
|
||||||
import { capitalize } from 'lodash';
|
import { capitalize } from "lodash";
|
||||||
import { TileTypeBasic, Tile } from "../../../../types/launch-update";
|
import { TileTypeBasic, Tile } from "../../../../types/launch-update";
|
||||||
|
|
||||||
import { Box, Img, Text } from "@tlon/indigo-react";
|
import { Box, Img as _Img, Text } from "@tlon/indigo-react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
// Need to change dojo image
|
||||||
|
const Img = styled(_Img)<{ invert?: boolean }>`
|
||||||
|
${(p) =>
|
||||||
|
p.theme.colors.white !== "rgba(255,255,255,1)" ? `filter: invert(1);` : ``}
|
||||||
|
|
||||||
|
${(p) =>
|
||||||
|
!p.invert
|
||||||
|
? ``
|
||||||
|
: p.theme.colors.white !== "rgba(255,255,255,1)"
|
||||||
|
? `
|
||||||
|
filter: invert(0);
|
||||||
|
`
|
||||||
|
: `filter: invert(1);`}
|
||||||
|
`;
|
||||||
|
|
||||||
interface DragTileProps {
|
interface DragTileProps {
|
||||||
index: number;
|
index: number;
|
||||||
@ -27,6 +43,7 @@ function DragTileBox({ title, index, tile, ...props }: any) {
|
|||||||
justifyContent="space-around"
|
justifyContent="space-around"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
border={1}
|
border={1}
|
||||||
|
borderColor="black"
|
||||||
height="100%"
|
height="100%"
|
||||||
width="100%"
|
width="100%"
|
||||||
style={{ cursor: "move" }}
|
style={{ cursor: "move" }}
|
||||||
@ -38,7 +55,13 @@ function DragTileBox({ title, index, tile, ...props }: any) {
|
|||||||
function DragTileCustom({ index, title, style }: any) {
|
function DragTileCustom({ index, title, style }: any) {
|
||||||
const tile = { type: { custom: null } };
|
const tile = { type: { custom: null } };
|
||||||
return (
|
return (
|
||||||
<DragTileBox bg="white" style={style} title={title} tile={tile} index={index}>
|
<DragTileBox
|
||||||
|
bg="white"
|
||||||
|
style={style}
|
||||||
|
title={title}
|
||||||
|
tile={tile}
|
||||||
|
index={index}
|
||||||
|
>
|
||||||
<Text fontSize={1}>{capitalize(title)}</Text>
|
<Text fontSize={1}>{capitalize(title)}</Text>
|
||||||
</DragTileBox>
|
</DragTileBox>
|
||||||
);
|
);
|
||||||
@ -55,11 +78,19 @@ function DragTileBasic(props: {
|
|||||||
<DragTileBox
|
<DragTileBox
|
||||||
tile={{ type: props.tile }}
|
tile={{ type: props.tile }}
|
||||||
index={props.index}
|
index={props.index}
|
||||||
bg={isDojo ? "black" : "white"}
|
bg={
|
||||||
|
"white" // isDojo ? "black" : "white"
|
||||||
|
}
|
||||||
style={props.style}
|
style={props.style}
|
||||||
>
|
>
|
||||||
<Img width="48px" height="48px" src={tile.iconUrl} />
|
<Img width="48px" height="48px" src={tile.iconUrl} invert={isDojo} />
|
||||||
<Text color={isDojo ? "white" : "black"}>{tile.title}</Text>
|
<Text
|
||||||
|
color={
|
||||||
|
"black" // isDojo ? "white" : "black"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{tile.title}
|
||||||
|
</Text>
|
||||||
</DragTileBox>
|
</DragTileBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,68 +1,124 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { Box, Col, Center, Icon } from "@tlon/indigo-react";
|
import { Box, Text, Row, Col, Center, Icon } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { Sigil } from "~/logic/lib/sigil";
|
import { Sigil } from "~/logic/lib/sigil";
|
||||||
|
import { uxToHex, MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
|
||||||
|
|
||||||
import Settings from "./components/settings";
|
import Settings from "./components/settings";
|
||||||
|
import { Route, Link } from "react-router-dom";
|
||||||
|
import { ContactCard } from "../groups/components/lib/ContactCard";
|
||||||
|
|
||||||
|
const SidebarItem = ({ children, view, current }) => {
|
||||||
|
const selected = current === view;
|
||||||
|
const color = selected ? "blue" : "black";
|
||||||
|
return (
|
||||||
|
<Link to={`/~profile/${view}`}>
|
||||||
|
<Row
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
verticalAlign="middle"
|
||||||
|
py={1}
|
||||||
|
px={3}
|
||||||
|
backgroundColor={selected ? "washedBlue" : "white"}
|
||||||
|
>
|
||||||
|
<Icon mr={2} display="inline-block" icon="Circle" fill={color} />
|
||||||
|
<Text color={color} fontSize={0}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function ProfileScreen(props: any) {
|
export default function ProfileScreen(props: any) {
|
||||||
const { ship, dark } = props;
|
const { ship, dark } = props;
|
||||||
return (
|
return (
|
||||||
<Box height="100%" px={[0,3]} pb={[0,3]} borderRadius={1}>
|
<Route
|
||||||
<Box
|
path={["/~profile/:view", "/~profile"]}
|
||||||
height="100%"
|
render={({ match, history }) => {
|
||||||
width="100%"
|
const { view } = match.params;
|
||||||
display="flex"
|
const contact = props.contacts?.["/~/default"]?.[window.ship];
|
||||||
borderRadius={1}
|
const sigilColor = contact?.color
|
||||||
bg="white"
|
? `#${uxToHex(contact.color)}`
|
||||||
border={1}
|
: dark
|
||||||
borderColor="washedGray"
|
? "#FFFFFF"
|
||||||
>
|
: "#000000";
|
||||||
<Col
|
if(!contact) {
|
||||||
display={["none", "block"]}
|
return null;
|
||||||
collapse
|
}
|
||||||
borderRight={1}
|
if (!view && !MOBILE_BROWSER_REGEX.test(window.navigator.userAgent)) {
|
||||||
borderColor="washedGray"
|
history.replace("/~profile/settings");
|
||||||
>
|
}
|
||||||
<Box borderBottom={1} borderBottomColor="washedGray">
|
|
||||||
|
return (
|
||||||
|
<Box height="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}>
|
||||||
<Box
|
<Box
|
||||||
bg="black"
|
height="100%"
|
||||||
borderRadius={8}
|
width="100%"
|
||||||
margin={4}
|
display="grid"
|
||||||
height={128}
|
gridTemplateColumns={["100%", "200px 1fr"]}
|
||||||
width={128}
|
gridTemplateRows={["48px 1fr", "1fr"]}
|
||||||
display="flex"
|
borderRadius={1}
|
||||||
justifyContent="center"
|
bg="white"
|
||||||
alignItems="center"
|
border={1}
|
||||||
|
borderColor="washedGray"
|
||||||
>
|
>
|
||||||
<Sigil
|
<Col
|
||||||
ship={`~${ship}`}
|
display={!view ? "flex" : ["none", "flex"]}
|
||||||
size={80}
|
alignItems="center"
|
||||||
color={dark ? "#FFFFFF" : "#000000"}
|
borderRight={1}
|
||||||
/>
|
borderColor="washedGray"
|
||||||
|
>
|
||||||
|
<Box width="100%" borderBottom={1} borderBottomColor="washedGray">
|
||||||
|
<Box
|
||||||
|
mx="auto"
|
||||||
|
bg={sigilColor}
|
||||||
|
borderRadius={8}
|
||||||
|
my={4}
|
||||||
|
height={128}
|
||||||
|
width={128}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Sigil ship={`~${ship}`} size={80} color={sigilColor} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box width="100%" py={3}>
|
||||||
|
<SidebarItem current={view} view="settings">
|
||||||
|
Ship Settings
|
||||||
|
</SidebarItem>
|
||||||
|
<SidebarItem current={view} view="identity">
|
||||||
|
Your Identity
|
||||||
|
</SidebarItem>
|
||||||
|
</Box>
|
||||||
|
</Col>
|
||||||
|
<Box
|
||||||
|
display={!view ? "none" : ["flex", "none"]}
|
||||||
|
alignItems="center"
|
||||||
|
px={3}
|
||||||
|
borderBottom={1}
|
||||||
|
borderBottomColor="washedGray"
|
||||||
|
>
|
||||||
|
<Link to="/~profile">{"<- Back"}</Link>
|
||||||
|
</Box>
|
||||||
|
<Box overflowY="auto" flexGrow={1}>
|
||||||
|
{view === "settings" && <Settings {...props} />}
|
||||||
|
|
||||||
|
{view === "identity" && (
|
||||||
|
<ContactCard
|
||||||
|
contact={contact}
|
||||||
|
path="/~/default"
|
||||||
|
api={props.api}
|
||||||
|
s3={props.s3}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box py={4}>
|
);
|
||||||
<Box
|
}}
|
||||||
display="flex"
|
></Route>
|
||||||
alignItems="center"
|
|
||||||
verticalAlign="middle"
|
|
||||||
fontSize={0}
|
|
||||||
py={1}
|
|
||||||
px={3}
|
|
||||||
color="blue"
|
|
||||||
backgroundColor="washedBlue"
|
|
||||||
>
|
|
||||||
<Icon mr={2} display="inline-block" icon="Circle" fill="blue" />
|
|
||||||
Ship Settings
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Col>
|
|
||||||
<Box overflowY="auto" flexGrow={1}>
|
|
||||||
<Settings {...props} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
64
pkg/interface/src/views/components/ColorInput.tsx
Normal file
64
pkg/interface/src/views/components/ColorInput.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useField } from "formik";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { Col, InputLabel, Row, Box, ErrorMessage } from "@tlon/indigo-react";
|
||||||
|
|
||||||
|
import { uxToHex, hexToUx } from "~/logic/lib/util";
|
||||||
|
|
||||||
|
const Input = styled.input`
|
||||||
|
background-color: ${ p => p.theme.colors.white };
|
||||||
|
color: ${ p => p.theme.colors.black };
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid;
|
||||||
|
border-right: none;
|
||||||
|
border-color: ${(p) => p.theme.colors.lightGray};
|
||||||
|
border-top-left-radius: ${(p) => p.theme.radii[2]}px;
|
||||||
|
border-bottom-left-radius: ${(p) => p.theme.radii[2]}px;
|
||||||
|
padding: ${(p) => p.theme.space[2]}px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type ColorInputProps = Parameters<typeof Col>[0] & {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColorInput(props: ColorInputProps) {
|
||||||
|
const { id, label, ...rest } = props;
|
||||||
|
const [{ value }, { error }, { setValue }] = useField(id);
|
||||||
|
|
||||||
|
const hex = value.substr(2).replace('.', '');
|
||||||
|
const padded = hex.padStart(6, '0');
|
||||||
|
|
||||||
|
const onChange = (e: any) => {
|
||||||
|
const { value: newValue } = e.target as HTMLInputElement;
|
||||||
|
const valid = newValue.match(/^(\d|[a-f]|[A-F]){0,6}$/);
|
||||||
|
|
||||||
|
if(!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = hexToUx(newValue);
|
||||||
|
setValue(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col {...rest}>
|
||||||
|
<InputLabel htmlFor={id}>{label}</InputLabel>
|
||||||
|
<Row mt={2}>
|
||||||
|
<Input onChange={onChange} value={hex} />
|
||||||
|
<Box
|
||||||
|
borderBottomRightRadius={1}
|
||||||
|
borderTopRightRadius={1}
|
||||||
|
border={1}
|
||||||
|
borderLeft={0}
|
||||||
|
borderColor="lightGray"
|
||||||
|
width="32px"
|
||||||
|
alignSelf="stretch"
|
||||||
|
bg={`#${padded}`}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
<ErrorMessage mt="2">{error}</ErrorMessage>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
@ -6,19 +6,16 @@ import { useField } from "formik";
|
|||||||
import { S3State } from "~/types/s3-update";
|
import { S3State } from "~/types/s3-update";
|
||||||
import { useS3 } from "~/logic/lib/useS3";
|
import { useS3 } from "~/logic/lib/useS3";
|
||||||
|
|
||||||
interface ImageInputProps {
|
type ImageInputProps = Parameters<typeof Box>[0] & {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
|
||||||
label: string;
|
label: string;
|
||||||
url: string;
|
|
||||||
api: GlobalApi;
|
|
||||||
s3: S3State;
|
s3: S3State;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function ImageInput(props: ImageInputProps) {
|
export function ImageInput(props: ImageInputProps) {
|
||||||
const { name, id, label, url, api } = props;
|
const { id, label, s3, ...rest } = props;
|
||||||
|
|
||||||
const { uploadDefault, canUpload } = useS3(props.s3);
|
const { uploadDefault, canUpload } = useS3(s3);
|
||||||
|
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
||||||
@ -47,8 +44,8 @@ export function ImageInput(props: ImageInputProps) {
|
|||||||
}, [ref]);
|
}, [ref]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex">
|
<Box {...rest} display="flex">
|
||||||
<Input type="text" label={label} id={id} />
|
<Input disabled={uploading} type="text" label={label} id={id} />
|
||||||
{canUpload && (
|
{canUpload && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
@ -1,50 +1,40 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Box, Text } from '@tlon/indigo-react';
|
import { Box, Text } from "@tlon/indigo-react";
|
||||||
|
|
||||||
|
const ReconnectBox = ({ color, children, onClick }) => (
|
||||||
|
<Box
|
||||||
|
ml={2}
|
||||||
|
px={2}
|
||||||
|
py={1}
|
||||||
|
display="flex"
|
||||||
|
color={color}
|
||||||
|
bg="white"
|
||||||
|
alignItems="center"
|
||||||
|
border={1}
|
||||||
|
verticalAlign="middle"
|
||||||
|
lineHeight="0"
|
||||||
|
borderRadius={2}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Text color={color}>{children}</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
const ReconnectButton = ({ connection, subscription }) => {
|
const ReconnectButton = ({ connection, subscription }) => {
|
||||||
const connectedStatus = connection || 'connected';
|
const connectedStatus = connection || "connected";
|
||||||
const reconnect = subscription.restart.bind(subscription);
|
const reconnect = subscription.restart.bind(subscription);
|
||||||
if (connectedStatus === 'disconnected') {
|
if (connectedStatus === "disconnected") {
|
||||||
return (
|
return (
|
||||||
<>
|
<ReconnectBox onClick={reconnect} color="red">
|
||||||
<Box
|
Reconnect ↻
|
||||||
ml={2}
|
</ReconnectBox>
|
||||||
px={2}
|
|
||||||
py={1}
|
|
||||||
display='inline-block'
|
|
||||||
color='red'
|
|
||||||
bg="white"
|
|
||||||
border={1}
|
|
||||||
verticalAlign="middle"
|
|
||||||
lineHeight='0'
|
|
||||||
borderRadius={2}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
onClick={reconnect}>
|
|
||||||
<Text color='red'>Reconnect ↻</Text>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
} else if (connectedStatus === 'reconnecting') {
|
} else if (connectedStatus === "reconnecting") {
|
||||||
return (
|
return <ReconnectBox color="yellow">Reconnecting</ReconnectBox>;
|
||||||
<>
|
} else {
|
||||||
<Box
|
return null;
|
||||||
ml={2}
|
}
|
||||||
px={2}
|
};
|
||||||
py={1}
|
|
||||||
bg='white'
|
|
||||||
lineHeight="0"
|
|
||||||
verticalAlign="middle"
|
|
||||||
display='inline-block'
|
|
||||||
color='yellow'
|
|
||||||
border={1}
|
|
||||||
borderRadius={2}>
|
|
||||||
<Text color='yellow'>Reconnecting</Text>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ReconnectButton;
|
export default ReconnectButton;
|
||||||
|
Loading…
Reference in New Issue
Block a user