mirror of
https://github.com/urbit/shrub.git
synced 2024-12-01 06:35:32 +03:00
interface: removed unused links files
This commit is contained in:
parent
77b1db2d16
commit
97e1fa12a9
@ -232,29 +232,6 @@ export function stringToTa(string) {
|
||||
return '~.' + out;
|
||||
}
|
||||
|
||||
// used in Links
|
||||
|
||||
export function makeRoutePath(
|
||||
resource,
|
||||
page = 0,
|
||||
url = null,
|
||||
index = 0,
|
||||
compage = 0
|
||||
) {
|
||||
let route = '/~link' + resource;
|
||||
if (!url) {
|
||||
if (page !== 0) {
|
||||
route = route + '/' + page;
|
||||
}
|
||||
} else {
|
||||
route = `${route}/${page}/${index}/${base64urlEncode(url)}`;
|
||||
if (compage !== 0) {
|
||||
route = route + '/' + compage;
|
||||
}
|
||||
}
|
||||
return route;
|
||||
}
|
||||
|
||||
export function amOwnerOfGroup(groupPath) {
|
||||
if (!groupPath)
|
||||
return false;
|
||||
|
@ -7,14 +7,13 @@ import { StoreState } from "~/logic/store/type";
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { Association, GraphNode } from "~/types";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { LinkList } from "./components/link-list";
|
||||
import { LinkDetail } from "./components/link-detail";
|
||||
|
||||
import { LinkItem } from "./components/lib/link-item";
|
||||
import { LinkSubmit } from "./components/lib/link-submit";
|
||||
import { LinkPreview } from "./components/lib/link-preview";
|
||||
import { CommentSubmit } from "./components/lib/comment-submit";
|
||||
import { Comments } from "./components/lib/comments";
|
||||
import { LinkItem } from "./components/link-item";
|
||||
import { LinkSubmit } from "./components/link-submit";
|
||||
import { LinkPreview } from "./components/link-preview";
|
||||
import { CommentSubmit } from "./components/comment-submit";
|
||||
import { Comments } from "./components/comments";
|
||||
|
||||
import "./css/custom.css";
|
||||
|
||||
type LinkResourceProps = StoreState & {
|
||||
|
@ -1,227 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import './css/custom.css';
|
||||
|
||||
import { Skeleton } from './components/skeleton';
|
||||
import { NewScreen } from './components/new';
|
||||
import { SettingsScreen } from './components/settings';
|
||||
import { MessageScreen } from './components/lib/message-screen';
|
||||
import { LinkList } from './components/link-list';
|
||||
import { LinkDetail } from './components/link-detail';
|
||||
|
||||
import {
|
||||
amOwnerOfGroup,
|
||||
base64urlDecode
|
||||
} from '~/logic/lib/util';
|
||||
|
||||
|
||||
export default class LinksApp extends Component {
|
||||
componentDidMount() {
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
|
||||
this.props.subscription.startApp('graph');
|
||||
if (!this.props.sidebarShown) {
|
||||
this.props.api.local.sidebarToggle();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.subscription.stopApp('graph');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const contacts = props.contacts ? props.contacts : {};
|
||||
const groups = props.groups ? props.groups : {};
|
||||
const associations =
|
||||
props.associations ? props.associations : { graph: {}, contacts: {} };
|
||||
const graphKeys = props.graphKeys || new Set([]);
|
||||
const graphs = props.graphs || {};
|
||||
|
||||
const invites = props.invites ?
|
||||
props.invites : {};
|
||||
|
||||
const {
|
||||
api, sidebarShown, s3,
|
||||
hideAvatars, hideNicknames, remoteContentPolicy
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet defer={false}>
|
||||
<title>OS1 - Links</title>
|
||||
</Helmet>
|
||||
<Switch>
|
||||
<Route exact path="/~link"
|
||||
render={ (props) => (
|
||||
<Skeleton
|
||||
active="collections"
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
rightPanelHide={true}
|
||||
sidebarShown={sidebarShown}
|
||||
api={api}
|
||||
graphKeys={graphKeys}>
|
||||
<MessageScreen text="Select or create a collection to begin." />
|
||||
</Skeleton>
|
||||
)}
|
||||
/>
|
||||
<Route exact path="/~link/new"
|
||||
render={ (props) => (
|
||||
<Skeleton
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
sidebarShown={sidebarShown}
|
||||
api={api}
|
||||
graphKeys={graphKeys}>
|
||||
<NewScreen
|
||||
api={api}
|
||||
graphKeys={graphKeys}
|
||||
associations={associations}
|
||||
groups={groups}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
)}
|
||||
/>
|
||||
<Route exact path="/~link/:ship/:name/settings"
|
||||
render={ (props) => {
|
||||
const resourcePath =
|
||||
`${props.match.params.ship}/${props.match.params.name}`;
|
||||
const metPath = `/ship/~${resourcePath}`;
|
||||
const resource =
|
||||
associations.graph[metPath] ?
|
||||
associations.graph[metPath] : { metadata: {} };
|
||||
|
||||
const contactDetails = contacts[resource['group-path']] || {};
|
||||
const group = groups[resource['group-path']] || new Set([]);
|
||||
const amOwner = amOwnerOfGroup(resource['group-path']);
|
||||
const hasGraph = !!graphs[resourcePath];
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
selected={resourcePath}
|
||||
sidebarShown={sidebarShown}
|
||||
graphKeys={graphKeys}
|
||||
api={api}>
|
||||
<SettingsScreen
|
||||
sidebarShown={sidebarShown}
|
||||
resource={resource}
|
||||
contacts={contacts}
|
||||
contactDetails={contactDetails}
|
||||
graphResource={graphKeys.has(resourcePath)}
|
||||
hasGraph={!!hasGraph}
|
||||
group={group}
|
||||
amOwner={amOwner}
|
||||
resourcePath={resourcePath}
|
||||
api={api}
|
||||
{...props} />
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~link/:ship/:name"
|
||||
render={ (props) => {
|
||||
const resourcePath =
|
||||
`${props.match.params.ship}/${props.match.params.name}`;
|
||||
const metPath = `/ship/~${resourcePath}`;
|
||||
const resource =
|
||||
associations.graph[metPath] ?
|
||||
associations.graph[metPath] : { metadata: {} };
|
||||
|
||||
const contactDetails = contacts[resource['group-path']] || {};
|
||||
const graph = graphs[resourcePath] || null;
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
selected={resourcePath}
|
||||
sidebarShown={sidebarShown}
|
||||
sidebarHideMobile={true}
|
||||
api={api}
|
||||
graphKeys={graphKeys}>
|
||||
<LinkList
|
||||
{...props}
|
||||
api={api}
|
||||
s3={s3}
|
||||
graph={graph}
|
||||
graphResource={graphKeys.has(resourcePath)}
|
||||
resourcePath={resourcePath}
|
||||
metadata={resource.metadata}
|
||||
contacts={contactDetails}
|
||||
hideAvatars={hideAvatars}
|
||||
hideNicknames={hideNicknames}
|
||||
sidebarShown={sidebarShown}
|
||||
ship={props.match.params.ship}
|
||||
name={props.match.params.name}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~link/:ship/:name/:index"
|
||||
render={ (props) => {
|
||||
const resourcePath =
|
||||
`${props.match.params.ship}/${props.match.params.name}`;
|
||||
const metPath = `/ship/~${resourcePath}`;
|
||||
const resource =
|
||||
associations.graph[metPath] ?
|
||||
associations.graph[metPath] : { metadata: {} };
|
||||
|
||||
const contactDetails = contacts[resource['group-path']] || {};
|
||||
|
||||
const indexArr = props.match.params.index.split('-');
|
||||
const graph = graphs[resourcePath] || null;
|
||||
|
||||
if (indexArr.length <= 1) {
|
||||
return <div>Malformed URL</div>;
|
||||
}
|
||||
|
||||
const index = parseInt(indexArr[1], 10);
|
||||
const node = Boolean(graph) ? graph.get(index) : null;
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
selected={resourcePath}
|
||||
sidebarShown={sidebarShown}
|
||||
sidebarHideMobile={true}
|
||||
graphKeys={graphKeys}
|
||||
api={api}>
|
||||
<LinkDetail
|
||||
{...props}
|
||||
node={node}
|
||||
graphResource={graphKeys.has(resourcePath)}
|
||||
ship={props.match.params.ship}
|
||||
name={props.match.params.name}
|
||||
resource={resource}
|
||||
contacts={contactDetails}
|
||||
sidebarShown={sidebarShown}
|
||||
api={api}
|
||||
hideAvatars={hideAvatars}
|
||||
hideNicknames={hideNicknames}
|
||||
remoteContentPolicy={remoteContentPolicy} />
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
export class ChannelItem extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const selectedClass = (props.selected)
|
||||
? 'bg-gray5 bg-gray1-d'
|
||||
: 'pointer hover-bg-gray5 hover-bg-gray1-d';
|
||||
|
||||
const unseenCount = props.unseenCount > 0
|
||||
? <span className="dib white bg-gray3 bg-gray2-d fw6 br1 absolute" style={{ padding: '1px 5px', right: 8 }}>{props.unseenCount}</span>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Link to={`/~link/${props.link}`}>
|
||||
<div className={'w-100 v-mid f9 ph5 z1 pv1 relative ' + selectedClass}>
|
||||
<p className="f9 dib">{props.name}</p>
|
||||
<p className="f9 dib fr">
|
||||
{unseenCount}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,109 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { GroupItem } from './group-item';
|
||||
import SidebarInvite from '~/views/components/Sidebar/SidebarInvite';
|
||||
import { Welcome } from './welcome';
|
||||
import { alphabetiseAssociations } from '~/logic/lib/util';
|
||||
|
||||
export const ChannelSidebar = (props) => {
|
||||
const sidebarInvites = Object.keys(props.invites)
|
||||
.map((uid) => {
|
||||
return (
|
||||
<SidebarInvite
|
||||
key={uid}
|
||||
invite={props.invites[uid]}
|
||||
onAccept={() => props.api.invite.accept('/link', uid)}
|
||||
onDecline={() => props.api.invite.decline('/link', uid)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const associations = props.associations.contacts ?
|
||||
alphabetiseAssociations(props.associations.contacts) : {};
|
||||
|
||||
const graphAssoc = props.associations.graph || {};
|
||||
|
||||
const groupedChannels = {};
|
||||
[...props.graphKeys].map((gKey) => {
|
||||
const path = `/ship/~${gKey.split('/')[0]}/${gKey.split('/')[1]}`;
|
||||
const groupPath = graphAssoc[path] ? graphAssoc[path]['group-path'] : '';
|
||||
|
||||
if (groupPath in associations) {
|
||||
// managed
|
||||
|
||||
if (groupedChannels[groupPath]) {
|
||||
const array = groupedChannels[groupPath];
|
||||
array.push(path);
|
||||
groupedChannels[groupPath] = array;
|
||||
} else {
|
||||
groupedChannels[groupPath] = [path];
|
||||
}
|
||||
|
||||
} else {
|
||||
// unmanaged
|
||||
|
||||
if (groupedChannels['/~/']) {
|
||||
const array = groupedChannels['/~/'];
|
||||
array.push(path);
|
||||
groupedChannels['/~/'] = array;
|
||||
} else {
|
||||
groupedChannels['/~/'] = [path];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const groupedItems = Object.keys(associations).map((each, i) => {
|
||||
const channels = groupedChannels[each];
|
||||
if (!channels || channels.length === 0) { return; }
|
||||
|
||||
return (
|
||||
<GroupItem
|
||||
key={i + 1}
|
||||
unmanaged={false}
|
||||
association={associations[each]}
|
||||
metadata={graphAssoc}
|
||||
channels={channels}
|
||||
selected={props.selected}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
if (groupedChannels['/~/'] && groupedChannels['/~/'].length !== 0) {
|
||||
groupedItems.push(
|
||||
<GroupItem
|
||||
key={0}
|
||||
unmanaged={true}
|
||||
association={'/~/'}
|
||||
metadata={graphAssoc}
|
||||
channels={groupedChannels['/~/']}
|
||||
selected={props.selected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const activeClasses = (props.active === 'collections') ? ' ' : 'dn-s ';
|
||||
|
||||
return (
|
||||
<div className={
|
||||
`bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100` +
|
||||
`flex-shrink-0 mw5-m mw5-l mw5-xl pt3 pt0-m pt0-l pt0-xl relative ` +
|
||||
activeClasses +
|
||||
((props.sidebarShown) ? 'flex-basis-100-s flex-basis-30-ns' : 'dn')
|
||||
}>
|
||||
<div className="overflow-y-scroll h-100">
|
||||
<div className="w-100 bg-transparent">
|
||||
<Link
|
||||
className="dib f9 pointer green2 gray4-d pa4"
|
||||
to={'/~link/new'}>
|
||||
New Collection
|
||||
</Link>
|
||||
</div>
|
||||
<Welcome associations={props.associations} />
|
||||
{sidebarInvites}
|
||||
{groupedItems}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,43 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { ChannelItem } from './channel-item';
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
|
||||
|
||||
export const GroupItem = (props) => {
|
||||
const association = props.association ? props.association : {};
|
||||
|
||||
let title =
|
||||
association['app-path'] ? association['app-path'] : 'Unmanaged Collections';
|
||||
|
||||
if (association.metadata && association.metadata.title) {
|
||||
title = association.metadata.title !== ''
|
||||
? association.metadata.title : title;
|
||||
}
|
||||
|
||||
const channels = props.channels ? props.channels : [];
|
||||
const unmanaged = props.unmanaged ? 'pt6' : 'pt1';
|
||||
|
||||
const channelItems = channels.map((each, i) => {
|
||||
const meta = props.metadata[each];
|
||||
if (!meta) { return null; }
|
||||
const link = `${deSig(each.split('/')[2])}/${each.split('/')[3]}`;
|
||||
|
||||
const selected = (props.selected === each);
|
||||
return (
|
||||
<ChannelItem
|
||||
key={each}
|
||||
link={link}
|
||||
selected={selected}
|
||||
name={meta.metadata.title}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={unmanaged}>
|
||||
<p className="f9 ph4 pb2 gray3">{title}</p>
|
||||
{channelItems}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,55 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { uxToHex, cite } from '~/logic/lib/util';
|
||||
export class MemberElement extends Component {
|
||||
onRemove() {
|
||||
const { props } = this;
|
||||
props.api.groups.remove(props.groupPath, [`~${props.ship}`]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
let actionElem;
|
||||
if (props.ship === props.owner) {
|
||||
actionElem = (
|
||||
<p className="w-20 dib list-ship black white-d f8 c-default">
|
||||
Host
|
||||
</p>
|
||||
);
|
||||
} else if (props.amOwner && window.ship !== props.ship) {
|
||||
actionElem = (
|
||||
<a onClick={this.onRemove.bind(this)}
|
||||
className="w-20 dib list-ship black white-d f8 pointer"
|
||||
>
|
||||
Ban
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
actionElem = (
|
||||
<span></span>
|
||||
);
|
||||
}
|
||||
|
||||
const name = props.contact
|
||||
? `${props.contact.nickname} (${cite(props.ship)})`
|
||||
: `${cite(props.ship)}`;
|
||||
const color = props.contact ? uxToHex(props.contact.color) : '000000';
|
||||
|
||||
const img = props.contact.avatar
|
||||
? <img src={props.contact.avatar} height={32} width={32} className="dib" />
|
||||
: <Sigil ship={props.ship} size={32} color={`#${color}`} />;
|
||||
|
||||
return (
|
||||
<div className="flex mb2">
|
||||
{img}
|
||||
<p className={'w-70 mono list-ship dib v-mid black white-d ml2 nowrap f8'}
|
||||
title={props.ship}
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
{actionElem}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class MessageScreen extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column bg-white bg-gray0-d dn db-ns">
|
||||
<div className="pl3 pr3 pt2 dt pb3 w-100 h-100">
|
||||
<p className="f8 pt3 gray2 w-100 h-100 dtc v-mid tc">
|
||||
{this.props.text}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { makeRoutePath } from '~/logic/lib/util';
|
||||
|
||||
export class Pagination extends Component {
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
const prevPage = (Number(props.page) - 1);
|
||||
const nextPage = (Number(props.page) + 1);
|
||||
|
||||
const prevDisplay = ((props.currentPage > 0))
|
||||
? 'dib absolute left-0'
|
||||
: 'dn';
|
||||
|
||||
const nextDisplay = ((props.currentPage + 1) < props.totalPages)
|
||||
? 'dib absolute right-0'
|
||||
: 'dn';
|
||||
|
||||
return (
|
||||
<div className="w-100 inter relative pv6">
|
||||
<div className={prevDisplay + ' inter f8'}>
|
||||
<Link to={makeRoutePath(props.resourcePath, prevPage)}>
|
||||
<- Previous Page
|
||||
</Link>
|
||||
</div>
|
||||
<div className={nextDisplay + ' inter f8'}>
|
||||
<Link to={makeRoutePath(props.resourcePath, nextPage)}>
|
||||
Next Page ->
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Pagination;
|
@ -1,41 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Welcome extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
show: true
|
||||
};
|
||||
this.disableWelcome = this.disableWelcome.bind(this);
|
||||
}
|
||||
|
||||
disableWelcome() {
|
||||
this.setState({ show: false });
|
||||
localStorage.setItem('urbit-link:wasWelcomed', JSON.stringify(true));
|
||||
}
|
||||
|
||||
render() {
|
||||
let wasWelcomed = localStorage.getItem('urbit-link:wasWelcomed');
|
||||
if (wasWelcomed === null) {
|
||||
localStorage.setItem('urbit-link:wasWelcomed', JSON.stringify(false));
|
||||
return wasWelcomed = false;
|
||||
} else {
|
||||
wasWelcomed = JSON.parse(wasWelcomed);
|
||||
}
|
||||
|
||||
const associations = this.props.associations ? this.props.associations : {};
|
||||
|
||||
return ((!wasWelcomed && this.state.show) && (associations.length !== 0)) ? (
|
||||
<div className="ma4 pa2 bg-welcome-green bg-gray1-d white-d">
|
||||
<p className="f8 lh-copy">Links are for collecting and discussing outside content. Each post is a URL and a comment thread.</p>
|
||||
<p className="f8 pt2 dib bb pointer"
|
||||
onClick={(() => this.disableWelcome())}
|
||||
>
|
||||
Close this
|
||||
</p>
|
||||
</div>
|
||||
) : <div />;
|
||||
}
|
||||
}
|
||||
|
||||
export default Welcome;
|
@ -1,100 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { TabBar } from '~/views/components/chat-link-tabbar';
|
||||
import { LinkPreview } from './lib/link-preview';
|
||||
import { CommentSubmit } from './lib/comment-submit';
|
||||
import { SidebarSwitcher } from '~/views/components/SidebarSwitch';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Comments } from './lib/comments';
|
||||
import { getContactDetails } from '~/logic/lib/util';
|
||||
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
export const LinkDetail = (props) => {
|
||||
if (!props.node && props.graphResource) {
|
||||
useEffect(() => {
|
||||
props.api.graph.getGraph(
|
||||
`~${props.match.params.ship}`,
|
||||
props.match.params.name
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>Loading...</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!props.node) {
|
||||
return (
|
||||
<div>Not found</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { nickname } = getContactDetails(props.contacts[props.node?.post?.author]);
|
||||
const resourcePath = `${props.ship}/${props.name}`;
|
||||
const title = props.resource.metadata.title || resourcePath;
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-hidden flex flex-column">
|
||||
<Box
|
||||
pl='12px'
|
||||
pt='2'
|
||||
display='flex'
|
||||
position='relative'
|
||||
overflowX={['scroll', 'auto']}
|
||||
flexShrink='0'
|
||||
borderBottom='1px solid'
|
||||
borderColor='washedGray'
|
||||
height='48px'
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
api={props.api}
|
||||
/>
|
||||
<Link className="dib f9 fw4 pt2 gray2 lh-solid"
|
||||
to={`/~link/${resourcePath}`}
|
||||
>
|
||||
<h2
|
||||
className="dib f9 fw4 lh-solid v-top black white-d"
|
||||
style={{ width: 'max-content' }}
|
||||
>
|
||||
{`${title}`}
|
||||
</h2>
|
||||
</Link>
|
||||
<TabBar
|
||||
location={props.location}
|
||||
settings={`/~link/${resourcePath}/settings`}
|
||||
/>
|
||||
</Box>
|
||||
<div className="w-100 mt2 flex justify-center overflow-y-scroll ph4 pb4">
|
||||
<div className="w-100 mw7">
|
||||
<LinkPreview
|
||||
resourcePath={resourcePath}
|
||||
post={props.node.post}
|
||||
nickname={nickname}
|
||||
hideNicknames={props.hideNicknames}
|
||||
commentNumber={props.node.children.size}
|
||||
remoteContentPolicy={props.remoteContentPolicy}
|
||||
/>
|
||||
<div className="flex">
|
||||
<CommentSubmit
|
||||
name={props.name}
|
||||
ship={props.ship}
|
||||
api={props.api}
|
||||
parentIndex={props.node.post.index}
|
||||
/>
|
||||
</div>
|
||||
<Comments
|
||||
comments={props.node.children}
|
||||
resourcePath={resourcePath}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
hideAvatars={props.hideAvatars}
|
||||
hideNicknames={props.hideNicknames}
|
||||
remoteContentPolicy={props.remoteContentPolicy}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -33,7 +33,7 @@ export const LinkItem = (props) => {
|
||||
? <img src={props.avatar} height={36} width={36} className="dib" />
|
||||
: <Sigil ship={`~${author}`} size={36} color={'#' + props.color} />;
|
||||
|
||||
const baseUrl = props.baseUrl || `/~link/${resource}`;
|
||||
const baseUrl = props.baseUrl || `/~404/${resource}`;
|
||||
|
||||
return (
|
||||
<Row minWidth='0' flexShrink='0' width="100%" alignItems="center" py={3} bg="white">
|
@ -1,91 +0,0 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { TabBar } from '~/views/components/chat-link-tabbar';
|
||||
import { SidebarSwitcher } from '~/views/components/SidebarSwitch';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LinkItem } from './lib/link-item';
|
||||
import LinkSubmit from './lib/link-submit';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
import { getContactDetails } from "~/logic/lib/util";
|
||||
|
||||
export const LinkList = (props) => {
|
||||
const resource = `${props.ship}/${props.name}`;
|
||||
const title = props.metadata.title || resource;
|
||||
useEffect(() => {
|
||||
props.api.graph.getGraph(
|
||||
`~${props.match.params.ship}`,
|
||||
props.match.params.name
|
||||
);
|
||||
}, [props.match.params.ship, props.match.params.name]);
|
||||
|
||||
if (!props.graph && props.graphResource) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!props.graph) {
|
||||
return <div>Not found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-hidden flex flex-column">
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: "1rem" }}
|
||||
>
|
||||
<Link to="/~link">{"⟵ All Channels"}</Link>
|
||||
</div>
|
||||
<Box
|
||||
pl='12px'
|
||||
pt='2'
|
||||
display='flex'
|
||||
position='relative'
|
||||
overflowX={['scroll', 'auto']}
|
||||
flexShrink='0'
|
||||
borderBottom='1px solid'
|
||||
borderColor='washedGray'
|
||||
height='48px'>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
api={props.api} />
|
||||
<h2
|
||||
className="dib f9 fw4 pt2 lh-solid v-top black white-d"
|
||||
style={{ width: 'max-content' }}>
|
||||
{title}
|
||||
</h2>
|
||||
<TabBar
|
||||
location={props.location}
|
||||
settings={`/~link/${resource}/settings`}
|
||||
/>
|
||||
</Box>
|
||||
<div className="w-100 mt6 flex justify-center overflow-y-scroll ph4 pb4">
|
||||
<div className="w-100 mw7">
|
||||
<div className="flex">
|
||||
<LinkSubmit
|
||||
name={props.name}
|
||||
ship={props.ship}
|
||||
api={props.api}
|
||||
s3={props.s3} />
|
||||
</div>
|
||||
{ Array.from(props.graph).map(([date, node]) => {
|
||||
const { nickname, color, avatar } =
|
||||
getContactDetails(props.contacts[node?.post?.author]);
|
||||
return (
|
||||
<LinkItem
|
||||
key={date}
|
||||
resource={resource}
|
||||
node={node}
|
||||
nickname={nickname}
|
||||
color={color}
|
||||
avatar={avatar}
|
||||
hideAvatars={props.hideAvatars}
|
||||
hideNicknames={props.hideNicknames}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { MessageScreen } from './lib/message-screen';
|
||||
|
||||
export class LoadingScreen extends Component {
|
||||
render() {
|
||||
return (<MessageScreen text="Loading..." />);
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { Box, ManagedTextInputField as Input, Col } from "@tlon/indigo-react";
|
||||
import { Formik, Form } from "formik";
|
||||
import * as Yup from "yup";
|
||||
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import { FormError } from "~/views/components/FormError";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { stringToSymbol } from "~/logic/lib/util";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
|
||||
import { Associations } from "~/types/metadata-update";
|
||||
import { Notebooks } from "~/types/publish-update";
|
||||
import { Groups, GroupPolicy } from "~/types/group-update";
|
||||
|
||||
const formSchema = Yup.object({
|
||||
name: Yup.string().required("Collection must have a name"),
|
||||
description: Yup.string(),
|
||||
group: Yup.string(),
|
||||
});
|
||||
|
||||
|
||||
export function NewScreen(props: object) {
|
||||
const { history, api } = props;
|
||||
const waiter = useWaitForProps(props, 5000);
|
||||
|
||||
const onSubmit = async (values: object, actions) => {
|
||||
const resourceId = stringToSymbol(values.name);
|
||||
try {
|
||||
const { name, description, group } = values;
|
||||
if (!!group) {
|
||||
await props.api.graph.createManagedGraph(
|
||||
resourceId,
|
||||
name,
|
||||
description,
|
||||
group,
|
||||
"link"
|
||||
);
|
||||
} else {
|
||||
await props.api.graph.createUnmanagedGraph(
|
||||
resourceId,
|
||||
name,
|
||||
description,
|
||||
{ invite: { pending: [] } },
|
||||
"link"
|
||||
);
|
||||
}
|
||||
|
||||
await waiter((p) => p?.graphKeys?.has(`${window.ship}/${resourceId}`));
|
||||
actions.setStatus({ success: null });
|
||||
history.push(`/~link/${window.ship}/${resourceId}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: "Collection creation failed" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Col p={3}>
|
||||
<Box mb={4} color="black">New Collection</Box>
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={{ name: "", description: "", group: "" }}
|
||||
onSubmit={onSubmit}>
|
||||
<Form>
|
||||
<Box
|
||||
display="grid"
|
||||
gridTemplateRows="auto"
|
||||
gridRowGap={4}
|
||||
gridTemplateColumns="300px">
|
||||
<Input
|
||||
id="name"
|
||||
label="Name"
|
||||
caption="Provide a name for your collection"
|
||||
placeholder="eg. My Links"
|
||||
/>
|
||||
<Input
|
||||
id="description"
|
||||
label="Description"
|
||||
caption="What's your collection about?"
|
||||
placeholder="Collection description"
|
||||
/>
|
||||
<GroupSearch
|
||||
id="group"
|
||||
label="Group"
|
||||
caption="What group is the collection for?"
|
||||
associations={props.associations}
|
||||
/>
|
||||
<Box justifySelf="start">
|
||||
<AsyncButton loadingText="Creating..." type="submit" border>
|
||||
Create Collection
|
||||
</AsyncButton>
|
||||
</Box>
|
||||
<FormError message="Collection creation failed" />
|
||||
</Box>
|
||||
</Form>
|
||||
</Formik>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
export default NewScreen;
|
@ -1,184 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { LoadingScreen } from './loading';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { TabBar } from '~/views/components/chat-link-tabbar';
|
||||
import SidebarSwitcher from '~/views/components/SidebarSwitch';
|
||||
|
||||
import { MetadataSettings } from '~/views/components/metadata/settings';
|
||||
|
||||
import { Box, Text, Button, Col, Row } from '@tlon/indigo-react';
|
||||
|
||||
export class SettingsScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
awaiting: false,
|
||||
type: 'Editing'
|
||||
};
|
||||
|
||||
this.renderDelete = this.renderDelete.bind(this);
|
||||
this.changeLoading = this.changeLoading.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { props, state } = this;
|
||||
|
||||
if (Boolean(state.isLoading) && !props.resource) {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
}, () => {
|
||||
props.history.push('/~link');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeLoading(isLoading, awaiting, type, closure) {
|
||||
this.setState({
|
||||
isLoading,
|
||||
awaiting,
|
||||
type
|
||||
}, closure);
|
||||
}
|
||||
|
||||
removeCollection() {
|
||||
const { props } = this;
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
awaiting: true,
|
||||
type: 'Removing'
|
||||
});
|
||||
|
||||
props.api.graph.leaveGraph(
|
||||
`~${props.match.params.ship}`,
|
||||
props.match.params.name
|
||||
);
|
||||
}
|
||||
|
||||
deleteCollection() {
|
||||
const { props } = this;
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
awaiting: true,
|
||||
type: 'Deleting'
|
||||
});
|
||||
|
||||
props.api.graph.deleteGraph(props.match.params.name);
|
||||
}
|
||||
|
||||
renderRemove() {
|
||||
const { props } = this;
|
||||
|
||||
if (props.amOwner) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Box width='100%' mt='3'>
|
||||
<Text display='block' mt='3' fontSize='1' mb='1'>Remove Collection</Text>
|
||||
<Text display='block' fontSize='0' gray mb='4'>
|
||||
Remove this collection from your collection list
|
||||
</Text>
|
||||
<Button onClick={this.removeCollection.bind(this)}>
|
||||
Remove collection
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderDelete() {
|
||||
const { props } = this;
|
||||
|
||||
if (!props.amOwner) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Box width='100%' mt='3'>
|
||||
<Text fontSize='1' mt='3' display='block' mb='1'>Delete collection</Text>
|
||||
<Text fontSize='0' gray display='block' mb='4'>
|
||||
Delete this collection, for you and all group members
|
||||
</Text>
|
||||
<Button primary onClick={this.deleteCollection.bind(this)} destructive mb='4'>
|
||||
Delete collection
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const title = props.resource.metadata.title || props.resourcePath;
|
||||
|
||||
if (
|
||||
(!props.hasGraph || !props.resource.metadata.color)
|
||||
&& props.graphResource
|
||||
) {
|
||||
return <LoadingScreen />;
|
||||
} else if (!props.graphResource) {
|
||||
props.history.push('/~link');
|
||||
return <Box />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Col height='100%' width='100' overflowX='hidden'>
|
||||
<Box width='100%' display={['block', 'none']} pt='4' pb='6' pl='3' fontSize='1' height='1rem'>
|
||||
<Link to="/~link">{'⟵ All Collections'}</Link>
|
||||
</Box>
|
||||
<Row
|
||||
pl='12px'
|
||||
pt='2'
|
||||
borderBottom='1px solid'
|
||||
borderColor='washedGray'
|
||||
flexShrink='0'
|
||||
overflowX={['scroll', 'auto']}
|
||||
height='48px'
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={this.props.sidebarShown}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<Link className="dib f9 fw4 pt2 gray2 lh-solid"
|
||||
to={`/~link/${props.resourcePath}`}>
|
||||
<Text
|
||||
display='inline-block'
|
||||
fontSize='0'
|
||||
verticalAlign='top'
|
||||
width='max-content'>
|
||||
{title}
|
||||
</Text>
|
||||
</Link>
|
||||
<TabBar
|
||||
location={props.location}
|
||||
settings={`/~link/${props.resourcePath}/settings`}
|
||||
/>
|
||||
</Row>
|
||||
<Box width='100' pl='3' mt='3'>
|
||||
<Text display='block' fontSize='1' pb='2'>Collection Settings</Text>
|
||||
{this.renderRemove()}
|
||||
{this.renderDelete()}
|
||||
<MetadataSettings
|
||||
isOwner={props.amOwner}
|
||||
changeLoading={this.changeLoading}
|
||||
api={props.api}
|
||||
association={props.resource}
|
||||
resource="collection"
|
||||
app="graph"
|
||||
module="link"
|
||||
/>
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes="absolute right-1 bottom-1 pa2 ba b--black b--gray0-d white-d"
|
||||
text={this.state.type}
|
||||
/>
|
||||
</Box>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { ChannelSidebar } from './lib/channel-sidebar';
|
||||
import ErrorBoundary from '~/views/components/ErrorBoundary';
|
||||
|
||||
export class Skeleton extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
const rightPanelHide = props.rightPanelHide ? 'dn-s' : '';
|
||||
|
||||
const linkInvites = ('/link' in props.invites)
|
||||
? props.invites['/link'] : {};
|
||||
|
||||
return (
|
||||
<div className='absolute w-100 ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl'
|
||||
style={{ height: 'calc(100% - 45px)' }}>
|
||||
<div className='bg-white bg-gray0-d cf w-100 h-100 flex ba-m ba-l ba-xl b--gray4 b--gray1-d br1'>
|
||||
<ChannelSidebar
|
||||
active={props.active}
|
||||
associations={props.associations}
|
||||
invites={linkInvites}
|
||||
groups={props.groups}
|
||||
selected={props.selected}
|
||||
sidebarShown={props.sidebarShown}
|
||||
api={props.api}
|
||||
graphKeys={props.graphKeys} />
|
||||
<div className={'h-100 w-100 flex-auto relative ' + rightPanelHide}
|
||||
style={{ flexGrow: 1 }}>
|
||||
<ErrorBoundary>
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
export const TabBar = (props) => {
|
||||
const {
|
||||
location,
|
||||
settings,
|
||||
} = props;
|
||||
let setColor = '';
|
||||
|
||||
if (location.pathname.includes('/settings')) {
|
||||
setColor = 'black white-d';
|
||||
} else {
|
||||
setColor = 'gray3';
|
||||
}
|
||||
|
||||
return (
|
||||
<Box display='inline-block' flexShrink='0' flexGrow='1'>
|
||||
<Box display='inline-block' pt='9px' fontSize='0' pl='16px' pr='6'>
|
||||
<Link
|
||||
className={'no-underline ' + setColor}
|
||||
to={settings}>
|
||||
Settings
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user