mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-10 18:21:34 +03:00
landscape: migrate global components to indigo-react
Fixes urbit/landscape#50.
This commit is contained in:
parent
2834d6b2e6
commit
d9d7edf720
@ -1,5 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Text, Box, Col } from '@tlon/indigo-react';
|
||||
import { Text, Box, Col, Button, BaseAnchor } from '@tlon/indigo-react';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@ -13,6 +13,8 @@ const Summary = styled.summary`
|
||||
color: ${ p => p.theme.colors.black };
|
||||
`;
|
||||
|
||||
const Details = styled.details``;
|
||||
|
||||
class ErrorComponent extends Component<ErrorProps> {
|
||||
render () {
|
||||
const { code, error, history, description } = this.props;
|
||||
@ -25,20 +27,20 @@ class ErrorComponent extends Component<ErrorProps> {
|
||||
</Box>
|
||||
{ description && (<Box mb={4}><Text>{description}</Text></Box>) }
|
||||
{error && (
|
||||
<Box mb={4} style={{maxWidth: '100%'}}>
|
||||
<Box mb={4} style={{ maxWidth: '100%' }}>
|
||||
<Box mb={2}>
|
||||
<Text fontFamily="mono"><code>“{error.message}”</code></Text>
|
||||
<Text mono>“{error.message}”</Text>
|
||||
</Box>
|
||||
<details>
|
||||
<Details>
|
||||
<Summary>Stack trace</Summary>
|
||||
<Text><pre style={{ wordWrap: 'break-word', overflowX: 'scroll' }} className="tl">{error.stack}</pre></Text>
|
||||
</details>
|
||||
<Text mono p='1' borderRadius='1' display='block' overflow='auto' backgroundColor='washedGray' style={{ whiteSpace: 'pre', wordWrap: 'break-word' }}>{error.stack}</Text>
|
||||
</Details>
|
||||
</Box>
|
||||
)}
|
||||
<Text mb={4} textAlign="center">If this is unexpected, email <code>support@tlon.io</code> or <a className="bb" href="https://github.com/urbit/urbit/issues/new/choose">submit an issue</a>.</Text>
|
||||
<Text mb={4} textAlign="center">If this is unexpected, email <code>support@tlon.io</code> or <BaseAnchor color='black' href="https://github.com/urbit/urbit/issues/new/choose">submit an issue</BaseAnchor>.</Text>
|
||||
{history.length > 1
|
||||
? <button className="bg-light-green green2 pa2 pointer" onClick={() => history.go(-1) }>Go back</button>
|
||||
: <button className="bg-light-green green2 pa2 pointer" onClick={() => history.push('/') }>Go home</button>
|
||||
? <Button primary onClick={() => history.go(-1) }>Go back</Button>
|
||||
: <Button primary onClick={() => history.push('/') }>Go home</Button>
|
||||
}
|
||||
</Col>
|
||||
);
|
||||
|
@ -1,350 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import _, { capitalize } from 'lodash';
|
||||
import { Virtuoso as VirtualList } from 'react-virtuoso';
|
||||
|
||||
import { cite, deSig } from '~/logic/lib/util';
|
||||
import { roleForShip, resourceFromPath } from '~/logic/lib/group';
|
||||
import {
|
||||
Group,
|
||||
InvitePolicy,
|
||||
OpenPolicy,
|
||||
roleTags,
|
||||
Groups,
|
||||
} from '~/types/group-update';
|
||||
import { Path, PatpNoSig, Patp } from '~/types/noun';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Menu, MenuButton, MenuList, MenuItem, Text } from '@tlon/indigo-react';
|
||||
import InviteSearch, { Invites } from './InviteSearch';
|
||||
import { Spinner } from './Spinner';
|
||||
import { Rolodex } from '~/types/contact-update';
|
||||
import { Associations } from '~/types/metadata-update';
|
||||
|
||||
class GroupMember extends Component<{ ship: Patp; options: any[] }, {}> {
|
||||
render() {
|
||||
const { ship, options, children } = this.props;
|
||||
|
||||
return (
|
||||
<div className='flex justify-between f9 items-center'>
|
||||
<div className='flex flex-column flex-shrink-0'>
|
||||
<Text mono mr='2'>{`${cite(ship)}`}</Text>
|
||||
{children}
|
||||
</div>
|
||||
{options.length > 0 && (
|
||||
<Menu>
|
||||
<MenuButton width='min-content'>Options</MenuButton>
|
||||
<MenuList>
|
||||
{options.map(({ onSelect, text }) => (
|
||||
<MenuItem onSelect={onSelect}><Text fontsize='0'>{text}</Text></MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Tag extends Component<{ description: string; onRemove?: () => any }, {}> {
|
||||
render() {
|
||||
const { description, onRemove } = this.props;
|
||||
return (
|
||||
<div className='br-pill ba b-black b--white-d r-full items-center ph2 f9 mr2 flex'>
|
||||
<Text>{description}</Text>
|
||||
{Boolean(onRemove) && (
|
||||
<Text onClick={onRemove} ml='1' style={{ cursor: 'pointer' }}>
|
||||
✗
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface GroupViewAppTag {
|
||||
tag: string;
|
||||
app: string;
|
||||
desc: string;
|
||||
addDesc: string;
|
||||
}
|
||||
|
||||
interface GroupViewProps {
|
||||
group: Group;
|
||||
groups: Groups;
|
||||
contacts: Rolodex;
|
||||
associations: Associations;
|
||||
resourcePath: Path;
|
||||
appTags?: GroupViewAppTag[];
|
||||
api: GlobalApi;
|
||||
className: string;
|
||||
permissions?: boolean;
|
||||
inviteShips: (ships: PatpNoSig[]) => Promise<any>;
|
||||
}
|
||||
|
||||
export class GroupView extends Component<
|
||||
GroupViewProps,
|
||||
{ invites: Invites; awaiting: boolean }
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.setInvites = this.setInvites.bind(this);
|
||||
this.inviteShips = this.inviteShips.bind(this);
|
||||
this.state = {
|
||||
invites: {
|
||||
ships: [],
|
||||
groups: [],
|
||||
},
|
||||
awaiting: false
|
||||
};
|
||||
}
|
||||
|
||||
removeUser(who: PatpNoSig) {
|
||||
return () => {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
this.props.api.groups.remove(resource, [`~${who}`]);
|
||||
};
|
||||
}
|
||||
|
||||
banUser(who: PatpNoSig) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
this.props.api.groups.changePolicy(resource, {
|
||||
open: {
|
||||
banShips: [`~${who}`],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
allowUser(who: PatpNoSig) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
this.props.api.groups.changePolicy(resource, {
|
||||
open: {
|
||||
allowShips: [`~${who}`],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
removeInvite(who: PatpNoSig) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
this.props.api.groups.changePolicy(resource, {
|
||||
invite: {
|
||||
removeInvites: [`~${who}`],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
removeTag(who: PatpNoSig, tag: any) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
|
||||
return this.props.api.groups.removeTag(resource, tag, [`~${who}`]);
|
||||
}
|
||||
|
||||
addTag(who: PatpNoSig, tag: any) {
|
||||
const resource = resourceFromPath(this.props.resourcePath);
|
||||
return this.props.api.groups.addTag(resource, tag, [`~${who}`]);
|
||||
}
|
||||
|
||||
isAdmin(): boolean {
|
||||
const role = roleForShip(this.props.group, window.ship);
|
||||
return role === 'admin';
|
||||
}
|
||||
|
||||
optionsForShip(ship: Patp, missing: GroupViewAppTag[]) {
|
||||
const { permissions, resourcePath, group } = this.props;
|
||||
const resource = resourceFromPath(resourcePath);
|
||||
let options: any[] = [];
|
||||
if (!permissions) {
|
||||
return options;
|
||||
}
|
||||
const role = roleForShip(group, ship);
|
||||
const myRole = roleForShip(group, window.ship);
|
||||
if (role === 'admin' || resource.ship === ship) {
|
||||
return [];
|
||||
}
|
||||
if (
|
||||
'open' in group.policy // If blacklist, not whitelist
|
||||
&& (this.isAdmin()) // And we can ban people (TODO: add || role === 'moderator')
|
||||
&& ship !== window.ship // We can't ban ourselves
|
||||
) {
|
||||
options.unshift({ text: 'Ban', onSelect: () => this.banUser(ship) });
|
||||
}
|
||||
if (this.isAdmin() && !role) {
|
||||
options = options.concat(
|
||||
missing.map(({ addDesc, tag, app }) => ({
|
||||
text: addDesc,
|
||||
onSelect: () => this.addTag(ship, { tag, app }),
|
||||
}))
|
||||
);
|
||||
options = options.concat(
|
||||
roleTags.reduce(
|
||||
(acc, role) => [
|
||||
...acc,
|
||||
{
|
||||
text: `Make ${capitalize(role)}`,
|
||||
onSelect: () => this.addTag(ship, { tag: role }),
|
||||
},
|
||||
],
|
||||
[] as any[]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
doIfAdmin<Ret>(f: () => Ret) {
|
||||
return this.isAdmin() ? f : undefined;
|
||||
}
|
||||
|
||||
getAppTags(ship: Patp): [GroupViewAppTag[], GroupViewAppTag[]] {
|
||||
const { tags } = this.props.group;
|
||||
const { appTags } = this.props;
|
||||
|
||||
return _.partition(appTags, ({ app, tag }) => {
|
||||
return tags?.[app]?.[tag]?.has(ship);
|
||||
});
|
||||
}
|
||||
|
||||
memberElements() {
|
||||
const { group, permissions } = this.props;
|
||||
const { members } = group;
|
||||
const isAdmin = this.isAdmin();
|
||||
return Array.from(members).map((ship) => {
|
||||
const role = roleForShip(group, deSig(ship));
|
||||
const onRoleRemove =
|
||||
role && isAdmin
|
||||
? () => {
|
||||
this.removeTag(ship, { tag: role });
|
||||
}
|
||||
: undefined;
|
||||
const [present, missing] = this.getAppTags(ship);
|
||||
const options = this.optionsForShip(ship, missing);
|
||||
|
||||
return (
|
||||
<GroupMember ship={ship} options={options}>
|
||||
{((permissions && role) || present.length > 0) && (
|
||||
<div className='flex mt1'>
|
||||
{role && (
|
||||
<Tag
|
||||
onRemove={onRoleRemove}
|
||||
description={capitalize(role)}
|
||||
/>
|
||||
)}
|
||||
{present.map((tag, idx) => (
|
||||
<Tag
|
||||
key={idx}
|
||||
onRemove={this.doIfAdmin(() =>
|
||||
this.removeTag(ship, tag)
|
||||
)}
|
||||
description={tag.desc}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</GroupMember>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
setInvites(invites: Invites) {
|
||||
this.setState({ invites });
|
||||
}
|
||||
|
||||
inviteShips(invites: Invites) {
|
||||
const { props, state } = this;
|
||||
this.setState({ awaiting: true });
|
||||
props.inviteShips(invites.ships).then(() => {
|
||||
this.setState({ invites: { ships: [], groups: [] }, awaiting: false });
|
||||
});
|
||||
}
|
||||
|
||||
renderInvites(policy: InvitePolicy) {
|
||||
const { props, state } = this;
|
||||
const ships = Array.from(policy.invite.pending || []);
|
||||
|
||||
const options = (ship: Patp) => [
|
||||
{ text: 'Uninvite', onSelect: () => this.removeInvite(ship) },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='flex flex-column'>
|
||||
<div className='f9 gray2 mt6 mb3'>Pending</div>
|
||||
{ships.map((ship) => (
|
||||
<GroupMember key={ship} ship={ship} options={options(ship)} />
|
||||
))}
|
||||
{ships.length === 0 && <Text>No ships are pending</Text>}
|
||||
{props.inviteShips && this.isAdmin() && (
|
||||
<>
|
||||
<div className='f9 gray2 mt6 mb3'>Invite</div>
|
||||
<div style={{ width: 'calc(min(400px, 100%)' }}>
|
||||
<InviteSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
shipResults
|
||||
groupResults={false}
|
||||
invites={state.invites}
|
||||
setInvite={this.setInvites}
|
||||
associations={props.associations}
|
||||
/>
|
||||
</div>
|
||||
<a
|
||||
onClick={() => this.inviteShips(state.invites)}
|
||||
className='db ba tc w-auto mr-auto mt2 ph2 black white-d f8 pointer'
|
||||
>
|
||||
Invite
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBanned(policy: OpenPolicy) {
|
||||
const ships = Array.from(policy.open.banned || []);
|
||||
|
||||
const options = (ship: Patp) => [
|
||||
{ text: 'Unban', onSelect: () => this.allowUser(ship) },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='flex flex-column'>
|
||||
<div className='f9 gray2 mt6 mb3'>Banned</div>
|
||||
{ships.map((ship) => (
|
||||
<GroupMember key={ship} ship={ship} options={options(ship)} />
|
||||
))}
|
||||
{ships.length === 0 && <Text>No ships are banned</Text>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { group, resourcePath, className } = this.props;
|
||||
const resource = resourceFromPath(resourcePath);
|
||||
const memberElements = this.memberElements();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='flex flex-column'>
|
||||
<Text gray display='block'>Host</Text>
|
||||
<div className='flex justify-between mt3'>
|
||||
<Text mono mr='2'>{cite(resource.ship)}</Text>
|
||||
</div>
|
||||
</div>
|
||||
{'invite' in group.policy && this.renderInvites(group.policy)}
|
||||
{'open' in group.policy && this.renderBanned(group.policy)}
|
||||
<div className='flex flex-column'>
|
||||
<div className='f9 gray2 mt6 mb3'>Members</div>
|
||||
<VirtualList
|
||||
style={{ height: '500px', width: '100%' }}
|
||||
totalCount={memberElements.length}
|
||||
item={(index) => <div key={index} className='flex flex-column pv3'>{memberElements[index]}</div>}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes='mt4'
|
||||
text='Inviting to chat...'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import {
|
||||
Button,
|
||||
Label,
|
||||
ErrorLabel,
|
||||
BaseInput
|
||||
} from "@tlon/indigo-react";
|
||||
import { useField } from "formik";
|
||||
import { S3State } from "~/types/s3-update";
|
||||
@ -75,7 +76,7 @@ export function ImageInput(props: ImageInputProps) {
|
||||
>
|
||||
{uploading ? "Uploading" : "Upload"}
|
||||
</Button>
|
||||
<input
|
||||
<BaseInput
|
||||
style={{ display: "none" }}
|
||||
type="file"
|
||||
id="fileElement"
|
||||
|
@ -1,540 +0,0 @@
|
||||
import React, { Component, createRef } from 'react';
|
||||
import _ from 'lodash';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { PatpNoSig, Path } from '~/types/noun';
|
||||
import { Groups} from '~/types/group-update';
|
||||
import { Rolodex, Contact } from '~/types/contact-update';
|
||||
import { Associations } from '~/types/metadata-update';
|
||||
|
||||
export interface Invites {
|
||||
ships: PatpNoSig[];
|
||||
groups: string[][];
|
||||
}
|
||||
|
||||
interface InviteSearchProps {
|
||||
groups: Groups;
|
||||
contacts: Rolodex;
|
||||
groupResults: boolean;
|
||||
shipResults: boolean;
|
||||
invites: Invites;
|
||||
setInvite: (inv: Invites) => void;
|
||||
disabled?: boolean;
|
||||
associations?: Associations;
|
||||
}
|
||||
|
||||
interface InviteSearchState {
|
||||
groups: string[][];
|
||||
peers: PatpNoSig[];
|
||||
contacts: Map<PatpNoSig, string[]>;
|
||||
searchValue: string;
|
||||
searchResults: Invites;
|
||||
selected: PatpNoSig | Path | null;
|
||||
inviteError: boolean;
|
||||
}
|
||||
|
||||
export class InviteSearch extends Component<
|
||||
InviteSearchProps,
|
||||
InviteSearchState
|
||||
> {
|
||||
textarea: React.RefObject<HTMLTextAreaElement> = createRef();
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
groups: [],
|
||||
peers: [],
|
||||
contacts: new Map(),
|
||||
searchValue: '',
|
||||
searchResults: {
|
||||
groups: [],
|
||||
ships: [],
|
||||
},
|
||||
selected: null,
|
||||
inviteError: false,
|
||||
};
|
||||
this.search = this.search.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.peerUpdate();
|
||||
this.bindShortcuts();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps !== this.props) {
|
||||
this.peerUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
peerUpdate() {
|
||||
const groups = Array.from(Object.keys(this.props.contacts))
|
||||
.filter((e) => !e.startsWith('/~/'))
|
||||
.map((e) => {
|
||||
const eachGroup: Path[] = [];
|
||||
eachGroup.push(e);
|
||||
if (this.props.associations) {
|
||||
let name = e;
|
||||
if (e in this.props.associations) {
|
||||
name =
|
||||
this.props.associations[e].metadata.title !== ''
|
||||
? this.props.associations[e].metadata.title
|
||||
: e;
|
||||
}
|
||||
eachGroup.push(name);
|
||||
}
|
||||
return Array.from(eachGroup);
|
||||
});
|
||||
|
||||
let peers: PatpNoSig[] = [];
|
||||
const peerSet = new Set<PatpNoSig>();
|
||||
const contacts = new Map();
|
||||
|
||||
_.map(this.props.groups, (group, path) => {
|
||||
if (group.members.size > 0) {
|
||||
const groupEntries = group.members.values();
|
||||
for (const member of groupEntries) {
|
||||
peerSet.add(member);
|
||||
}
|
||||
}
|
||||
|
||||
const groupContacts = this.props.contacts[path];
|
||||
|
||||
if (groupContacts) {
|
||||
const groupEntries = group.members.values();
|
||||
for (const member of groupEntries) {
|
||||
if (groupContacts[member]) {
|
||||
if (contacts.has(member)) {
|
||||
contacts
|
||||
.get(member)
|
||||
.push(groupContacts[member].nickname);
|
||||
} else {
|
||||
contacts.set(member, [
|
||||
groupContacts[member].nickname,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
peers = Array.from(peerSet);
|
||||
|
||||
this.setState({ groups: groups, peers: peers, contacts: contacts });
|
||||
}
|
||||
|
||||
search(event) {
|
||||
const searchTerm = event.target.value.toLowerCase().replace('~', '');
|
||||
const { state, props } = this;
|
||||
|
||||
this.setState({ searchValue: event.target.value });
|
||||
|
||||
if (searchTerm.length < 1) {
|
||||
this.setState({ searchResults: { groups: [], ships: [] } });
|
||||
}
|
||||
|
||||
if (searchTerm.length > 0) {
|
||||
if (state.inviteError === true) {
|
||||
this.setState({ inviteError: false });
|
||||
}
|
||||
|
||||
let groupMatches = !props.groupResults ? [] :
|
||||
state.groups.filter((e) => {
|
||||
return (
|
||||
e[0].includes(searchTerm) || e[1].toLowerCase().includes(searchTerm)
|
||||
);
|
||||
});
|
||||
|
||||
let shipMatches = !props.shipResults ? [] :
|
||||
state.peers.filter((e) => {
|
||||
return (
|
||||
e.includes(searchTerm) && !props.invites.ships.includes(e)
|
||||
);
|
||||
});
|
||||
|
||||
for (const contact of state.contacts.keys()) {
|
||||
const thisContact = state.contacts.get(contact) || [];
|
||||
const match = thisContact.filter((e) => {
|
||||
return e.toLowerCase().includes(searchTerm);
|
||||
});
|
||||
if (match.length > 0) {
|
||||
if (!(contact in shipMatches) && props.shipResults) {
|
||||
shipMatches.push(contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isValid = true;
|
||||
if (!urbitOb.isValidPatp('~' + searchTerm)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (props.shipResults && isValid && shipMatches.findIndex((s) => s === searchTerm) < 0) {
|
||||
shipMatches.unshift(searchTerm);
|
||||
}
|
||||
|
||||
const { selected } = state;
|
||||
const groupIdx = groupMatches.findIndex(([path]) => path === selected);
|
||||
const shipIdx = shipMatches.findIndex((ship) => ship === selected);
|
||||
const staleSelection = groupIdx < 0 && shipIdx < 0;
|
||||
if (!selected || staleSelection) {
|
||||
const newSelection = _.get(groupMatches, '[0][0]') || shipMatches[0];
|
||||
this.setState({ selected: newSelection });
|
||||
}
|
||||
|
||||
if (searchTerm.length < 3) {
|
||||
groupMatches = groupMatches
|
||||
.filter(([, name]) =>
|
||||
name
|
||||
.toLowerCase()
|
||||
.split(' ')
|
||||
.some((s) => s.startsWith(searchTerm))
|
||||
)
|
||||
.sort((a, b) => a[1].length - b[1].length);
|
||||
|
||||
shipMatches = shipMatches.slice(0, 3);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
searchResults: { groups: groupMatches, ships: shipMatches },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bindShortcuts() {
|
||||
const mousetrap = Mousetrap(this.textarea.current);
|
||||
mousetrap.bind(['down', 'tab'], (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.nextSelection();
|
||||
});
|
||||
|
||||
mousetrap.bind(['up', 'shift+tab'], (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.nextSelection(true);
|
||||
});
|
||||
|
||||
mousetrap.bind('enter', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const { selected } = this.state;
|
||||
if (selected && selected.startsWith('/')) {
|
||||
this.addGroup(selected);
|
||||
} else if (selected) {
|
||||
this.addShip(selected);
|
||||
}
|
||||
this.setState({ selected: null });
|
||||
});
|
||||
}
|
||||
nextSelection(backward = false) {
|
||||
const { selected, searchResults } = this.state;
|
||||
const { ships, groups } = searchResults;
|
||||
if (!selected) {
|
||||
return;
|
||||
}
|
||||
let groupIdx = groups.findIndex(([path]) => path === selected);
|
||||
let shipIdx = ships.findIndex((ship) => ship === selected);
|
||||
if (groupIdx >= 0) {
|
||||
backward ? groupIdx-- : groupIdx++;
|
||||
let selected = _.get(groups, [groupIdx, 0]);
|
||||
if (groupIdx === groups.length) {
|
||||
selected = ships.length === 0 ? groups[0][0] : ships[0];
|
||||
}
|
||||
if (groupIdx < 0) {
|
||||
selected =
|
||||
ships.length === 0
|
||||
? groups[groups.length - 1][0]
|
||||
: ships[ships.length - 1];
|
||||
}
|
||||
this.setState({ selected });
|
||||
return;
|
||||
}
|
||||
if (shipIdx >= 0) {
|
||||
backward ? shipIdx-- : shipIdx++;
|
||||
let selected = ships[shipIdx];
|
||||
if (shipIdx === ships.length) {
|
||||
selected = groups.length === 0 ? ships[0] : groups[0][0];
|
||||
}
|
||||
|
||||
if (shipIdx < 0) {
|
||||
selected =
|
||||
groups.length === 0
|
||||
? ships[ships.length - 1]
|
||||
: groups[groups.length - 1][0];
|
||||
}
|
||||
|
||||
this.setState({ selected });
|
||||
}
|
||||
}
|
||||
deleteGroup() {
|
||||
const { ships } = this.props.invites;
|
||||
this.setState({
|
||||
searchValue: '',
|
||||
searchResults: { groups: [], ships: [] },
|
||||
});
|
||||
this.props.setInvite({ groups: [], ships: ships });
|
||||
}
|
||||
|
||||
deleteShip(ship) {
|
||||
let { groups, ships } = this.props.invites;
|
||||
this.setState({
|
||||
searchValue: '',
|
||||
searchResults: { groups: [], ships: [] },
|
||||
});
|
||||
ships = ships.filter((e) => {
|
||||
return e !== ship;
|
||||
});
|
||||
this.props.setInvite({ groups: groups, ships: ships });
|
||||
}
|
||||
|
||||
addGroup(group) {
|
||||
this.setState({
|
||||
searchValue: '',
|
||||
searchResults: { groups: [], ships: [] },
|
||||
});
|
||||
this.props.setInvite({ groups: [group], ships: [] });
|
||||
}
|
||||
|
||||
addShip(ship) {
|
||||
const { groups, ships } = this.props.invites;
|
||||
this.setState({
|
||||
searchValue: '',
|
||||
searchResults: { groups: [], ships: [] },
|
||||
});
|
||||
if (!ships.includes(ship)) {
|
||||
ships.push(ship);
|
||||
}
|
||||
if (groups.length > 0) {
|
||||
return false;
|
||||
}
|
||||
this.props.setInvite({ groups: groups, ships: ships });
|
||||
return true;
|
||||
}
|
||||
|
||||
submitShipToAdd(ship) {
|
||||
const searchTerm = ship.toLowerCase().replace('~', '').trim();
|
||||
let isValid = true;
|
||||
if (!urbitOb.isValidPatp('~' + searchTerm)) {
|
||||
isValid = false;
|
||||
}
|
||||
if (!isValid) {
|
||||
this.setState({ inviteError: true, searchValue: '' });
|
||||
} else if (isValid) {
|
||||
this.addShip(searchTerm);
|
||||
this.setState({ searchValue: '' });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
let searchDisabled = props.disabled;
|
||||
if (props.invites.groups) {
|
||||
if (props.invites.groups.length > 0) {
|
||||
searchDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
let participants = <div />;
|
||||
let searchResults = <div />;
|
||||
|
||||
let placeholder = '';
|
||||
if (props.shipResults) {
|
||||
placeholder = 'ships';
|
||||
}
|
||||
if (props.groupResults) {
|
||||
if (placeholder.length > 0) {
|
||||
placeholder = placeholder + ' or ';
|
||||
}
|
||||
placeholder = placeholder + 'existing groups';
|
||||
}
|
||||
placeholder = 'Search for ' + placeholder;
|
||||
|
||||
let invErrElem = <span />;
|
||||
if (state.inviteError) {
|
||||
invErrElem = (
|
||||
<span className='f9 inter red2 db pt2'>
|
||||
Invited ships must be validly formatted ship names.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
state.searchResults.groups.length > 0 ||
|
||||
state.searchResults.ships.length > 0
|
||||
) {
|
||||
const groupHeader =
|
||||
state.searchResults.groups.length > 0 ? (
|
||||
<p className='f9 gray2 ph3 pb2'>Groups</p>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
|
||||
const shipHeader =
|
||||
state.searchResults.ships.length > 0 ? (
|
||||
<p className='f9 gray2 pv2 ph3'>Ships</p>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
|
||||
const groupResults = state.searchResults.groups.map((group) => {
|
||||
return (
|
||||
<li
|
||||
key={group[0]}
|
||||
className={
|
||||
'list white-d f8 pv2 ph3 pointer' +
|
||||
' hover-bg-gray4 hover-bg-gray1-d ' +
|
||||
(group[1] ? 'inter' : 'mono') +
|
||||
(group[0] === state.selected ? ' bg-gray1-d bg-gray4' : '')
|
||||
}
|
||||
onClick={() => this.addGroup(group[0])}
|
||||
>
|
||||
<span className='mix-blend-diff white'>
|
||||
{group[1] ? group[1] : group[0]}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
const shipResults = Array.from(new Set(state.searchResults.ships)).map((ship) => {
|
||||
const nicknames = (this.state.contacts.get(ship) || [])
|
||||
.filter((e) => {
|
||||
return !(e === '');
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
return (
|
||||
<li
|
||||
key={ship}
|
||||
className={
|
||||
'list mono white-d f8 pv1 ph3 pointer' +
|
||||
' hover-bg-gray4 hover-bg-gray1-d relative' +
|
||||
(ship === state.selected ? ' bg-gray1-d bg-gray4' : '')
|
||||
}
|
||||
onClick={(e) => this.addShip(ship)}
|
||||
>
|
||||
<Sigil
|
||||
ship={'~' + ship}
|
||||
size={24}
|
||||
color='#000000'
|
||||
classes='mix-blend-diff v-mid'
|
||||
/>
|
||||
<span className='v-mid ml2 mw5 truncate dib mix-blend-diff white'>
|
||||
{'~' + ship}
|
||||
</span>
|
||||
<span className='absolute right-1 di truncate mw4 inter f9 pt1 mix-blend-diff white'>
|
||||
{nicknames}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
searchResults = (
|
||||
<div
|
||||
className={
|
||||
'absolute bg-white bg-gray0-d white-d' +
|
||||
' pv3 z-1 w-100 mt1 ba b--white-d overflow-y-scroll mh-16'
|
||||
}
|
||||
>
|
||||
{groupHeader}
|
||||
{groupResults}
|
||||
{shipHeader}
|
||||
{shipResults}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const groupInvites = props.invites.groups || [];
|
||||
const shipInvites = props.invites.ships || [];
|
||||
|
||||
if (groupInvites.length > 0 || shipInvites.length > 0) {
|
||||
const groups = groupInvites.map((group) => {
|
||||
return (
|
||||
<span
|
||||
key={group[0]}
|
||||
className={
|
||||
'f9 mono black pa2 bg-gray5 bg-gray1-d' +
|
||||
' ba b--gray4 b--gray2-d white-d dib mr2 mt2 c-default'
|
||||
}
|
||||
>
|
||||
{group}
|
||||
<span
|
||||
className='white-d ml3 mono pointer'
|
||||
onClick={(e) => this.deleteGroup()}
|
||||
>
|
||||
x
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
const ships = shipInvites.map((ship) => {
|
||||
return (
|
||||
<span
|
||||
key={ship}
|
||||
className={
|
||||
'f9 mono black pa2 bg-gray5 bg-gray1-d' +
|
||||
' ba b--gray4 b--gray2-d white-d dib mr2 mt2 c-default'
|
||||
}
|
||||
>
|
||||
{'~' + ship}
|
||||
<span
|
||||
className='white-d ml3 mono pointer'
|
||||
onClick={(e) => this.deleteShip(ship)}
|
||||
>
|
||||
x
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
participants = (
|
||||
<div
|
||||
className={
|
||||
'f9 gray2 bb bl br b--gray3 b--gray2-d bg-gray0-d ' +
|
||||
'white-d pa3 db w-100 inter'
|
||||
}
|
||||
>
|
||||
<span className='db gray2'>Participants</span>
|
||||
{groups} {ships}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<img
|
||||
src='/~landscape/img/search.png'
|
||||
className='absolute invert-d'
|
||||
style={{
|
||||
height: 16,
|
||||
width: 16,
|
||||
top: 14,
|
||||
left: 12,
|
||||
}}
|
||||
/>
|
||||
<textarea
|
||||
ref={this.textarea}
|
||||
className={
|
||||
'f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 w-100' +
|
||||
' db focus-b--black focus-b--white-d'
|
||||
}
|
||||
placeholder={placeholder}
|
||||
disabled={searchDisabled}
|
||||
rows={1}
|
||||
spellCheck={false}
|
||||
style={{
|
||||
resize: 'none',
|
||||
paddingLeft: 36,
|
||||
}}
|
||||
onChange={this.search}
|
||||
value={state.searchValue}
|
||||
/>
|
||||
{searchResults}
|
||||
{participants}
|
||||
{invErrElem}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteSearch;
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import { LocalUpdateRemoteContentPolicy } from "~/types/local-update";
|
||||
import { Button } from '@tlon/indigo-react';
|
||||
import { BaseAnchor, BaseImage, Box, Button } from '@tlon/indigo-react';
|
||||
import { hasProvider } from 'oembed-parser';
|
||||
import EmbedContainer from 'react-oembed-container';
|
||||
import { memoize } from 'lodash';
|
||||
@ -70,15 +70,15 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
||||
}
|
||||
|
||||
wrapInLink(contents) {
|
||||
return (<a
|
||||
return (<BaseAnchor
|
||||
href={this.props.url}
|
||||
style={{ color: 'inherit' }}
|
||||
style={{ color: 'inherit', textDecoration: 'none' }}
|
||||
className={`word-break-all ${(typeof contents === 'string') ? 'bb' : ''}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{contents}
|
||||
</a>);
|
||||
</BaseAnchor>);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -102,7 +102,7 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
||||
|
||||
if (isImage && remoteContentPolicy.imageShown) {
|
||||
return this.wrapInLink(
|
||||
<img
|
||||
<BaseImage
|
||||
src={url}
|
||||
style={style}
|
||||
onLoad={onLoad}
|
||||
@ -157,8 +157,11 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
||||
>
|
||||
{this.state.unfold ? 'collapse' : 'expand'}
|
||||
</Button> : null}
|
||||
<div
|
||||
className={'embed-container mb2 w-100 w-75-l w-50-xl ' + (this.state.unfold ? 'db' : 'dn')}
|
||||
<Box
|
||||
mb='2'
|
||||
width='100%'
|
||||
display={this.state.unfold ? 'block' : 'none'}
|
||||
className='embed-container'
|
||||
style={style}
|
||||
onLoad={onLoad}
|
||||
{...oembedProps}
|
||||
@ -169,7 +172,7 @@ export default class RemoteContent extends PureComponent<RemoteContentProps, Rem
|
||||
<div dangerouslySetInnerHTML={{__html: this.state.embed.html}}></div>
|
||||
</EmbedContainer>
|
||||
: null}
|
||||
</div>
|
||||
</Box>
|
||||
</Fragment>
|
||||
);
|
||||
} else {
|
||||
|
@ -1,39 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class SidebarSwitcher extends Component {
|
||||
render() {
|
||||
|
||||
const classes = this.props.classes ? this.props.classes : '';
|
||||
|
||||
const style = this.props.style || {};
|
||||
|
||||
const paddingTop = this.props.classes ? '0px' : '8px';
|
||||
|
||||
return (
|
||||
<div className={classes} style={{ paddingTop: paddingTop, ...style }}>
|
||||
<a
|
||||
className='pointer flex-shrink-0'
|
||||
onClick={() => {
|
||||
this.props.api.local.sidebarToggle();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className='pr3 dn dib-m dib-l dib-xl'
|
||||
src={
|
||||
this.props.sidebarShown
|
||||
? '/~landscape/img/ChatSwitcherLink.png'
|
||||
: '/~landscape/img/ChatSwitcherClosed.png'
|
||||
}
|
||||
height='16'
|
||||
width='16'
|
||||
style={{
|
||||
maxWidth: 16
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SidebarSwitcher;
|
@ -1,6 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import _ from 'lodash';
|
||||
import normalizeWheel from 'normalize-wheel';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
interface VirtualScrollerProps {
|
||||
origin: 'top' | 'bottom';
|
||||
@ -216,7 +217,7 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
if (map.has(event.code) && document.body.isSameNode(document.activeElement)) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
let distance = map.get(event.code);
|
||||
let distance = map.get(event.code);
|
||||
if (event.code === 'Space' && event.shiftKey) {
|
||||
distance = distance * -1;
|
||||
}
|
||||
@ -332,13 +333,13 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={this.setWindow.bind(this)} onScroll={this.onScroll.bind(this)} style={{...style, ...{overflowY: 'scroll', transform }}}>
|
||||
<div ref={this.scrollContainer} style={{ transform }}>
|
||||
<div style={{ height: `${origin === 'top' ? startgap : endgap}px` }}></div>
|
||||
<Box overflowY='scroll' ref={this.setWindow.bind(this)} onScroll={this.onScroll.bind(this)} style={{ ...style, ...{ transform } }}>
|
||||
<Box ref={this.scrollContainer} style={{ transform }}>
|
||||
<Box style={{ height: `${origin === 'top' ? startgap : endgap}px` }}></Box>
|
||||
{indexesToRender.map(render)}
|
||||
<div style={{ height: `${origin === 'top' ? endgap : startgap}px` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
<Box style={{ height: `${origin === 'top' ? endgap : startgap}px` }}></Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,85 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Box, Text, Row, BaseInput } from '@tlon/indigo-react';
|
||||
|
||||
export class MetadataColor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
color: props.initialValue
|
||||
};
|
||||
|
||||
this.changeColor = this.changeColor.bind(this);
|
||||
this.submitColor = this.submitColor.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props } = this;
|
||||
if (prevProps.initialValue !== props.initialValue) {
|
||||
this.setState({ color: props.initialValue });
|
||||
}
|
||||
}
|
||||
|
||||
changeColor(event) {
|
||||
this.setState({ color: event.target.value });
|
||||
}
|
||||
|
||||
submitColor() {
|
||||
const { props, state } = this;
|
||||
|
||||
let color = state.color;
|
||||
if (color.startsWith('#')) {
|
||||
color = state.color.substr(1);
|
||||
}
|
||||
const hexExp = /([0-9A-Fa-f]{6})/;
|
||||
const hexTest = hexExp.exec(color);
|
||||
if (!props.isDisabled && hexTest && (state.color !== props.initialValue)) {
|
||||
props.setValue(color);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
return (
|
||||
<Box
|
||||
width='100%'
|
||||
mb='3'
|
||||
opacity={(props.isDisabled) ? '0.3' : '1'}
|
||||
>
|
||||
<Text my='1' display='block' fontSize='1'>Change color</Text>
|
||||
<Text fontSize='0' gray display='block' mb='3'>Give this {props.resource} a color when viewing group channels</Text>
|
||||
<Row
|
||||
position='relative'
|
||||
maxWidth='10rem'
|
||||
width='100%'
|
||||
>
|
||||
<Box
|
||||
position='absolute'
|
||||
height='16px'
|
||||
width='16px'
|
||||
backgroundColor={state.color}
|
||||
style={{ top: 18, left: 11 }}
|
||||
/>
|
||||
<BaseInput
|
||||
pl='5'
|
||||
fontSize='1'
|
||||
border='1px solid'
|
||||
borderColor='gray'
|
||||
backgroundColor='white'
|
||||
pt='3'
|
||||
pb='3'
|
||||
pr='3'
|
||||
display='block'
|
||||
width='100%'
|
||||
flex='auto'
|
||||
color='black'
|
||||
mr='3'
|
||||
value={state.color}
|
||||
disabled={props.isDisabled}
|
||||
onChange={this.changeColor}
|
||||
onBlur={this.submitColor}
|
||||
/>
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Box, Text, Row, BaseInput } from '@tlon/indigo-react';
|
||||
|
||||
export class MetadataInput extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: props.initialValue
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props } = this;
|
||||
if (prevProps.initialValue !== props.initialValue) {
|
||||
this.setState({ value: props.initialValue });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
isDisabled,
|
||||
setValue
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Box
|
||||
width='100%'
|
||||
mb='3'
|
||||
opacity={(isDisabled) ? '0.3' : '1'}
|
||||
>
|
||||
<Text display='block' fontSize='1' mb='1'>{title}</Text>
|
||||
<Text display='block' mb='4' fontSize='0' gray>{description}</Text>
|
||||
<Row
|
||||
width='100%'
|
||||
position='relative'
|
||||
maxWidth='29rem'
|
||||
>
|
||||
<BaseInput
|
||||
fontSize='1'
|
||||
border='1px solid'
|
||||
borderColor='gray'
|
||||
backgroundColor='white'
|
||||
p='3'
|
||||
display='block'
|
||||
color='black'
|
||||
width='100'
|
||||
flex='auto'
|
||||
mr='3'
|
||||
type="text"
|
||||
value={this.state.value}
|
||||
disabled={isDisabled}
|
||||
onChange={(e) => {
|
||||
this.setState({ value: e.target.value });
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!isDisabled) {
|
||||
setValue(this.state.value || '');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MetadataColor } from './color';
|
||||
import { MetadataInput } from './input';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
|
||||
|
||||
export const MetadataSettings = (props) => {
|
||||
const {
|
||||
isOwner,
|
||||
association,
|
||||
changeLoading,
|
||||
api,
|
||||
resource,
|
||||
app,
|
||||
module
|
||||
} = props;
|
||||
|
||||
const title =
|
||||
(props.association && 'metadata' in props.association) ?
|
||||
association.metadata.title : '';
|
||||
const description =
|
||||
(props.association && 'metadata' in props.association) ?
|
||||
association.metadata.description : '';
|
||||
const color =
|
||||
(props.association && 'metadata' in props.association) ?
|
||||
`#${uxToHex(props.association.metadata.color)}` : '';
|
||||
|
||||
return (
|
||||
<Box mt='6'>
|
||||
<MetadataInput
|
||||
title='Rename'
|
||||
description={`Change the name of this ${resource}`}
|
||||
isDisabled={!isOwner}
|
||||
initialValue={title}
|
||||
setValue={(val) => {
|
||||
changeLoading(false, true, `Editing ${resource}...`, () => {
|
||||
api.metadata.metadataAdd(
|
||||
app,
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
val,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
uxToHex(association.metadata.color),
|
||||
module
|
||||
).then(() => {
|
||||
changeLoading(false, false, '', () => {});
|
||||
});
|
||||
});
|
||||
}} />
|
||||
<MetadataInput
|
||||
title='Change description'
|
||||
description={`Change the description of this ${resource}`}
|
||||
isDisabled={!isOwner}
|
||||
initialValue={description}
|
||||
setValue={(val) => {
|
||||
changeLoading(false, true, `Editing ${resource}...`, () => {
|
||||
api.metadata.metadataAdd(
|
||||
app,
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
val,
|
||||
association.metadata['date-created'],
|
||||
uxToHex(association.metadata.color),
|
||||
module
|
||||
).then(() => {
|
||||
changeLoading(false, false, '', () => {});
|
||||
});
|
||||
});
|
||||
}} />
|
||||
<MetadataColor
|
||||
initialValue={color}
|
||||
isDisabled={!isOwner}
|
||||
resource={resource}
|
||||
setValue={(val) => {
|
||||
changeLoading(false, true, `Editing ${resource}...`, () => {
|
||||
props.api.metadata.metadataAdd(
|
||||
app,
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
val,
|
||||
module
|
||||
).then(() => {
|
||||
changeLoading(false, false, '', () => {});
|
||||
});
|
||||
});
|
||||
}} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Box, Text, Icon } from "@tlon/indigo-react";
|
||||
import { BaseInput, Box, Text, Icon } from "@tlon/indigo-react";
|
||||
|
||||
import S3Client from '~/logic/lib/s3';
|
||||
import { Spinner } from './Spinner';
|
||||
@ -117,7 +117,6 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
|
||||
this.setState({ isUploading: false });
|
||||
});
|
||||
}, 200);
|
||||
|
||||
}
|
||||
|
||||
onClick() {
|
||||
@ -141,8 +140,8 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
|
||||
const display = children || <Icon icon='ArrowNorth' />;
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
className="dn"
|
||||
<BaseInput
|
||||
display='none'
|
||||
type="file"
|
||||
id="fileElement"
|
||||
ref={this.inputRef}
|
||||
@ -150,7 +149,7 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
|
||||
onChange={this.onChange.bind(this)} />
|
||||
{this.state.isUploading
|
||||
? <Spinner awaiting={true} classes={className} />
|
||||
: <span className={`pointer ${className}`} onClick={this.onClick.bind(this)}>{display}</span>
|
||||
: <Text cursor='pointer' className={className} onClick={this.onClick.bind(this)}>{display}</Text>
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
@ -1,20 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Toggle extends Component {
|
||||
render() {
|
||||
const switchClasses = (this.props.boolean)
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
style={{ WebkitAppearance: 'none', width: 28 }}
|
||||
className={switchClasses}
|
||||
onChange={this.props.change}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Toggle;
|
Loading…
Reference in New Issue
Block a user