Merge branch 'lf/indigo-bump' (#3540)

* origin/lf/indigo-bump:
  publish: set max width on new note title input
  interface: fix flexbox for groupView button layout
  interface: add cursor css to tabs
  publish: use Row for NoteForm layout
  interface: cleanup more indigo shenanigans
  remove fontSize property
  profile: fix form breakage caused by 1.2 migration
  publish: use CodeMirror as editor
  Revert "publish: introduce ProseMirror for markdown editing"
  interface: fix assorted breakage from 1.2 indigo bump
  publish: introduce ProseMirror for markdown editing
  interface: bump indigo version

Signed-off-by: Matilde Park <matilde@tlon.io>
This commit is contained in:
Matilde Park 2020-09-24 18:11:49 -04:00
commit c3991676c2
35 changed files with 610 additions and 407 deletions

View File

@ -1709,9 +1709,21 @@
"integrity": "sha512-3OPSdf9cejP/TSzWXuBaYbzLtAfBzQnc75SlPLkoPfwpxnv1Bvy9hiWngLY0WnKRR6lMOldnkYQCCuNWeDibYQ=="
},
"@tlon/indigo-react": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.1.15.tgz",
"integrity": "sha512-Ao+1hAJjN5y1gDyT7GIUgXORPXTIpZKVVtrS++ZGYBemYMSq3oJFMIZertsSZbDHuh/TsVPenJrMUZBpV60law=="
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.6.tgz",
"integrity": "sha512-Dng+OfQ6ViMrdJXtQvgsVrW9vglPGUmbv0ffi2MSwLfe6FhUdL1CHoOjGxm4pATLOou53Kqcrt4g1Svw7y9THw==",
"requires": {
"@reach/menu-button": "^0.10.5",
"react": "^16.13.1",
"tslib": "^2.0.1"
},
"dependencies": {
"tslib": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
}
}
},
"@types/anymatch": {
"version": "1.3.1",
@ -6895,6 +6907,11 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
},
"normalize-wheel": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU="
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",

View File

@ -9,7 +9,7 @@
"@reach/menu-button": "^0.10.5",
"@reach/tabs": "^0.10.5",
"@tlon/indigo-light": "^1.0.3",
"@tlon/indigo-react": "^1.1.15",
"@tlon/indigo-react": "1.2.6",
"aws-sdk": "^2.726.0",
"classnames": "^2.2.6",
"codemirror": "^5.55.0",

View File

@ -0,0 +1,2 @@
export type PropFunc<T extends (...args: any[]) => any> = Parameters<T>[0];

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { Spinner } from '../../../components/Spinner';
import urbitOb from 'urbit-ob';
import { Box, Text, Input, Button } from '@tlon/indigo-react';
import { Box, Text, ManagedTextInputField as Input, Button } from '@tlon/indigo-react';
import { Formik, Form } from 'formik'
import * as Yup from 'yup';

View File

@ -1,13 +1,17 @@
import React, { Component } from "react";
import React 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 {
ManagedForm as Form,
Col,
ManagedTextInputField as Input,
Box,
Text,
Row,
} from "@tlon/indigo-react";
import { Formik, FormikHelpers } from "formik";
import { Contact } from "~/types/contact-update";
import { AsyncButton } from "~/views/components/AsyncButton";
import { ColorInput } from "~/views/components/ColorInput";
@ -93,30 +97,34 @@ export function ContactCard(props: ContactCardProps) {
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
display="grid"
gridAutoRows="auto"
gridTemplateColumns="100%"
gridRowGap="5"
maxWidth="400px"
>
<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 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>
</Form>
</Formik>
</Box>

View File

@ -1,9 +1,16 @@
import React from 'react';
import { Box, InputLabel, Radio, Input } from '@tlon/indigo-react';
import React from "react";
import {
Box,
Row,
Label,
Col,
ManagedRadioButtonField as Radio,
ManagedTextInputField as Input,
} from "@tlon/indigo-react";
import GlobalApi from '~/logic/api/global';
import { S3State } from '~/types';
import { ImageInput } from '~/views/components/ImageInput';
import GlobalApi from "~/logic/api/global";
import { S3State } from "~/types";
import { ImageInput } from "~/views/components/ImageInput";
export type BgType = "none" | "url" | "color";
@ -18,37 +25,33 @@ export function BackgroundPicker({
api: GlobalApi;
s3: S3State;
}) {
const rowSpace = { my: 0, alignItems: 'center' };
const radioProps = { my: 4, mr: 4, name: 'bgType' };
return (
<Box>
<InputLabel>Landscape Background</InputLabel>
<Box display="flex" alignItems="center">
<Box mt={3} mr={7}>
<Radio label="Image" id="url" name="bgType" />
{bgType === "url" && (
<ImageInput
api={api}
s3={s3}
id="bgUrl"
name="bgUrl"
label="URL"
url={bgUrl || ""}
/>
)}
<Radio label="Color" id="color" name="bgType" />
{bgType === "color" && (
<Input
ml={4}
type="text"
label="Color"
id="bgColor"
name="bgColor"
/>
)}
<Radio label="None" id="none" name="bgType" />
</Box>
</Box>
</Box>
<Col>
<Label mb="2">Landscape Background</Label>
<Row {...rowSpace}>
<Radio {...radioProps} label="Image" id="url" />
{bgType === "url" && (
<ImageInput
ml="3"
api={api}
s3={s3}
id="bgUrl"
name="bgUrl"
label="URL"
url={bgUrl || ""}
/>
)}
</Row>
<Row {...rowSpace}>
<Radio label="Color" id="color" {...radioProps} />
{bgType === "color" && (
<Input ml={4} type="text" label="Color" id="bgColor" />
)}
</Row>
<Radio label="None" id="none" {...radioProps} />
</Col>
);
}

View File

@ -1,7 +1,8 @@
import React, { useCallback } from "react";
import {
Input,
ManagedTextInputField as Input,
ManagedForm as Form,
Box,
Button,
Col,
@ -11,9 +12,9 @@ import {
MenuList,
MenuItem,
} from "@tlon/indigo-react";
import { Formik, Form } from "formik";
import { Formik } from "formik";
import GlobalApi from "../../../../api/global";
import GlobalApi from "~/logic/api/global";
export function BucketList({
buckets,
@ -53,49 +54,48 @@ export function BucketList({
return (
<Formik initialValues={{ newBucket: "" }} onSubmit={onSubmit}>
<Form>
<Col alignItems="start">
{_buckets.map((bucket) => (
<Box
key={bucket}
display="flex"
justifyContent="space-between"
alignItems="center"
borderRadius={1}
border={1}
borderColor="washedGray"
fontSize={1}
pl={2}
mb={2}
width="100%"
>
<Text>{bucket}</Text>
{bucket === selected && (
<Text p={1} color="green">
Active
</Text>
)}
{bucket !== selected && (
<Menu>
<MenuButton sm>Options</MenuButton>
<MenuList>
<MenuItem onSelect={onSelect(bucket)}>Make Active</MenuItem>
<MenuItem onSelect={onDelete(bucket)}>Delete</MenuItem>
</MenuList>
</Menu>
)}
</Box>
))}
<Input
mt={2}
type="text"
label="New Bucket"
id="newBucket"
/>
<Button border borderColor="washedGrey" type="submit">
Add
</Button>
</Col>
<Form
display="grid"
gridTemplateColumns="100%"
gridAutoRows="auto"
gridRowGap={2}
>
{_buckets.map((bucket) => (
<Box
key={bucket}
display="flex"
justifyContent="space-between"
alignItems="center"
borderRadius={1}
border={1}
borderColor="washedGray"
fontSize={1}
pl={2}
mb={2}
>
<Text>{bucket}</Text>
{bucket === selected && (
<Text p={2} color="green">
Active
</Text>
)}
{bucket !== selected && (
<Menu>
<MenuButton border={0} cursor="pointer" width="auto">
Options
</MenuButton>
<MenuList>
<MenuItem onSelect={onSelect(bucket)}>Make Active</MenuItem>
<MenuItem onSelect={onDelete(bucket)}>Delete</MenuItem>
</MenuList>
</Menu>
)}
</Box>
))}
<Input mt="2" label="New Bucket" id="newBucket" />
<Button mt="2" borderColor="washedGrey" type="submit">
Add
</Button>
</Form>
</Formik>
);

View File

@ -2,8 +2,8 @@ import React from "react";
import {
Box,
InputLabel,
Checkbox,
Label,
ManagedCheckboxField as Checkbox,
Button,
} from "@tlon/indigo-react";
import { Formik, Form } from "formik";
@ -14,7 +14,7 @@ import GlobalApi from "../../../../api/global";
import { LaunchState } from "../../../../types/launch-update";
import { DropLaunchTiles } from "./DropLaunch";
import { S3State, BackgroundConfig } from "../../../../types";
import { BackgroundPicker, BgType } from './BackgroundPicker';
import { BackgroundPicker, BgType } from "./BackgroundPicker";
const formSchema = Yup.object().shape({
tileOrdering: Yup.array().of(Yup.string()),
@ -47,14 +47,7 @@ interface DisplayFormProps {
}
export default function DisplayForm(props: DisplayFormProps) {
const {
api,
launch,
background,
hideAvatars,
hideNicknames,
s3
} = props;
const { api, launch, background, hideAvatars, hideNicknames, s3 } = props;
let bgColor, bgUrl;
if (background?.type === "url") {
@ -99,17 +92,17 @@ export default function DisplayForm(props: DisplayFormProps) {
<Form>
<Box
display="grid"
gridTemplateColumns="1fr"
gridTemplateColumns="100%"
gridTemplateRows="auto"
gridRowGap={3}
gridRowGap={5}
>
<Box color="black" fontSize={1} mb={3} fontWeight={900}>
Display Preferences
</Box>
<Box mb={2}>
<InputLabel display="block" pb={2}>
<Label display="block" pb={2}>
Tile Order
</InputLabel>
</Label>
<DropLaunchTiles
id="tileOrdering"
name="tileOrdering"
@ -123,22 +116,20 @@ export default function DisplayForm(props: DisplayFormProps) {
api={api}
s3={s3}
/>
<Box>
<Checkbox
label="Disable avatars"
id="avatars"
caption="Do not show user-set avatars"
/>
<Checkbox
label="Disable nicknames"
id="nicknames"
caption="Do not show user-set nicknames"
/>
</Box>
<Checkbox
label="Disable avatars"
id="avatars"
caption="Do not show user-set avatars"
/>
<Checkbox
label="Disable nicknames"
id="nicknames"
caption="Do not show user-set nicknames"
/>
<Button border={1} borderColor="washedGray" type="submit">
Save
</Button>
</Box>
<Button border={1} borderColor="washedGray" type="submit">
Save
</Button>
</Form>
)}
</Formik>

View File

@ -4,11 +4,11 @@ import { usePreview } from "react-dnd-multi-backend";
import { capitalize } from "lodash";
import { TileTypeBasic, Tile } from "../../../../types/launch-update";
import { Box, Img as _Img, Text } from "@tlon/indigo-react";
import { Box, Image as _Image, Text } from "@tlon/indigo-react";
import styled from "styled-components";
// Need to change dojo image
const Img = styled(_Img)<{ invert?: boolean }>`
const Image = styled(_Image)<{ invert?: boolean }>`
${(p) =>
p.theme.colors.white !== "rgba(255,255,255,1)" ? `filter: invert(1);` : ``}
@ -83,7 +83,7 @@ function DragTileBasic(props: {
}
style={props.style}
>
<Img width="48px" height="48px" src={tile.iconUrl} invert={isDojo} />
<Image width="48px" height="48px" src={tile.iconUrl} invert={isDojo} />
<Text
color={
"black" // isDojo ? "white" : "black"

View File

@ -1,5 +1,9 @@
import React from "react";
import { Box, Button, Checkbox } from '@tlon/indigo-react';
import {
Box,
Button,
ManagedCheckboxField as Checkbox,
} from "@tlon/indigo-react";
import { Formik, Form } from "formik";
import * as Yup from "yup";
@ -10,7 +14,7 @@ const formSchema = Yup.object().shape({
imageShown: Yup.boolean(),
audioShown: Yup.boolean(),
videoShown: Yup.boolean(),
oembedShown: Yup.boolean()
oembedShown: Yup.boolean(),
});
interface FormSchema {
@ -39,7 +43,7 @@ export default function RemoteContentForm(props: RemoteContentFormProps) {
imageShown,
audioShown,
videoShown,
oembedShown
oembedShown,
} as FormSchema
}
onSubmit={(values, actions) => {
@ -47,7 +51,7 @@ export default function RemoteContentForm(props: RemoteContentFormProps) {
imageShown: values.imageShown,
audioShown: values.audioShown,
videoShown: values.videoShown,
oembedShown: values.oembedShown
oembedShown: values.oembedShown,
});
api.local.dehydrate();
actions.setSubmitting(false);
@ -59,36 +63,26 @@ export default function RemoteContentForm(props: RemoteContentFormProps) {
display="grid"
gridTemplateColumns="1fr"
gridTemplateRows="audio"
gridRowGap={3}
gridRowGap={5}
>
<Box color="black" fontSize={1} mb={3} fontWeight={900}>
<Box color="black" fontSize={1} fontWeight={900}>
Remote Content
</Box>
<Box>
<Checkbox
label="Load images"
id="imageShown"
/>
<Checkbox
label="Load audio files"
id="audioShown"
/>
<Checkbox
label="Load video files"
id="videoShown"
/>
<Checkbox
label="Load embedded content"
id="oembedShown"
caption="Embedded content may contain scripts"
/>
</Box>
<Checkbox label="Load images" id="imageShown" />
<Checkbox label="Load audio files" id="audioShown" />
<Checkbox label="Load video files" id="videoShown" />
<Checkbox
label="Load embedded content"
id="oembedShown"
caption="Embedded content may contain scripts"
/>
<Button border={1} borderColor="washedGray" type="submit">
Save
</Button>
</Box>
<Button border={1} borderColor="washedGray" type="submit">
Save
</Button>
</Form>
)}
</Formik>
);
}
}

View File

@ -1,17 +1,18 @@
import React, { useCallback } from "react";
import {
Input,
ManagedTextInputField as Input,
ManagedForm as Form,
Box,
Button,
Col,
Text,
Menu
Menu,
} from "@tlon/indigo-react";
import { Formik, Form } from "formik";
import { Formik } from "formik";
import GlobalApi from "../../../../api/global";
import { BucketList } from './BucketList';
import { BucketList } from "./BucketList";
import { S3State } from "../../../../types";
interface FormSchema {
@ -49,9 +50,6 @@ export default function S3Form(props: S3FormProps) {
return (
<>
<Col>
<Box color="black" mb={4} fontSize={1} fontWeight={900}>
S3 Credentials
</Box>
<Formik
initialValues={
{
@ -64,23 +62,23 @@ export default function S3Form(props: S3FormProps) {
}
onSubmit={onSubmit}
>
<Form>
<Input width="256px" type="text" label="Endpoint" id="s3endpoint" />
<Form
display="grid"
gridTemplateColumns="100%"
gridAutoRows="auto"
gridRowGap={5}
>
<Box color="black" fontSize={1} fontWeight={900}>
S3 Credentials
</Box>
<Input label="Endpoint" id="s3endpoint" />
<Input label="Access Key ID" id="s3accessKeyId" />
<Input
width="256px"
type="text"
label="Access Key ID"
id="s3accessKeyId"
/>
<Input
width="256px"
type="password"
label="Secret Access Key"
id="s3secretAccessKey"
/>
<Button border={1} type="submit">
Submit
</Button>
<Button type="submit">Submit</Button>
</Form>
</Formik>
</Col>

View File

@ -36,7 +36,6 @@ export default function Settings({
return (
<Box
backgroundColor="white"
fontSize={2}
display="grid"
gridTemplateRows="auto"
gridTemplateColumns="1fr"

View File

@ -22,7 +22,7 @@ const SidebarItem = ({ children, view, current }) => {
px={3}
backgroundColor={selected ? "washedBlue" : "white"}
>
<Icon mr={2} display="inline-block" icon="Circle" fill={color} />
<Icon mr={2} display="inline-block" icon="Circle" color={color} />
<Text color={color} fontSize={0}>
{children}
</Text>

View File

@ -2,7 +2,7 @@ import React from "react";
import * as Yup from "yup";
import { Formik, FormikHelpers, Form, useFormikContext } from "formik";
import { AsyncButton } from "../../../../components/AsyncButton";
import { TextArea } from "@tlon/indigo-react";
import { ManagedTextAreaField as TextArea } from "@tlon/indigo-react";
interface FormSchema {
comment: string;
@ -48,7 +48,7 @@ export default function CommentInput(props: CommentInputProps) {
id="comment"
placeholder={props.placeholder || ""}
/>
<AsyncButton loadingText={loading} border type="submit">
<AsyncButton mt={2} loadingText={loading} border type="submit">
{label}
</AsyncButton>
</Form>

View File

@ -38,7 +38,7 @@ export function EditPost(props: EditPostProps & RouteComponentProps) {
<PostForm
initial={initial}
onSubmit={onSubmit}
submitLabel={`Update ${note.title}`}
submitLabel="Update"
loadingText="Updating..."
/>
);

View File

@ -1,5 +1,5 @@
import React, { useCallback, useState, useRef, useEffect } from "react";
import { Col, Text, ErrorMessage } from "@tlon/indigo-react";
import { Col, Text, ErrorLabel } from "@tlon/indigo-react";
import { Spinner } from "~/views/components/Spinner";
import { Notebooks } from "~/types/publish-update";
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
@ -46,7 +46,7 @@ export function JoinScreen(props: JoinScreenProps & RouteComponentProps) {
<Col p={4}>
<Text fontSize={1}>Joining Notebook</Text>
<Spinner awaiting text="Joining..." />
{error && <ErrorMessage>Unable to join notebook</ErrorMessage>}
{error && <ErrorLabel>Unable to join notebook</ErrorLabel>}
</Col>
);
}

View File

@ -0,0 +1,73 @@
import React, { useCallback } from "react";
import { UnControlled as CodeEditor } from "react-codemirror2";
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
import { PropFunc } from "~/types/util";
import CodeMirror from "codemirror";
import "codemirror/mode/markdown/markdown";
import "codemirror/addon/display/placeholder";
import "codemirror/lib/codemirror.css";
import { Box } from "@tlon/indigo-react";
const MARKDOWN_CONFIG = {
name: "markdown",
};
interface MarkdownEditorProps {
placeholder?: string;
value: string;
onChange: (s: string) => void;
onBlur?: (e: any) => void;
}
export function MarkdownEditor(
props: MarkdownEditorProps & PropFunc<typeof Box>
) {
const { onBlur, placeholder, value, onChange, ...boxProps } = props;
const options = {
mode: MARKDOWN_CONFIG,
theme: "tlon",
lineNumbers: false,
lineWrapping: true,
scrollbarStyle: "native",
// cursorHeight: 0.85,
placeholder: placeholder || "",
};
const handleChange = useCallback(
(_e, _d, v: string) => {
onChange(v);
},
[onChange]
);
const handleBlur = useCallback(
(_i, e: any) => {
onBlur && onBlur(e);
},
[onBlur]
);
return (
<Box
flexGrow={1}
position="static"
className="publish"
p={1}
border={1}
borderColor="lightGray"
borderRadius={2}
{...boxProps}
>
<CodeEditor
onBlur={onBlur}
value={value}
options={options}
onChange={handleChange}
/>
</Box>
);
}

View File

@ -1,27 +1,43 @@
import React from 'react';
import styled from 'styled-components';
import { MarkdownEditor as _MarkdownEditor, Box, ErrorMessage } from '@tlon/indigo-react';
import { useField } from 'formik';
import React, { useCallback } from "react";
import _ from "lodash";
import { Box, ErrorLabel } from "@tlon/indigo-react";
import { useField } from "formik";
import { MarkdownEditor } from "./MarkdownEditor";
const MarkdownEditor = styled(_MarkdownEditor)`
border: 1px solid ${(p) => p.theme.colors.lightGray};
border-radius: ${(p) => p.theme.radii[2]}px;
`;
export const MarkdownField = ({
id,
...rest
}: { id: string } & Parameters<typeof Box>[0]) => {
const [{ value, onBlur }, { error, touched }, { setValue }] = useField(id);
export const MarkdownField = ({ id, ...rest }: { id: string; } & Parameters<typeof Box>[0]) => {
const [{ value }, { error, touched }, { setValue, setTouched }] = useField(id);
const handleBlur = useCallback(
(e: any) => {
_.set(e, "target.id", id);
console.log(e);
onBlur && onBlur(e);
},
[onBlur, id]
);
const hasError = !!(error && touched);
return (
<Box overflowY="hidden" width="100%" display="flex" flexDirection="column" {...rest}>
<Box
overflowY="hidden"
width="100%"
display="flex"
flexDirection="column"
{...rest}
>
<MarkdownEditor
onFocus={() => setTouched(true)}
onBlur={() => setTouched(false)}
borderColor={hasError ? "red" : "lightGray"}
onBlur={handleBlur}
value={value}
onBeforeChange={(e, d, v) => setValue(v)}
onChange={setValue}
/>
<ErrorMessage>{touched && error}</ErrorMessage>
<ErrorLabel mt="2" hasError={!!(error && touched)}>
{error}
</ErrorLabel>
</Box>
);
};

View File

@ -3,11 +3,9 @@ import { AsyncButton } from "../../../../components/AsyncButton";
import * as Yup from "yup";
import {
Box,
Input,
Checkbox,
ManagedTextInputField as Input,
ManagedCheckboxField as Checkbox,
Col,
InputLabel,
InputCaption,
Button,
Center,
} from "@tlon/indigo-react";

View File

@ -1,6 +1,11 @@
import React from "react";
import * as Yup from "yup";
import { Box, Input } from "@tlon/indigo-react";
import {
Box,
ManagedTextInputField as Input,
Row,
Col,
} from "@tlon/indigo-react";
import { AsyncButton } from "../../../../components/AsyncButton";
import { Formik, Form, FormikHelpers } from "formik";
import { MarkdownField } from "./MarkdownField";
@ -29,32 +34,29 @@ export function PostForm(props: PostFormProps) {
const { initial, onSubmit, submitLabel, loadingText } = props;
return (
<Box
width="100%"
height="100%"
p={[2, 4]}
display="grid"
justifyItems="start"
gridTemplateRows={["64px 64px 1fr", "64px 1fr"]}
gridTemplateColumns={["100%", "1fr 1fr"]}
gridColumnGap={2}
gridRowGap={2}
>
<Col width="100%" height="100%" p={[2, 4]}>
<Formik
validationSchema={formSchema}
initialValues={initial}
onSubmit={onSubmit}
validateOnBlur
>
<Form style={{ display: "contents" }}>
<Input width="100%" placeholder="Post Title" id="title" />
<Box gridRow={["1/2", "auto"]} mt={1} justifySelf={["start", "end"]}>
<AsyncButton primary loadingText={loadingText}>
<Row flexDirection={["column-reverse", "row"]} mb={4} gapX={4} justifyContent='space-between'>
<Input maxWidth='40rem' flexGrow={1} placeholder="Post Title" id="title" />
<AsyncButton
ml={[0,2]}
mb={[4,0]}
flexShrink={1}
primary
loadingText={loadingText}
>
{submitLabel}
</AsyncButton>
</Box>
<MarkdownField gridColumn={["1/2", "1/3"]} id="body" />
</Row>
<MarkdownField flexGrow={1} id="body" />
</Form>
</Formik>
</Box>
</Col>
);
}

View File

@ -3,35 +3,18 @@ import { Link, RouteComponentProps, Route, Switch } from "react-router-dom";
import { NotebookPosts } from "./NotebookPosts";
import { Subscribers } from "./Subscribers";
import { Settings } from "./Settings";
import { Spinner } from '~/views/components/Spinner';
import { Spinner } from "~/views/components/Spinner";
import { Tabs, Tab } from "~/views/components/Tab";
import { roleForShip } from "~/logic/lib/group";
import {
Box,
Button,
Text,
Tab as _Tab,
Tabs,
TabList as _TabList,
TabPanels,
TabPanel,
Row,
} from "@tlon/indigo-react";
import { Box, Button, Text, Row } from "@tlon/indigo-react";
import { Notebook as INotebook } from "~/types/publish-update";
import { Groups } from "~/types/group-update";
import { Contacts, Rolodex } from "~/types/contact-update";
import GlobalApi from "~/logic/api/global";
import styled from "styled-components";
import {Associations} from "~/types";
import { Associations } from "~/types";
import { deSig } from "~/logic/lib/util";
const TabList = styled(_TabList)`
margin-bottom: ${(p) => p.theme.space[4]}px;
`;
const Tab = styled(_Tab)`
flex-grow: 1;
`;
interface NotebookProps {
api: GlobalApi;
ship: string;
@ -46,18 +29,39 @@ interface NotebookProps {
interface NotebookState {
isUnsubscribing: boolean;
tab: string;
}
export class Notebook extends PureComponent<NotebookProps & RouteComponentProps, NotebookState> {
export class Notebook extends PureComponent<
NotebookProps & RouteComponentProps,
NotebookState
> {
constructor(props) {
super(props);
this.state = {
isUnsubscribing: false
isUnsubscribing: false,
tab: "all",
};
this.setTab = this.setTab.bind(this);
}
setTab(tab: string) {
this.setState({ tab });
}
render() {
const { api, ship, book, notebook, notebookContacts, groups, history, hideNicknames, associations } = this.props;
const {
api,
ship,
book,
notebook,
notebookContacts,
groups,
history,
hideNicknames,
associations,
} = this.props;
const { state } = this;
const group = groups[notebook?.["writers-group-path"]];
if (!group) return null; // Waitin on groups to populate
@ -66,14 +70,14 @@ export class Notebook extends PureComponent<NotebookProps & RouteComponentProps,
const role = group ? roleForShip(group, window.ship) : undefined;
const isOwn = `~${window.ship}` === ship;
const isAdmin = role === "admin" || isOwn;
const isWriter =
isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
const notesList = notebook?.["notes-by-date"] || [];
const notes = notebook?.notes || {};
const showNickname = contact?.nickname && !hideNicknames;
return (
<Box
pt={4}
@ -99,71 +103,103 @@ export class Notebook extends PureComponent<NotebookProps & RouteComponentProps,
<Row justifyContent={["flex-start", "flex-end"]}>
{isWriter && (
<Link to={`/~publish/notebook/${ship}/${book}/new`}>
<Button primary border>
New Post
</Button>
<Button primary>New Post</Button>
</Link>
)}
{!isOwn
? this.state.isUnsubscribing
? <Spinner awaiting={this.state.isUnsubscribing} classes="mt2 ml2" text="Unsubscribing..." />
: <Button ml={isWriter ? 2 : 0} error border onClick={() => {
this.setState({ isUnsubscribing: true });
api.publish.unsubscribeNotebook(deSig(ship), book).then(() => {
history.push("/~publish");
}).catch(() => {
this.setState({ isUnsubscribing: false });
});
}}>
{!isOwn ? (
this.state.isUnsubscribing ? (
<Spinner
awaiting={this.state.isUnsubscribing}
classes="mt2 ml2"
text="Unsubscribing..."
/>
) : (
<Button
ml={isWriter ? 2 : 0}
destructive
onClick={() => {
this.setState({ isUnsubscribing: true });
api.publish
.unsubscribeNotebook(deSig(ship), book)
.then(() => {
history.push("/~publish");
})
.catch(() => {
this.setState({ isUnsubscribing: false });
});
}}
>
Unsubscribe
</Button>
: null
}
)
) : null}
</Row>
<Box gridColumn={["1/2", "1/3"]}>
<Tabs>
<TabList>
<Tab>All Posts</Tab>
<Tab>About</Tab>
{isAdmin && <Tab>Subscribers</Tab>}
{isOwn && <Tab>Settings</Tab>}
</TabList>
<TabPanels>
<TabPanel>
<NotebookPosts
notes={notes}
list={notesList}
host={ship}
book={book}
contacts={notebookContacts}
hideNicknames={hideNicknames}
<Tab
selected={state.tab}
setSelected={this.setTab}
label="All Posts"
id="all"
/>
<Tab
selected={state.tab}
setSelected={this.setTab}
label="About"
id="about"
/>
{isAdmin && (
<>
<Tab
selected={state.tab}
setSelected={this.setTab}
label="Subscribers"
id="subscribers"
/>
</TabPanel>
<TabPanel>
<Box color="black">{notebook?.about}</Box>
</TabPanel>
<TabPanel>
<Subscribers
host={ship}
book={book}
notebook={notebook}
api={api}
groups={groups}
<Tab
selected={state.tab}
setSelected={this.setTab}
label="Settings"
id="settings"
/>
</TabPanel>
<TabPanel>
<Settings
host={ship}
book={book}
api={api}
notebook={notebook}
contacts={notebookContacts}
associations={associations}
groups={groups}
/>
</TabPanel>
</TabPanels>
</>
)}
</Tabs>
{state.tab === "all" && (
<NotebookPosts
notes={notes}
list={notesList}
host={ship}
book={book}
contacts={notebookContacts}
hideNicknames={hideNicknames}
/>
)}
{state.tab === "about" && (
<Box mt="3" color="black">
{notebook?.about}
</Box>
)}
{state.tab === "subscribers" && (
<Subscribers
host={ship}
book={book}
notebook={notebook}
api={api}
groups={groups}
/>
)}
{state.tab === "settings" && (
<Settings
host={ship}
book={book}
api={api}
notebook={notebook}
contacts={notebookContacts}
associations={associations}
groups={groups}
/>
)}
</Box>
</Box>
);

View File

@ -15,7 +15,7 @@ interface NotebookPostsProps {
export function NotebookPosts(props: NotebookPostsProps) {
return (
<Col>
<Col mt="3">
{props.list.map((noteId: NoteId) => {
const note = props.notes[noteId];
if (!note) {

View File

@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { Box, Col, Button, InputLabel, InputCaption } from "@tlon/indigo-react";
import { Box, Col, Button, Label } from "@tlon/indigo-react";
import GlobalApi from "~/logic/api/global";
import { Notebook } from "~/types/publish-update";
import { Contacts } from "~/types/contact-update";
@ -20,7 +20,7 @@ interface SettingsProps {
}
const Divider = (props) => (
<Box {...props} mb={4} borderBottom={1} borderBottomColor="lightGray" />
<Box {...props} borderBottom={1} borderBottomColor="lightGray" />
);
export function Settings(props: SettingsProps) {
const history = useHistory();
@ -36,10 +36,11 @@ export function Settings(props: SettingsProps) {
<Box
mx="auto"
maxWidth="300px"
mb={4}
my={4}
gridTemplateColumns="1fr"
gridAutoRows="auto"
display="grid"
gridRowGap={5}
>
{isUnmanaged && (
<>
@ -50,12 +51,12 @@ export function Settings(props: SettingsProps) {
<MetadataForm {...props} />
<Divider />
<Col mb={4}>
<InputLabel>Delete Notebook</InputLabel>
<InputCaption>
<Label>Delete Notebook</Label>
<Label gray mt="2">
Permanently delete this notebook. (All current members will no longer
see this notebook.)
</InputCaption>
<Button onClick={onDelete} mt={1} border error>
</Label>
<Button mt="2" onClick={onDelete} destructive>
Delete this notebook
</Button>
</Col>

View File

@ -106,7 +106,8 @@ export function Sidebar(props: any) {
pt={[3, 0]}
overflowY="auto"
display={display}
maxWidth={["none", "250px"]}
flexShrink={0}
width={["auto", "250px"]}
>
<Box>
<Link to="/~publish/new" className="green2 pa4 f9 dib">

View File

@ -80,7 +80,7 @@ export class Subscribers extends Component<SubscribersProps> {
const role = roleForShip(group, window.ship)
return (
<Box>
<Box mt="3">
{ role === 'admin' && (
<Button mb={3} border onClick={this.addAll}>
Add all members as writers

View File

@ -58,7 +58,7 @@ export default function NewPost(props: NewPostProps & RouteComponentProps) {
<PostForm
initial={initialValues}
onSubmit={onSubmit}
submitLabel={`Publish to ${notebook?.title}`}
submitLabel="Publish"
loadingText="Posting..."
/>
);

View File

@ -1,5 +1,5 @@
import React, { useCallback } from "react";
import { Box, Input, Col } from "@tlon/indigo-react";
import { Box, ManagedTextInputField as Input, Col } from "@tlon/indigo-react";
import { Formik, Form } from "formik";
import * as Yup from "yup";
import GlobalApi from "~/logic/api/global";

View File

@ -65,12 +65,13 @@
.publish .react-codemirror2 {
width: 100%;
height: 100%;
}
.publish .CodeMirror {
padding: 12px;
height: 100% !important;
max-width: 700px;
overflow-y: hidden;
width: 100% !important;
cursor: text;
font-size: 12px;

View File

@ -1,8 +1,7 @@
import React, { ReactNode, useState, useEffect } from "react";
import { Button } from "@tlon/indigo-react";
import { Button, LoadingSpinner } from "@tlon/indigo-react";
import { Spinner } from "./Spinner";
import { useFormikContext } from "formik";
interface AsyncButtonProps {
@ -37,7 +36,12 @@ export function AsyncButton({
return (
<Button border disabled={!isValid} type="submit" {...rest}>
{isSubmitting ? (
<Spinner awaiting text={loadingText} />
<LoadingSpinner
foreground={rest.primary ? "white" : 'black'}
background="transparent"
awaiting
text={loadingText}
/>
) : success === true ? (
"Done"
) : success === false ? (

View File

@ -1,41 +1,34 @@
import React from "react";
import { useField } from "formik";
import styled from "styled-components";
import { Col, InputLabel, Row, Box, ErrorMessage } from "@tlon/indigo-react";
import {
Col,
Label,
Row,
Box,
ErrorLabel,
StatelessTextInput as Input,
} 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 { id, label, caption, ...rest } = props;
const [{ value, onBlur }, meta, { setValue }] = useField(id);
const hex = value.substr(2).replace('.', '');
const padded = hex.padStart(6, '0');
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) {
if (!valid) {
return;
}
const result = hexToUx(newValue);
@ -43,10 +36,21 @@ export function ColorInput(props: ColorInputProps) {
};
return (
<Col {...rest}>
<InputLabel htmlFor={id}>{label}</InputLabel>
<Row mt={2}>
<Input onChange={onChange} value={hex} />
<Box display="flex" flexDirection="column" {...props}>
<Label htmlFor={id}>{label}</Label>
{caption ? (
<Label mt="2" gray>
{caption}
</Label>
) : null}
<Row mt="2" alignItems="flex-end">
<Input
borderTopRightRadius={0}
borderBottomRightRadius={0}
onBlur={onBlur}
onChange={onChange}
value={hex}
/>
<Box
borderBottomRightRadius={1}
borderTopRightRadius={1}
@ -58,7 +62,9 @@ export function ColorInput(props: ColorInputProps) {
bg={`#${padded}`}
/>
</Row>
<ErrorMessage mt="2">{error}</ErrorMessage>
</Col>
<ErrorLabel mt="2" hasError={!!(meta.touched && meta.error)}>
{meta.error}
</ErrorLabel>
</Box>
);
}

View File

@ -10,9 +10,8 @@ import _ from "lodash";
import Mousetrap from "mousetrap";
import {
Box,
InputLabel,
ErrorMessage,
InputCaption,
Label,
ErrorLabel,
} from "@tlon/indigo-react";
import { useDropdown } from "~/logic/lib/useDropdown";
import styled from "styled-components";
@ -130,8 +129,8 @@ export function DropdownSearch<C>(props: DropdownSearchProps<C>) {
return (
<Box position="relative">
<InputLabel htmlFor={props.id}>{props.label}</InputLabel>
{caption ? <InputCaption>{caption}</InputCaption> : null}
<Label htmlFor={props.id}>{props.label}</Label>
{caption ? <Label mt="2" gray>{caption}</Label> : null}
{!props.disabled && (
<TextArea
ref={textarea}
@ -165,7 +164,7 @@ export function DropdownSearch<C>(props: DropdownSearchProps<C>) {
})}
</Box>
)}
<ErrorMessage>{props.error}</ErrorMessage>
<ErrorLabel>{props.error}</ErrorLabel>
</Box>
);
}

View File

@ -1,6 +1,6 @@
import React from "react";
import { useFormikContext } from "formik";
import { ErrorMessage } from "@tlon/indigo-react";
import { ErrorLabel } from "@tlon/indigo-react";
export function FormError(props: { message: string }) {
const { status } = useFormikContext();
@ -8,6 +8,6 @@ export function FormError(props: { message: string }) {
let s = status || {};
return (
<ErrorMessage>{"error" in s ? props.message : null}</ErrorMessage>
<ErrorLabel>{"error" in s ? props.message : null}</ErrorLabel>
);
}

View File

@ -25,16 +25,16 @@ class GroupMember extends Component<{ ship: Patp; options: any[] }, {}> {
return (
<div className='flex justify-between f9 items-center'>
<div className='flex flex-column'>
<div className='flex flex-column flex-shrink-0'>
<Text mono mr='2'>{`${cite(ship)}`}</Text>
{children}
</div>
{options.length > 0 && (
<Menu>
<MenuButton sm>Options</MenuButton>
<MenuButton width='min-content'>Options</MenuButton>
<MenuList>
{options.map(({ onSelect, text }) => (
<MenuItem onSelect={onSelect}>{text}</MenuItem>
<MenuItem onSelect={onSelect}><Text fontsize='0'>{text}</Text></MenuItem>
))}
</MenuList>
</Menu>

View File

@ -1,7 +1,13 @@
import React, { useRef, useCallback, useState } from "react";
import { Box, Input, Img, Button } from "@tlon/indigo-react";
import GlobalApi from "~/api/global";
import {
Box,
StatelessTextInput as Input,
Row,
Button,
Label,
ErrorLabel,
} from "@tlon/indigo-react";
import { useField } from "formik";
import { S3State } from "~/types/s3-update";
import { useS3 } from "~/logic/lib/useS3";
@ -10,16 +16,17 @@ type ImageInputProps = Parameters<typeof Box>[0] & {
id: string;
label: string;
s3: S3State;
placeholder?: string;
};
export function ImageInput(props: ImageInputProps) {
const { id, label, s3, ...rest } = props;
const { id, label, s3, caption, placeholder, ...rest } = props;
const { uploadDefault, canUpload } = useS3(s3);
const [uploading, setUploading] = useState(false);
const [, , { setValue, setError }] = useField(id);
const [field, meta, { setValue, setError }] = useField(id);
const ref = useRef<HTMLInputElement | null>(null);
@ -44,29 +51,44 @@ export function ImageInput(props: ImageInputProps) {
}, [ref]);
return (
<Box {...rest} display="flex">
<Input disabled={uploading} type="text" label={label} id={id} />
{canUpload && (
<>
<Button
ml={1}
border={3}
borderColor="washedGray"
style={{ marginTop: "18px" }}
onClick={onClick}
>
{uploading ? "Uploading" : "Upload"}
</Button>
<input
style={{ display: "none" }}
type="file"
id="fileElement"
ref={ref}
accept="image/*"
onChange={onImageUpload}
/>
</>
)}
<Box display="flex" flexDirection="column" {...props}>
<Label htmlFor={id}>{label}</Label>
{caption ? (
<Label mt="2" gray>
{caption}
</Label>
) : null}
<Row mt="2" alignItems="flex-end">
<Input
type={"text"}
hasError={meta.touched && meta.error !== undefined}
placeholder={placeholder}
{...field}
/>
{canUpload && (
<>
<Button
ml={1}
border={1}
borderColor="lightGray"
onClick={onClick}
>
{uploading ? "Uploading" : "Upload"}
</Button>
<input
style={{ display: "none" }}
type="file"
id="fileElement"
ref={ref}
accept="image/*"
onChange={onImageUpload}
/>
</>
)}
</Row>
<ErrorLabel mt="2" hasError={!!(meta.touched && meta.error)}>
{meta.error}
</ErrorLabel>
</Box>
);
}

View File

@ -0,0 +1,32 @@
import React from "react";
import { Box, Text, Row } from "@tlon/indigo-react";
export const Tab = ({ selected, id, label, setSelected }) => (
<Box
py={2}
borderBottom={1}
borderBottomColor={selected === id ? "black" : "washedGray"}
px={2}
cursor='pointer'
flexGrow={1}
display="flex"
alignItems="center"
justifyContent="center"
onClick={() => setSelected(id)}
>
<Text color={selected === id ? "black" : "gray"}>{label}</Text>
</Box>
);
export const Tabs = ({ children, ...rest }: Parameters<typeof Row>[0]) => (
<Row
bg="white"
mb={2}
justifyContent="stretch"
alignItems="flex-end"
{...rest}
>
{children}
</Row>
);