mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 22:33:06 +03:00
Merge branch 'release/next-userspace' into lf/settings-screen
This commit is contained in:
commit
e89cb3bd71
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d7088528dbfd54a913921ade093251d678c4ccebfd0ad85ef2022520266b3954
|
||||
size 16451173
|
||||
oid sha256:ef417c3092dc32d6d5897a7ba63f3f8910f928f0aa23adf3a356b88ce027a415
|
||||
size 6260173
|
||||
|
@ -1,7 +1,7 @@
|
||||
/- glob
|
||||
/+ default-agent, verb, dbug
|
||||
|%
|
||||
++ hash 0v2.ssh1k.1s91p.s70g6.j21r5.hr9lc
|
||||
++ hash 0v7.foe2o.ang8k.28dnr.fudi0.74c8d
|
||||
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||
+$ all-states
|
||||
$% state-0
|
||||
|
@ -4,11 +4,11 @@
|
||||
<title>OS1</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-touch-fullscreen" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<link rel="apple-touch-icon" href="/~landscape/img/touch_icon.png">
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no,maximum-scale=1"/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-touch-fullscreen" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<link rel="apple-touch-icon" href="/~landscape/img/touch_icon.png">
|
||||
<link rel="icon" type="image/png" href="/~landscape/img/Favicon.png">
|
||||
<link rel="manifest"
|
||||
href='data:application/manifest+json,{
|
||||
@ -20,9 +20,9 @@
|
||||
"theme_color": "%23000000"}' />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"/>
|
||||
<div id="root"></div>
|
||||
<script src="/~landscape/js/channel.js"></script>
|
||||
<script src="/~landscape/js/session.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.4257dcef75bd12c7c419.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.8e25dc41456a44c967da.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1831,6 +1831,8 @@
|
||||
::
|
||||
%subscribe
|
||||
?> (team:title our.bol src.bol)
|
||||
?: =(our.bol who.act)
|
||||
[~ state]
|
||||
=/ join-wire=wire
|
||||
/join-group/[(scot %p who.act)]/[book.act]
|
||||
=/ meta=(unit (set path))
|
||||
|
14
pkg/interface/.babelrc
Normal file
14
pkg/interface/.babelrc
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"babel-plugin-root-import",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"rootPathSuffix": "./src"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
15
pkg/interface/package-lock.json
generated
15
pkg/interface/package-lock.json
generated
@ -2479,6 +2479,15 @@
|
||||
"object.assign": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-root-import": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-root-import/-/babel-plugin-root-import-6.5.0.tgz",
|
||||
"integrity": "sha512-PTD8fPl4v1kwn01u9d4rgRavDs5Z+jv4qa4/y6iYtoSgM4/xmjwMqo66j5A/BTZQEMA6OV5iFgyZ1PIhroJqqg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"slash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-styled-components": {
|
||||
"version": "1.10.7",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.7.tgz",
|
||||
@ -8623,6 +8632,12 @@
|
||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
||||
"dev": true
|
||||
},
|
||||
"slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"slice-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
|
||||
|
@ -55,6 +55,7 @@
|
||||
"@typescript-eslint/parser": "^3.8.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-root-import": "^6.5.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"eslint": "^6.8.0",
|
||||
|
@ -1,65 +0,0 @@
|
||||
import React, { Component, Fragment } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { ChatTabBar } from "./chat-tabbar";
|
||||
import { SidebarSwitcher } from "../../../../components/SidebarSwitch";
|
||||
import { deSig } from "../../../../lib/util";
|
||||
|
||||
|
||||
export class ChatHeader extends Component {
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const isinPopout = props.popout ? "popout/" : "";
|
||||
const group = Array.from(props.group.members);
|
||||
let title = props.station.substr(1);
|
||||
if (props.association &&
|
||||
"metadata" in props.association &&
|
||||
props.association.metadata.tile !== "") {
|
||||
title = props.association.metadata.title
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: "1rem" }}>
|
||||
<Link to="/~chat/">{"⟵ All Chats"}</Link>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
"pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative " +
|
||||
"overflow-x-auto overflow-y-hidden flex-shrink-0 "
|
||||
}
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
popout={props.popout}
|
||||
api={props.api}
|
||||
/>
|
||||
<Link
|
||||
to={"/~chat/" + isinPopout + "room" + props.station}
|
||||
className="pt2 white-d"
|
||||
>
|
||||
<h2
|
||||
className={
|
||||
"dib f9 fw4 lh-solid v-top " +
|
||||
(title === props.station.substr(1) ? "mono" : "")
|
||||
}
|
||||
style={{ width: "max-content" }}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
</Link>
|
||||
<ChatTabBar
|
||||
location={props.location}
|
||||
station={props.station}
|
||||
isOwner={deSig(props.match.params.ship) === window.ship}
|
||||
popout={props.popout}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export class ChatTabBar extends Component {
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let memColor = '',
|
||||
setColor = '',
|
||||
popout = '';
|
||||
|
||||
if (props.location.pathname.includes('/settings')) {
|
||||
memColor = 'gray3';
|
||||
setColor = 'black white-d';
|
||||
} else if (props.location.pathname.includes('/members')) {
|
||||
memColor = 'black white-d';
|
||||
setColor = 'gray3';
|
||||
} else {
|
||||
memColor = 'gray3';
|
||||
setColor = 'gray3';
|
||||
}
|
||||
|
||||
popout = props.location.pathname.includes('/popout')
|
||||
? 'popout/' : '';
|
||||
|
||||
const hidePopoutIcon = (this.props.popout)
|
||||
? 'dn-m dn-l dn-xl' : 'dib-m dib-l dib-xl';
|
||||
|
||||
return (
|
||||
<div className="dib flex-shrink-0 flex-grow-1">
|
||||
{props.isOwner ? (
|
||||
<div className={'dib pt2 f9 pl6 lh-solid'}>
|
||||
<Link
|
||||
className={'no-underline ' + memColor}
|
||||
to={'/~chat/' + popout + 'members' + props.station}
|
||||
>
|
||||
Members
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="dib" style={{ width: 0 }}></div>
|
||||
)}
|
||||
<div className={'dib pt2 f9 pl6 pr6 lh-solid'}>
|
||||
<Link
|
||||
className={'no-underline ' + setColor}
|
||||
to={'/~chat/' + popout + 'settings' + props.station}
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
</div>
|
||||
<a href={'/~chat/popout/room' + props.station} rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="dib fr pr1"
|
||||
style={{ paddingTop: '8px' }}
|
||||
>
|
||||
<img
|
||||
className={'flex-shrink-0 pr3 dn ' + hidePopoutIcon}
|
||||
src="/~chat/img/popout.png"
|
||||
height="16"
|
||||
width="16"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { deSig } from '../../../lib/util';
|
||||
import { ChatTabBar } from './lib/chat-tabbar';
|
||||
import { MemberElement } from './lib/member-element';
|
||||
import { InviteElement } from './lib/invite-element';
|
||||
import { SidebarSwitcher } from '../../../components/SidebarSwitch';
|
||||
import { GroupView } from '../../../components/Group';
|
||||
import { PatpNoSig } from '../../../types/noun';
|
||||
|
||||
export class MemberScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.inviteShips = this.inviteShips.bind(this);
|
||||
}
|
||||
|
||||
inviteShips(ships) {
|
||||
const { props } = this;
|
||||
return props.api.chat.invite(props.station, ships.map(s => `~${s}`));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const isinPopout = this.props.popout ? 'popout/' : '';
|
||||
|
||||
let title = props.station.substr(1);
|
||||
|
||||
if (props.association && 'metadata' in props.association) {
|
||||
title =
|
||||
props.association.metadata.title !== ''
|
||||
? props.association.metadata.title
|
||||
: props.station.substr(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-100 w-100 overflow-x-hidden flex flex-column white-d'>
|
||||
<div
|
||||
className='w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8'
|
||||
style={{ height: '1rem' }}
|
||||
>
|
||||
<Link to='/~chat/'>{'⟵ All Chats'}</Link>
|
||||
</div>
|
||||
<div
|
||||
className={`pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative
|
||||
overflow-x-scroll overflow-x-auto-l overflow-x-auto-xl flex-shrink-0`}
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={this.props.sidebarShown}
|
||||
popout={this.props.popout}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<Link
|
||||
to={'/~chat/' + isinPopout + 'room' + props.station}
|
||||
className='pt2 white-d'
|
||||
>
|
||||
<h2
|
||||
className={
|
||||
'dib f9 fw4 lh-solid v-top ' +
|
||||
(title === props.station.substr(1) ? 'mono' : '')
|
||||
}
|
||||
style={{ width: 'max-content' }}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
</Link>
|
||||
<ChatTabBar
|
||||
{...props}
|
||||
station={props.station}
|
||||
numPeers={5}
|
||||
isOwner={deSig(props.match.params.ship) === window.ship}
|
||||
popout={this.props.popout}
|
||||
api={props.api}
|
||||
/>
|
||||
</div>
|
||||
<div className='w-100 pl3 mt0 mt4-m mt4-l mt4-xl cf pr6'>
|
||||
{ props.association['group-path'] && (
|
||||
<GroupView
|
||||
permissions
|
||||
group={props.group}
|
||||
resourcePath={props.association['group-path'] || ''}
|
||||
associations={props.associations}
|
||||
groups={props.groups}
|
||||
inviteShips={this.inviteShips}
|
||||
contacts={props.contacts}
|
||||
/> )}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,459 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { deSig, uxToHex, writeText } from '../../../lib/util';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import { ChatTabBar } from './lib/chat-tabbar';
|
||||
import { InviteSearch } from '../../../components/InviteSearch';
|
||||
import SidebarSwitcher from '../../../components/SidebarSwitch';
|
||||
import Toggle from '../../../components/toggle';
|
||||
|
||||
export class SettingsScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
title: '',
|
||||
description: '',
|
||||
color: '',
|
||||
// groupify settings
|
||||
targetGroup: null,
|
||||
inclusive: false,
|
||||
awaiting: false,
|
||||
type: 'Editing chat...'
|
||||
};
|
||||
|
||||
this.renderDelete = this.renderDelete.bind(this);
|
||||
this.changeTargetGroup = this.changeTargetGroup.bind(this);
|
||||
this.changeInclusive = this.changeInclusive.bind(this);
|
||||
this.changeTitle = this.changeTitle.bind(this);
|
||||
this.changeDescription = this.changeDescription.bind(this);
|
||||
this.changeColor = this.changeColor.bind(this);
|
||||
this.submitColor = this.submitColor.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { props } = this;
|
||||
if (props.association && 'metadata' in props.association) {
|
||||
this.setState({
|
||||
title: props.association.metadata.title,
|
||||
description: props.association.metadata.description,
|
||||
color: `#${uxToHex(props.association.metadata.color)}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props, state } = this;
|
||||
if (Boolean(state.isLoading) && !(props.station in props.inbox)) {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
}, () => {
|
||||
props.history.push('/~chat');
|
||||
});
|
||||
}
|
||||
|
||||
if ((state.title === '') && (prevProps !== props)) {
|
||||
if (props.association && 'metadata' in props.association)
|
||||
this.setState({
|
||||
title: props.association.metadata.title,
|
||||
description: props.association.metadata.description,
|
||||
color: `#${uxToHex(props.association.metadata.color)}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeTargetGroup(target) {
|
||||
if (target.groups.length === 1) {
|
||||
this.setState({ targetGroup: target.groups[0] });
|
||||
} else {
|
||||
this.setState({ targetGroup: null });
|
||||
}
|
||||
}
|
||||
|
||||
changeInclusive(event) {
|
||||
this.setState({ inclusive: Boolean(event.target.checked) });
|
||||
}
|
||||
|
||||
changeTitle() {
|
||||
this.setState({ title: event.target.value });
|
||||
}
|
||||
|
||||
changeDescription() {
|
||||
this.setState({ description: event.target.value });
|
||||
}
|
||||
|
||||
changeColor() {
|
||||
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);
|
||||
let currentColor = '000000';
|
||||
if (props.association && 'metadata' in props.association) {
|
||||
currentColor = uxToHex(props.association.metadata.color);
|
||||
}
|
||||
if (hexTest && (hexTest[1] !== currentColor)) {
|
||||
const chatOwner = (deSig(props.match.params.ship) === window.ship);
|
||||
const association =
|
||||
(props.association) && ('metadata' in props.association)
|
||||
? props.association : {};
|
||||
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
color
|
||||
).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteChat() {
|
||||
const { props } = this;
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
awaiting: true,
|
||||
type: (deSig(props.match.params.ship) === window.ship)
|
||||
? 'Deleting chat...'
|
||||
: 'Leaving chat...'
|
||||
}, (() => {
|
||||
props.api.chat.delete(props.station);
|
||||
}));
|
||||
}
|
||||
|
||||
groupifyChat() {
|
||||
const { props, state } = this;
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
awaiting: true,
|
||||
type: 'Converting chat...'
|
||||
}, (() => {
|
||||
props.api.chat.groupify(
|
||||
props.station, state.targetGroup, state.inclusive
|
||||
).then(() => this.setState({ awaiting: false }));
|
||||
}));
|
||||
}
|
||||
|
||||
renderDelete() {
|
||||
const { props } = this;
|
||||
|
||||
const chatOwner = (deSig(props.match.params.ship) === window.ship);
|
||||
|
||||
const deleteButtonClasses = (chatOwner) ? 'b--red2 red2 pointer bg-gray0-d' : 'b--gray3 gray3 bg-gray0-d c-default';
|
||||
const leaveButtonClasses = (!chatOwner) ? 'pointer' : 'c-default';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={'w-100 fl mt3 ' + ((chatOwner) ? 'o-30' : '')}>
|
||||
<p className="f8 mt3 lh-copy db">Leave Chat</p>
|
||||
<p className="f9 gray2 db mb4">Remove this chat from your chat list. You will need to request for access again.</p>
|
||||
<a onClick={(!chatOwner) ? this.deleteChat.bind(this) : null}
|
||||
className={'dib f9 black gray4-d bg-gray0-d ba pa2 b--black b--gray1-d ' + leaveButtonClasses}
|
||||
>Leave this chat</a>
|
||||
</div>
|
||||
<div className={'w-100 fl mt3 ' + ((!chatOwner) ? 'o-30' : '')}>
|
||||
<p className="f8 mt3 lh-copy db">Delete Chat</p>
|
||||
<p className="f9 gray2 db mb4">Permanently delete this chat. All current members will no longer see this chat.</p>
|
||||
<a onClick={(chatOwner) ? this.deleteChat.bind(this) : null}
|
||||
className={'dib f9 ba pa2 ' + deleteButtonClasses}
|
||||
>Delete this chat</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderGroupify() {
|
||||
const { props, state } = this;
|
||||
|
||||
const chatOwner = (deSig(props.match.params.ship) === window.ship);
|
||||
|
||||
const groupPath = props.association['group-path'];
|
||||
const ownedUnmanagedVillage =
|
||||
chatOwner &&
|
||||
!props.contacts[groupPath];
|
||||
|
||||
if (!ownedUnmanagedVillage) {
|
||||
return null;
|
||||
} else {
|
||||
let inclusiveToggle = <div />;
|
||||
if (state.targetGroup) {
|
||||
inclusiveToggle = (
|
||||
<div className="mt4">
|
||||
<Toggle
|
||||
boolean={state.inclusive}
|
||||
change={this.changeInclusive}
|
||||
/>
|
||||
<span className="dib f9 white-d inter ml3">
|
||||
Add all members to group
|
||||
</span>
|
||||
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
|
||||
Add chat members to the group if they aren't in it yet
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={'w-100 fl mt3'} style={{ maxWidth: '29rem' }}>
|
||||
<p className="f8 mt3 lh-copy db">Convert Chat</p>
|
||||
<p className="f9 gray2 db mb4">
|
||||
Convert this chat into a group with associated chat, or select a
|
||||
group to add this chat to.
|
||||
</p>
|
||||
<InviteSearch
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
associations={props.associations}
|
||||
groupResults={true}
|
||||
shipResults={false}
|
||||
invites={{
|
||||
groups: state.targetGroup ? [state.targetGroup] : [],
|
||||
ships: []
|
||||
}}
|
||||
setInvite={this.changeTargetGroup}
|
||||
/>
|
||||
{inclusiveToggle}
|
||||
<a onClick={this.groupifyChat.bind(this)}
|
||||
className={'dib f9 black gray4-d bg-gray0-d ba pa2 mt4 b--black b--gray1-d pointer'}
|
||||
>
|
||||
Convert to group
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderMetadataSettings() {
|
||||
const { props, state } = this;
|
||||
|
||||
const chatOwner = (deSig(props.match.params.ship) === window.ship);
|
||||
|
||||
const association = (props.association) && ('metadata' in props.association)
|
||||
? props.association : {};
|
||||
|
||||
return(
|
||||
<div>
|
||||
<div className={'w-100 pb6 fl mt3 ' + ((chatOwner) ? '' : 'o-30')}>
|
||||
<p className="f8 mt3 lh-copy">Rename</p>
|
||||
<p className="f9 gray2 db mb4">Change the name of this chat</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: '29rem' }}
|
||||
>
|
||||
<input
|
||||
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={state.title}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeTitle}
|
||||
onBlur={() => {
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
state.title,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
uxToHex(association.metadata.color)
|
||||
).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="f8 mt3 lh-copy">Change description</p>
|
||||
<p className="f9 gray2 db mb4">Change the description of this chat</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: '29rem' }}
|
||||
>
|
||||
<input
|
||||
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={state.description}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeDescription}
|
||||
onBlur={() => {
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
state.description,
|
||||
association.metadata['date-created'],
|
||||
uxToHex(association.metadata.color)
|
||||
).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="f8 mt3 lh-copy">Change color</p>
|
||||
<p className="f9 gray2 db mb4">Give this chat a color when viewing group channels</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: '10rem' }}
|
||||
>
|
||||
<div className="absolute"
|
||||
style={{
|
||||
height: 16,
|
||||
width: 16,
|
||||
backgroundColor: state.color,
|
||||
top: 13,
|
||||
left: 11
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
className={'pl7 f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={state.color}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeColor}
|
||||
onBlur={this.submitColor}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const isinPopout = this.props.popout ? 'popout/' : '';
|
||||
|
||||
const permission = Array.from(props.group.members.values());
|
||||
|
||||
if (state.isLoading) {
|
||||
let title = props.station.substr(1);
|
||||
|
||||
if ((props.association) && ('metadata' in props.association)) {
|
||||
title = (props.association.metadata.title !== '')
|
||||
? props.association.metadata.title : props.station.substr(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column white-d">
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: '1rem' }}
|
||||
>
|
||||
<Link to="/~chat/">{'⟵ All Chats'}</Link>
|
||||
</div>
|
||||
<div
|
||||
className="pl4 pt2 bb b--gray4 b--gray2-d bg-gray0-d flex relative overflow-x-scroll overflow-x-auto-l overflow-x-auto-xl flex-shrink-0"
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={this.props.sidebarShown}
|
||||
popout={this.props.popout}
|
||||
/>
|
||||
<Link to={'/~chat/' + isinPopout + 'room' + props.station}
|
||||
className="pt2 white-d"
|
||||
>
|
||||
<h2
|
||||
className={'dib f9 fw4 lh-solid v-top ' +
|
||||
((title === props.station.substr(1)) ? 'mono' : '')}
|
||||
style={{ width: 'max-content' }}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
</Link>
|
||||
<ChatTabBar
|
||||
{...props}
|
||||
station={props.station}
|
||||
numPeers={permission.length}
|
||||
host={props.match.params.ship}
|
||||
api={props.api}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-100 pl3 mt4 cf">
|
||||
<Spinner awaiting={state.awaiting}
|
||||
classes="absolute right-2 bottom-2 ba pa2 b--gray1-d"
|
||||
text={state.type} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let title = props.station.substr(1);
|
||||
|
||||
if ((props.association) && ('metadata' in props.association)) {
|
||||
title = (props.association.metadata.title !== '')
|
||||
? props.association.metadata.title : props.station.substr(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column white-d">
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: '1rem' }}
|
||||
>
|
||||
<Link to="/~chat/">{'⟵ All Chats'}</Link>
|
||||
</div>
|
||||
<div
|
||||
className="pl4 pt2 bb b--gray4 b--gray1-d flex relative overflow-x-scroll overflow-x-auto-l overflow-x-auto-xl flex-shrink-0"
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={this.props.sidebarShown}
|
||||
popout={this.props.popout}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<Link to={'/~chat/' + isinPopout + 'room' + props.station}
|
||||
className="pt2"
|
||||
>
|
||||
<h2
|
||||
className={'dib f9 fw4 lh-solid v-top ' +
|
||||
((title === props.station.substr(1)) ? 'mono' : '')}
|
||||
style={{ width: 'max-content' }}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
</Link>
|
||||
<ChatTabBar
|
||||
{...props}
|
||||
station={props.station}
|
||||
numPeers={permission.length}
|
||||
isOwner={deSig(props.match.params.ship) === window.ship}
|
||||
popout={this.props.popout}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-100 pl3 mt4 cf">
|
||||
<h2 className="f8 pb2">Chat Settings</h2>
|
||||
{this.renderGroupify()}
|
||||
{this.renderDelete()}
|
||||
{this.renderMetadataSettings()}
|
||||
<Spinner awaiting={state.awaiting}
|
||||
classes="absolute right-2 bottom-2 ba pa2 b--gray1-d"
|
||||
text={state.type} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
// import "./fonts/font.css";
|
||||
import App from './App';
|
||||
import App from './views/App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
@ -1,6 +1,6 @@
|
||||
import _ from "lodash";
|
||||
import { uuid } from "../lib/util";
|
||||
import { Patp, Path } from "../types/noun";
|
||||
import { Patp, Path } from "~/types/noun";
|
||||
import BaseStore from '../store/base';
|
||||
|
||||
export default class BaseApi<S extends object = {}> {
|
@ -1,7 +1,7 @@
|
||||
import BaseApi from './base';
|
||||
import { uuid } from '../lib/util';
|
||||
import { Letter, ChatAction, Envelope } from '../types/chat-update';
|
||||
import { Patp, Path, PatpNoSig } from '../types/noun';
|
||||
import { Letter, ChatAction, Envelope } from '~/types/chat-update';
|
||||
import { Patp, Path, PatpNoSig } from '~/types/noun';
|
||||
import { StoreState } from '../store/type';
|
||||
import BaseStore from '../store/base';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp, Path, Enc } from '../types/noun';
|
||||
import { Contact, ContactEdit } from '../types/contact-update';
|
||||
import { GroupPolicy, Resource } from '../types/group-update';
|
||||
import { Patp, Path, Enc } from '~/types/noun';
|
||||
import { Contact, ContactEdit } from '~/types/contact-update';
|
||||
import { GroupPolicy, Resource } from '~/types/group-update';
|
||||
|
||||
export default class ContactsApi extends BaseApi<StoreState> {
|
||||
create(
|
@ -1,4 +1,4 @@
|
||||
import { Patp } from '../types/noun';
|
||||
import { Patp } from '~/types/noun';
|
||||
import BaseApi from './base';
|
||||
import ChatApi from './chat';
|
||||
import { StoreState } from '../store/type';
|
@ -1,13 +1,13 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp, Enc } from '../types/noun';
|
||||
import { Path, Patp, Enc } from '~/types/noun';
|
||||
import {
|
||||
GroupAction,
|
||||
GroupPolicy,
|
||||
Resource,
|
||||
Tag,
|
||||
GroupPolicyDiff,
|
||||
} from '../types/group-update';
|
||||
} from '~/types/group-update';
|
||||
|
||||
export default class GroupsApi extends BaseApi<StoreState> {
|
||||
remove(resource: Resource, ships: Patp[]) {
|
@ -1,6 +1,6 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { Serial, Path } from "../types/noun";
|
||||
import { Serial, Path } from "~/types/noun";
|
||||
|
||||
export default class InviteApi extends BaseApi<StoreState> {
|
||||
accept(app: Path, uid: Serial) {
|
@ -2,7 +2,7 @@ import { stringToTa } from '../lib/util';
|
||||
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path } from '../types/noun';
|
||||
import { Path } from '~/types/noun';
|
||||
|
||||
export default class LinksApi extends BaseApi<StoreState> {
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp } from '../types/noun';
|
||||
import { Path, Patp } from '~/types/noun';
|
||||
|
||||
export default class MetadataApi extends BaseApi<StoreState> {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import BaseApi from './base';
|
||||
import { PublishResponse } from '../types/publish-response';
|
||||
import { PatpNoSig } from '../types/noun';
|
||||
import { BookId, NoteId } from '../types/publish-update';
|
||||
import { PublishResponse } from '~/types/publish-response';
|
||||
import { PatpNoSig } from '~/types/noun';
|
||||
import { BookId, NoteId } from '~/types/publish-update';
|
||||
|
||||
export default class PublishApi extends BaseApi {
|
||||
handleEvent(data: PublishResponse) {
|
@ -1,6 +1,6 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import {S3Update} from '../types/s3-update';
|
||||
import {S3Update} from '../../types/s3-update';
|
||||
|
||||
|
||||
export default class S3Api extends BaseApi<StoreState> {
|
@ -1,5 +1,5 @@
|
||||
import { roleTags, RoleTags, Group, Resource } from '../types/group-update';
|
||||
import { PatpNoSig, Path } from '../types/noun';
|
||||
import { roleTags, RoleTags, Group, Resource } from '~/types/group-update';
|
||||
import { PatpNoSig, Path } from '~/types/noun';
|
||||
|
||||
|
||||
export function roleForShip(group: Group, ship: PatpNoSig): RoleTags | undefined {
|
@ -1,47 +1,76 @@
|
||||
import defaultApps from './default-apps';
|
||||
|
||||
export default function index(associations, apps) {
|
||||
const index = new Map([
|
||||
const indexes = new Map([
|
||||
['commands', []],
|
||||
['subscriptions', []],
|
||||
['groups', []],
|
||||
['apps', []]
|
||||
]);
|
||||
|
||||
// result schematic
|
||||
const result = function(title, link, app, host) {
|
||||
return {
|
||||
'title': title,
|
||||
'link': link,
|
||||
'app': app,
|
||||
'host': host
|
||||
};
|
||||
// result schematic
|
||||
const result = function(title, link, app, host) {
|
||||
return {
|
||||
'title': title,
|
||||
'link': link,
|
||||
'app': app,
|
||||
'host': host
|
||||
};
|
||||
};
|
||||
|
||||
const commandIndex = function () {
|
||||
// commands are special cased for default suite
|
||||
const commands = [];
|
||||
defaultApps.filter((e) => {
|
||||
return (e !== 'dojo');
|
||||
}).map((e) => {
|
||||
let title = e;
|
||||
if (e === 'link') {
|
||||
title = 'Links';
|
||||
}
|
||||
defaultApps
|
||||
.filter((e) => {
|
||||
return e !== 'dojo';
|
||||
})
|
||||
.map((e) => {
|
||||
let title = e;
|
||||
if (e === 'link') {
|
||||
title = 'Links';
|
||||
}
|
||||
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
|
||||
let obj = result(`${title}: Create`, `/~${e}/new`, e, null);
|
||||
commands.push(obj);
|
||||
|
||||
if (title === 'Groups') {
|
||||
obj = result(`${title}: Join Group`, `/~${e}/join`, title, null);
|
||||
let obj = result(`${title}: Create`, `/~${e}/new`, e, null);
|
||||
commands.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
if (title === 'Groups') {
|
||||
obj = result(`${title}: Join Group`, `/~${e}/join`, title, null);
|
||||
commands.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
commands.push(result('Profile', '/~profile', 'profile', null));
|
||||
index.set('commands', commands);
|
||||
|
||||
return commands;
|
||||
};
|
||||
|
||||
const appIndex = function (apps) {
|
||||
// all apps are indexed from launch data
|
||||
// indexed into 'apps'
|
||||
const applications = [];
|
||||
Object.keys(apps)
|
||||
.filter((e) => {
|
||||
return apps[e]?.type?.basic;
|
||||
})
|
||||
.map((e) => {
|
||||
const obj = result(
|
||||
apps[e].type.basic.title,
|
||||
apps[e].type.basic.linkedUrl,
|
||||
apps[e].type.basic.title,
|
||||
null
|
||||
);
|
||||
applications.push(obj);
|
||||
});
|
||||
// add groups separately
|
||||
applications.push(
|
||||
result('Groups', '/~groups', 'groups', null)
|
||||
);
|
||||
return applications;
|
||||
};
|
||||
|
||||
export default function index(associations, apps) {
|
||||
// all metadata from all apps is indexed
|
||||
// into subscriptions and groups
|
||||
const subscriptions = [];
|
||||
@ -74,15 +103,9 @@ export default function index(associations, apps) {
|
||||
);
|
||||
groups.push(obj);
|
||||
} else {
|
||||
let endpoint = '';
|
||||
if (app === 'chat') {
|
||||
endpoint = '/room';
|
||||
} else if (app === 'publish') {
|
||||
endpoint = '/notebook';
|
||||
}
|
||||
const obj = result(
|
||||
title,
|
||||
`/~${each['app-name']}${endpoint}${each['app-path']}`,
|
||||
`/~${each['app-name']}/join${each['app-path']}`,
|
||||
app.charAt(0).toUpperCase() + app.slice(1),
|
||||
shipStart.slice(0, shipStart.indexOf('/'))
|
||||
);
|
||||
@ -90,19 +113,11 @@ export default function index(associations, apps) {
|
||||
}
|
||||
});
|
||||
});
|
||||
index.set('subscriptions', subscriptions);
|
||||
index.set('groups', groups);
|
||||
|
||||
// all apps are indexed from launch data
|
||||
// indexed into 'apps'
|
||||
const applications = [];
|
||||
Object.keys(apps).filter((e) => {
|
||||
return (apps[e]?.type?.basic);
|
||||
}).map((e) => {
|
||||
const obj = result(apps[e].type.basic.title, apps[e].type.basic.linkedUrl, apps[e].type.basic.title, null);
|
||||
applications.push(obj);
|
||||
});
|
||||
index.set('apps', applications);
|
||||
indexes.set('commands', commandIndex());
|
||||
indexes.set('subscriptions', subscriptions);
|
||||
indexes.set('groups', groups);
|
||||
indexes.set('apps', appIndex(apps));
|
||||
|
||||
return index;
|
||||
return indexes;
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { useCallback, useMemo, useEffect, useRef } from "react";
|
||||
import { S3State } from "../types/s3-update";
|
||||
import { S3State } from "../../types/s3-update";
|
||||
import S3 from "aws-sdk/clients/s3";
|
||||
|
||||
export function useS3(s3: S3State) {
|
@ -1,8 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { ChatUpdate } from '../types/chat-update';
|
||||
import { ChatHookUpdate } from '../types/chat-hook-update';
|
||||
import { StoreState } from '../../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { ChatUpdate } from '~/types/chat-update';
|
||||
import { ChatHookUpdate } from '~/types/chat-hook-update';
|
||||
|
||||
type ChatState = Pick<StoreState, 'chatInitialized' | 'chatSynced' | 'inbox' | 'pendingMessages'>;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
|
||||
type LocalState = Pick<StoreState, 'connection'>;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { ContactUpdate } from '../types/contact-update';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { ContactUpdate } from '~/types/contact-update';
|
||||
|
||||
type ContactState = Pick<StoreState, 'contacts'>;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import {
|
||||
GroupUpdate,
|
||||
Group,
|
||||
@ -11,8 +11,8 @@ import {
|
||||
OpenPolicy,
|
||||
InvitePolicyDiff,
|
||||
InvitePolicy,
|
||||
} from '../types/group-update';
|
||||
import { Enc, PatpNoSig } from '../types/noun';
|
||||
} from '~/types/group-update';
|
||||
import { Enc, PatpNoSig } from '~/types/noun';
|
||||
import { resourceAsPath } from '../lib/util';
|
||||
|
||||
type GroupState = Pick<StoreState, 'groups' | 'groupKeys'>;
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { InviteUpdate } from '../types/invite-update';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { InviteUpdate } from '~/types/invite-update';
|
||||
|
||||
type InviteState = Pick<StoreState, "invites">;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { LaunchUpdate } from '../types/launch-update';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../store/type';
|
||||
import { LaunchUpdate } from '~/types/launch-update';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { StoreState } from '../../store/type';
|
||||
|
||||
type LaunchState = Pick<StoreState, 'launch' | 'weather' | 'userLocation'>;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { LinkUpdate, Pagination } from '../types/link-update';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { LinkUpdate, Pagination } from '~/types/link-update';
|
||||
|
||||
// page size as expected from link-view.
|
||||
// must change in parallel with the +page-size in /app/link-view to
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { LinkListenUpdate } from '../types/link-listen-update';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { LinkListenUpdate } from '~/types/link-listen-update';
|
||||
|
||||
type LinkListenState = Pick<StoreState, 'linkListening'>;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { LocalUpdate, BackgroundConfig } from '../types/local-update';
|
||||
import { StoreState } from '~/store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { LocalUpdate, BackgroundConfig } from '~/types/local-update';
|
||||
|
||||
|
||||
type LocalState = Pick<StoreState, 'sidebarShown' | 'omniboxShown' | 'baseHash' | 'hideAvatars' | 'hideNicknames' | 'background' | 'dark'>;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { StoreState } from '../store/type';
|
||||
import { StoreState } from '../../store/type';
|
||||
|
||||
import { MetadataUpdate } from '../types/metadata-update';
|
||||
import { Cage } from '../types/cage';
|
||||
import { MetadataUpdate } from '~/types/metadata-update';
|
||||
import { Cage } from '~/types/cage';
|
||||
|
||||
type MetadataState = Pick<StoreState, 'associations'>;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { PermissionUpdate } from '../types/permission-update';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { PermissionUpdate } from '~/types/permission-update';
|
||||
|
||||
type PermissionState = Pick<StoreState, "permissions">;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
|
||||
type PublishState = Pick<StoreState, 'notebooks'>;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { PublishUpdate } from '../types/publish-update';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../store/type';
|
||||
import { getTagFromFrond } from '../types/noun';
|
||||
import { PublishUpdate } from '~/types/publish-update';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { getTagFromFrond } from '~/types/noun';
|
||||
|
||||
type PublishState = Pick<StoreState, 'notebooks'>;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { S3Update } from '../types/s3-update';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { S3Update } from '~/types/s3-update';
|
||||
|
||||
type S3State = Pick<StoreState, 's3'>;
|
||||
|
@ -5,7 +5,7 @@ import LocalReducer from '../reducers/local';
|
||||
import ChatReducer from '../reducers/chat-update';
|
||||
|
||||
import { StoreState } from './type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { Cage } from '~/types/cage';
|
||||
import ContactReducer from '../reducers/contact-update';
|
||||
import LinkUpdateReducer from '../reducers/link-update';
|
||||
import S3Reducer from '../reducers/s3-update';
|
@ -1,17 +1,17 @@
|
||||
import { Inbox, Envelope } from '../types/chat-update';
|
||||
import { ChatHookUpdate } from '../types/chat-hook-update';
|
||||
import { Path } from '../types/noun';
|
||||
import { Invites } from '../types/invite-update';
|
||||
import { BackgroundConfig } from '../types/local-update';
|
||||
import { Associations } from '../types/metadata-update';
|
||||
import { Rolodex } from '../types/contact-update';
|
||||
import { Notebooks } from '../types/publish-update';
|
||||
import { Groups } from '../types/group-update';
|
||||
import { S3State } from '../types/s3-update';
|
||||
import { Permissions } from '../types/permission-update';
|
||||
import { LaunchState, WeatherState } from '../types/launch-update';
|
||||
import { LinkComments, LinkCollections, LinkSeen } from '../types/link-update';
|
||||
import { ConnectionStatus } from '../types/connection';
|
||||
import { Inbox, Envelope } from '~/types/chat-update';
|
||||
import { ChatHookUpdate } from '~/types/chat-hook-update';
|
||||
import { Path } from '~/types/noun';
|
||||
import { Invites } from '~/types/invite-update';
|
||||
import { Associations } from '~/types/metadata-update';
|
||||
import { Rolodex } from '~/types/contact-update';
|
||||
import { Notebooks } from '~/types/publish-update';
|
||||
import { Groups } from '~/types/group-update';
|
||||
import { S3State } from '~/types/s3-update';
|
||||
import { Permissions } from '~/types/permission-update';
|
||||
import { LaunchState, WeatherState } from '~/types/launch-update';
|
||||
import { LinkComments, LinkCollections, LinkSeen } from '~/types/link-update';
|
||||
import { ConnectionStatus } from '~/types/connection';
|
||||
import { BackgroundConfig } from '~/types/local-update';
|
||||
|
||||
export interface StoreState {
|
||||
// local state
|
@ -1,6 +1,6 @@
|
||||
import BaseStore from "../store/base";
|
||||
import BaseApi from "../api/base";
|
||||
import { Path } from "../types/noun";
|
||||
import { Path } from "~/types/noun";
|
||||
|
||||
export default class BaseSubscription<S extends object> {
|
||||
private errorCount = 0;
|
@ -1,6 +1,6 @@
|
||||
import BaseSubscription from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path } from '../types/noun';
|
||||
import { Path } from '~/types/noun';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
@ -13,34 +13,16 @@ import './css/fonts.css';
|
||||
import light from './themes/light';
|
||||
import dark from './themes/old-dark';
|
||||
|
||||
import LaunchApp from './apps/launch/app';
|
||||
import ChatApp from './apps/chat/app';
|
||||
import DojoApp from './apps/dojo/app';
|
||||
import GroupsApp from './apps/groups/app';
|
||||
import LinksApp from './apps/links/app';
|
||||
import PublishApp from './apps/publish/app';
|
||||
import Profile from './apps/profile/profile';
|
||||
|
||||
import { Content } from './components/Content';
|
||||
import StatusBar from './components/StatusBar';
|
||||
import Omnibox from './components/Omnibox';
|
||||
import ErrorComponent from './components/Error';
|
||||
|
||||
import GlobalStore from './store/store';
|
||||
import GlobalSubscription from './subscription/global';
|
||||
import GlobalApi from './api/global';
|
||||
import { uxToHex } from './lib/util';
|
||||
import { Sigil } from './lib/sigil';
|
||||
|
||||
// const Style = createGlobalStyle`
|
||||
// ${cssReset}
|
||||
// html {
|
||||
// background-color: ${p => p.theme.colors.white};
|
||||
// }
|
||||
//
|
||||
// strong {
|
||||
// font-weight: 600;
|
||||
// }
|
||||
// `;
|
||||
import GlobalStore from '~/logic/store/store';
|
||||
import GlobalSubscription from '~/logic/subscription/global';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
|
||||
const Root = styled.div`
|
||||
font-family: ${p => p.theme.fonts.sans};
|
||||
@ -57,10 +39,6 @@ const Root = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
height: calc(100% - 45px);
|
||||
`;
|
||||
|
||||
const StatusBarWithRouter = withRouter(StatusBar);
|
||||
|
||||
class App extends React.Component {
|
||||
@ -127,10 +105,9 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const channel = window.channel;
|
||||
|
||||
const associations = this.state.associations ? this.state.associations : { contacts: {} };
|
||||
const { state } = this;
|
||||
const associations = state.associations ?
|
||||
state.associations : { contacts: {} };
|
||||
const theme = state.dark ? dark : light;
|
||||
const { background } = state;
|
||||
|
||||
@ -154,92 +131,11 @@ class App extends React.Component {
|
||||
dark={state.dark}
|
||||
show={state.omniboxShown}
|
||||
/>
|
||||
<Content>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path='/'
|
||||
render={p => (
|
||||
<LaunchApp
|
||||
ship={this.ship}
|
||||
api={this.api}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path='/~chat'
|
||||
render={p => (
|
||||
<ChatApp
|
||||
ship={this.ship}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path='/~dojo'
|
||||
render={p => (
|
||||
<DojoApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
subscription={this.subscription}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path='/~groups'
|
||||
render={p => (
|
||||
<GroupsApp
|
||||
ship={this.ship}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path='/~link'
|
||||
render={p => (
|
||||
<LinksApp
|
||||
ship={this.ship}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path='/~publish'
|
||||
render={p => (
|
||||
<PublishApp
|
||||
ship={this.ship}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path="/~profile"
|
||||
render={ p => (
|
||||
<Profile ship={this.ship} api={this.api} {...state} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
render={(props) => (
|
||||
<ErrorComponent {...props} code={404} description="Not Found" />
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
</Content>
|
||||
<Content
|
||||
ship={this.ship}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state} />
|
||||
</Router>
|
||||
</Root>
|
||||
</ThemeProvider>
|
@ -11,11 +11,11 @@ import { SettingsScreen } from './components/settings';
|
||||
import { NewScreen } from './components/new';
|
||||
import { JoinScreen } from './components/join';
|
||||
import { NewDmScreen } from './components/new-dm';
|
||||
import { PatpNoSig } from '../../types/noun';
|
||||
import GlobalApi from '../../api/global';
|
||||
import { StoreState } from '../../store/type';
|
||||
import GlobalSubscription from '../../subscription/global';
|
||||
import {groupBunts} from '../../types/group-update';
|
||||
import { PatpNoSig } from '~/types/noun';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
import GlobalSubscription from '~/logic/subscription/global';
|
||||
import {groupBunts} from '~/types/group-update';
|
||||
|
||||
type ChatAppProps = StoreState & {
|
||||
ship: PatpNoSig;
|
||||
@ -281,44 +281,6 @@ export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/~chat/(popout)?/members/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
|
||||
const association =
|
||||
station in associations['chat'] ? associations.chat[station] : {};
|
||||
const groupPath = association['group-path'];
|
||||
|
||||
const group = groups[groupPath] || {};
|
||||
return (
|
||||
<Skeleton
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<MemberScreen
|
||||
{...props}
|
||||
api={api}
|
||||
group={group}
|
||||
groups={groups}
|
||||
associations={associations}
|
||||
station={station}
|
||||
association={association}
|
||||
contacts={contacts}
|
||||
popout={popout}
|
||||
sidebarShown={sidebarShown}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/~chat/(popout)?/settings/(~)?/:ship/:station+"
|
@ -6,15 +6,15 @@ import { Link, RouteComponentProps } from "react-router-dom";
|
||||
import { ChatWindow } from './lib/chat-window';
|
||||
import { ChatHeader } from './lib/chat-header';
|
||||
import { ChatInput } from "./lib/chat-input";
|
||||
import { deSig } from "../../../lib/util";
|
||||
import { ChatHookUpdate } from "../../../types/chat-hook-update";
|
||||
import ChatApi from "../../../api/chat";
|
||||
import { Inbox, Envelope } from "../../../types/chat-update";
|
||||
import { Contacts } from "../../../types/contact-update";
|
||||
import { Path, Patp } from "../../../types/noun";
|
||||
import GlobalApi from "../../../api/global";
|
||||
import { Association } from "../../../types/metadata-update";
|
||||
import {Group} from "../../../types/group-update";
|
||||
import { deSig } from "~/logic/lib/util";
|
||||
import { ChatHookUpdate } from "~/types/chat-hook-update";
|
||||
import ChatApi from "~/logic/api/chat";
|
||||
import { Inbox, Envelope } from "~/types/chat-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import { Path, Patp } from "~/types/noun";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Association } from "~/types/metadata-update";
|
||||
import {Group} from "~/types/group-update";
|
||||
|
||||
|
||||
type ChatScreenProps = RouteComponentProps<{
|
||||
@ -96,7 +96,7 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
|
||||
props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
props.envelopes.length > 0;
|
||||
|
||||
|
||||
const unreadCount = props.length - props.read;
|
||||
const unreadMsg = unreadCount > 0 && props.envelopes[unreadCount - 1];
|
||||
|
||||
@ -114,6 +114,7 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
|
||||
sidebarShown={props.sidebarShown}
|
||||
popout={props.popout} />
|
||||
<ChatWindow
|
||||
history={props.history}
|
||||
isChatMissing={isChatMissing}
|
||||
isChatLoading={isChatLoading}
|
||||
isChatUnsynced={isChatUnsynced}
|
||||
@ -142,6 +143,9 @@ export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
|
||||
s3={props.s3}
|
||||
placeholder="Message..."
|
||||
message={this.state.messages.get(props.station) || ""}
|
||||
deleteMessage={() => this.setState({
|
||||
messages: this.state.messages.set(props.station, "")
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
export class JoinScreen extends Component {
|
@ -8,7 +8,7 @@ import 'codemirror/addon/display/placeholder';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
|
||||
const BROWSER_REGEX =
|
||||
new RegExp(String(!/Android|webOS|iPhone|iPad|iPod|BlackBerry/i));
|
||||
new RegExp(String(/Android|webOS|iPhone|iPad|iPod|BlackBerry/i));
|
||||
|
||||
|
||||
const MARKDOWN_CONFIG = {
|
||||
@ -47,6 +47,7 @@ export default class ChatEditor extends Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props } = this;
|
||||
|
||||
if (prevProps.message !== props.message) {
|
||||
this.editor.setValue(props.message);
|
||||
this.editor.setOption('mode', MARKDOWN_CONFIG);
|
||||
@ -79,11 +80,17 @@ export default class ChatEditor extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ message: '' });
|
||||
this.props.submit(editorMessage);
|
||||
this.editor.setValue('');
|
||||
}
|
||||
|
||||
messageChange(editor, data, value) {
|
||||
if (this.state.message !== '' && value == '') {
|
||||
this.setState({
|
||||
message: value
|
||||
});
|
||||
}
|
||||
if (value == this.props.message || value == '' || value == ' ') {
|
||||
return;
|
||||
}
|
||||
@ -123,7 +130,7 @@ export default class ChatEditor extends Component {
|
||||
onChange={(e, d, v) => this.messageChange(e, d, v)}
|
||||
editorDidMount={(editor) => {
|
||||
this.editor = editor;
|
||||
if (BROWSER_REGEX.test(navigator.userAgent)) {
|
||||
if (!(BROWSER_REGEX.test(navigator.userAgent))) {
|
||||
editor.focus();
|
||||
}
|
||||
}}
|
@ -0,0 +1,58 @@
|
||||
import React, { Component, Fragment } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { ChatTabBar } from "./chat-tabbar";
|
||||
import { SidebarSwitcher } from "~/views/components/SidebarSwitch";
|
||||
import { deSig } from "~/logic/lib/util";
|
||||
|
||||
|
||||
export const ChatHeader = (props) => {
|
||||
const isInPopout = props.popout ? "popout/" : "";
|
||||
const group = Array.from(props.group.members);
|
||||
let title = props.station.substr(1);
|
||||
if (props.association &&
|
||||
"metadata" in props.association &&
|
||||
props.association.metadata.tile !== "") {
|
||||
title = props.association.metadata.title
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: "1rem" }}>
|
||||
<Link to="/~chat/">{"⟵ All Chats"}</Link>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
"pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative " +
|
||||
"overflow-x-auto overflow-y-hidden flex-shrink-0 "
|
||||
}
|
||||
style={{ height: 48 }}>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
popout={props.popout}
|
||||
api={props.api}
|
||||
/>
|
||||
<Link
|
||||
to={"/~chat/" + isInPopout + "room" + props.station}
|
||||
className="pt2 white-d">
|
||||
<h2
|
||||
className={
|
||||
"dib f9 fw4 lh-solid v-top " +
|
||||
(title === props.station.substr(1) ? "mono" : "")
|
||||
}
|
||||
style={{ width: "max-content" }}>
|
||||
{title}
|
||||
</h2>
|
||||
</Link>
|
||||
<ChatTabBar
|
||||
location={props.location}
|
||||
station={props.station}
|
||||
isOwner={deSig(props.match.params.ship) === window.ship}
|
||||
popout={props.popout}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Sigil } from '../../../../lib/sigil';
|
||||
import ChatEditor from './chat-editor';
|
||||
import { S3Upload } from './s3-upload';
|
||||
import { uxToHex } from '../../../../lib/util';
|
||||
import { S3Upload } from './s3-upload'
|
||||
;
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
|
||||
|
||||
const URL_REGEX = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source));
|
||||
@ -10,7 +11,7 @@ const URL_REGEX = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\
|
||||
export class ChatInput extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
||||
this.state = {
|
||||
inCodeMode: false,
|
||||
};
|
||||
@ -94,8 +95,10 @@ export class ChatInput extends Component {
|
||||
let message = [];
|
||||
let isInCodeBlock = false;
|
||||
let endOfCodeBlock = false;
|
||||
text.split(/\r?\n/).forEach((line) => {
|
||||
message.push('\n');
|
||||
text.split(/\r?\n/).forEach((line, index) => {
|
||||
if (index !== 0) {
|
||||
message.push('\n');
|
||||
}
|
||||
// A line of backticks enters and exits a codeblock
|
||||
if (line.startsWith('```')) {
|
||||
// But we need to check if we've ended a codeblock
|
||||
@ -141,6 +144,8 @@ export class ChatInput extends Component {
|
||||
messages.push(message);
|
||||
}
|
||||
|
||||
props.deleteMessage();
|
||||
|
||||
messages.forEach((message) => {
|
||||
if (message.length > 0) {
|
||||
message = this.getLetterType(message.join(' '));
|
||||
@ -171,27 +176,6 @@ export class ChatInput extends Component {
|
||||
};
|
||||
this.closure = closure.bind(this);
|
||||
setTimeout(this.closure, 2000);*/
|
||||
|
||||
this.editor.setValue('');
|
||||
}
|
||||
|
||||
toggleCode() {
|
||||
if(this.state.code) {
|
||||
this.setState({ code: false });
|
||||
this.editor.setOption('mode', MARKDOWN_CONFIG);
|
||||
this.editor.setOption('placeholder', this.props.placeholder);
|
||||
} else {
|
||||
this.setState({ code: true });
|
||||
this.editor.setOption('mode', null);
|
||||
this.editor.setOption('placeholder', 'Code...');
|
||||
}
|
||||
const value = this.editor.getValue();
|
||||
|
||||
// Force redraw of placeholder
|
||||
if(value.length === 0) {
|
||||
this.editor.setValue(' ');
|
||||
this.editor.setValue('');
|
||||
}
|
||||
}
|
||||
|
||||
uploadSuccess(url) {
|
@ -1,6 +1,6 @@
|
||||
import React, { Component, Fragment } from "react";
|
||||
|
||||
import { scrollIsAtTop, scrollIsAtBottom } from "../../../../lib/util";
|
||||
import { scrollIsAtTop, scrollIsAtBottom } from "~/logic/lib/util";
|
||||
|
||||
// Restore chat position on FF when new messages come in
|
||||
const recalculateScrollTop = (lastScrollHeight, scrollContainer) => {
|
@ -0,0 +1,41 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export const ChatTabBar = (props) => {
|
||||
const {
|
||||
location,
|
||||
station
|
||||
} = props;
|
||||
let setColor = '', popout = '';
|
||||
|
||||
if (location.pathname.includes('/settings')) {
|
||||
setColor = 'black white-d';
|
||||
} else {
|
||||
setColor = 'gray3';
|
||||
}
|
||||
|
||||
const hidePopoutIcon = (popout)
|
||||
? 'dn-m dn-l dn-xl' : 'dib-m dib-l dib-xl';
|
||||
|
||||
return (
|
||||
<div className="dib flex-shrink-0 flex-grow-1">
|
||||
<div className={'dib pt2 f9 pl6 pr6 lh-solid'}>
|
||||
<Link
|
||||
className={'no-underline ' + setColor}
|
||||
to={'/~chat/' + popout + 'settings' + station}>
|
||||
Settings
|
||||
</Link>
|
||||
</div>
|
||||
<a href={'/~chat/popout/room' + station} rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="dib fr pr1"
|
||||
style={{ paddingTop: '8px' }}>
|
||||
<img
|
||||
className={'flex-shrink-0 pr3 dn ' + hidePopoutIcon}
|
||||
src="/~chat/img/popout.png"
|
||||
height="16"
|
||||
width="16" />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -99,6 +99,7 @@ export class ChatWindow extends Component {
|
||||
scrollIsAtBottom() {
|
||||
if (this.state.numPages !== 1) {
|
||||
this.setState({ numPages: 1 });
|
||||
this.dismissUnread();
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,9 +172,12 @@ export class ChatWindow extends Component {
|
||||
/>
|
||||
{ messages.map((msg, i) => (
|
||||
<ChatMessage
|
||||
key={msg.uid}
|
||||
unreadRef={this.unreadReference}
|
||||
isLastUnread={
|
||||
props.unreadCount > 0 && i === props.unreadCount - 1
|
||||
props.unreadCount > 0 &&
|
||||
i === props.unreadCount - 1 &&
|
||||
state.numPages !== 1
|
||||
}
|
||||
msg={msg}
|
||||
previousMsg={messages[i - 1]}
|
@ -1,8 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Button } from '@tlon/indigo-react';
|
||||
|
||||
|
||||
const IMAGE_REGEX =
|
||||
/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF|GIF|webp|WEBP|webm|WEBM|svg|SVG)$/;
|
||||
const IMAGE_REGEX = new RegExp(/(jpg|img|png|gif|tiff|jpeg|webp|webm|svg)$/i);
|
||||
|
||||
const YOUTUBE_REGEX =
|
||||
new RegExp(
|
||||
@ -25,8 +24,7 @@ export default class UrlContent extends Component {
|
||||
let unfoldState = this.state.unfold;
|
||||
unfoldState = !unfoldState;
|
||||
this.setState({ unfold: unfoldState });
|
||||
const iframe = this.refs.iframe;
|
||||
iframe.setAttribute('src', iframe.getAttribute('data-src'));
|
||||
this.iframe.setAttribute('src', this.iframe.dataset.src);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -42,13 +40,12 @@ export default class UrlContent extends Component {
|
||||
className="o-80-d"
|
||||
src={content.url}
|
||||
style={{
|
||||
width: '50%',
|
||||
maxWidth: '250px'
|
||||
maxWidth: '18rem'
|
||||
}}
|
||||
></img>
|
||||
);
|
||||
return (
|
||||
<a className={`f7 lh-copy v-top word-break-all`}
|
||||
<a className='f7 lh-copy v-top word-break-all'
|
||||
href={content.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@ -63,7 +60,9 @@ export default class UrlContent extends Component {
|
||||
? 'db' : 'dn')}
|
||||
>
|
||||
<iframe
|
||||
ref="iframe"
|
||||
ref={(el) => {
|
||||
this.iframe = el;
|
||||
}}
|
||||
width="560"
|
||||
height="315"
|
||||
data-src={`https://www.youtube.com/embed/${ytMatch[1]}`}
|
||||
@ -75,20 +74,26 @@ export default class UrlContent extends Component {
|
||||
return (
|
||||
<div>
|
||||
<a href={content.url}
|
||||
className={`f7 lh-copy v-top bb b--white-d word-break-all`}
|
||||
href={content.url}
|
||||
className='f7 lh-copy v-top bb b--white-d word-break-all'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">{content.url}</a>
|
||||
<a className="ml2 f7 pointer lh-copy v-top"
|
||||
onClick={e => this.unfoldEmbed()}>
|
||||
[embed]
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{content.url}
|
||||
</a>
|
||||
<Button
|
||||
border={1}
|
||||
style={{ display: 'inline-flex', height: '1.66em' }} // Height is hacked to line-height until Button supports proper size
|
||||
ml={1}
|
||||
onClick={e => this.unfoldEmbed()}
|
||||
>
|
||||
{this.state.unfold ? 'collapse' : 'embed'}
|
||||
</Button>
|
||||
{contents}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<a className={`f7 lh-copy v-top bb b--white-d b--black word-break-all`}
|
||||
<a className='f7 lh-copy v-top bb b--white-d b--black word-break-all'
|
||||
href={content.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
@ -0,0 +1,51 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
|
||||
export const DeleteButton = (props) => {
|
||||
const { isOwner, station, changeLoading, api } = props;
|
||||
const leaveButtonClasses = (!isOwner) ? 'pointer' : 'c-default';
|
||||
const deleteButtonClasses = (isOwner) ?
|
||||
'b--red2 red2 pointer bg-gray0-d' :
|
||||
'b--gray3 gray3 bg-gray0-d c-default';
|
||||
|
||||
const deleteChat = () => {
|
||||
changeLoading(
|
||||
true,
|
||||
true,
|
||||
isOwner ? 'Deleting chat...' : 'Leaving chat...',
|
||||
() => {
|
||||
api.chat.delete(station);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-100 cf">
|
||||
<div className={'w-100 fl mt3 ' + ((isOwner) ? 'o-30' : '')}>
|
||||
<p className="f8 mt3 lh-copy db">Leave Chat</p>
|
||||
<p className="f9 gray2 db mb4">
|
||||
Remove this chat from your chat list.{' '}
|
||||
You will need to request for access again.
|
||||
</p>
|
||||
<a onClick={(!isOwner) ? deleteChat : null}
|
||||
className={
|
||||
'dib f9 black gray4-d bg-gray0-d ba pa2 b--black b--gray1-d ' +
|
||||
leaveButtonClasses
|
||||
}>
|
||||
Leave this chat
|
||||
</a>
|
||||
</div>
|
||||
<div className={'w-100 fl mt3 ' + ((!isOwner) ? 'o-30' : '')}>
|
||||
<p className="f8 mt3 lh-copy db">Delete Chat</p>
|
||||
<p className="f9 gray2 db mb4">
|
||||
Permanently delete this chat.{' '}
|
||||
All current members will no longer see this chat.
|
||||
</p>
|
||||
<a onClick={(isOwner) ? deleteChat : null}
|
||||
className={'dib f9 ba pa2 ' + deleteButtonClasses}
|
||||
>Delete this chat</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,104 @@
|
||||
import React, { Component } from 'react';
|
||||
import Toggle from '~/views/components/toggle';
|
||||
import { InviteSearch } from '~/views/components/InviteSearch';
|
||||
|
||||
|
||||
export class GroupifyButton extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
inclusive: false,
|
||||
targetGroup: null
|
||||
};
|
||||
}
|
||||
|
||||
changeTargetGroup(target) {
|
||||
if (target.groups.length === 1) {
|
||||
this.setState({ targetGroup: target.groups[0] });
|
||||
} else {
|
||||
this.setState({ targetGroup: null });
|
||||
}
|
||||
}
|
||||
|
||||
changeInclusive(event) {
|
||||
this.setState({ inclusive: Boolean(event.target.checked) });
|
||||
}
|
||||
|
||||
renderInclusiveToggle() {
|
||||
return this.state.targetGroup ? (
|
||||
<div className="mt4">
|
||||
<Toggle
|
||||
boolean={inclusive}
|
||||
change={this.changeInclusive.bind(this)}
|
||||
/>
|
||||
<span className="dib f9 white-d inter ml3">
|
||||
Add all members to group
|
||||
</span>
|
||||
<p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
|
||||
Add chat members to the group if they aren't in it yet
|
||||
</p>
|
||||
</div>
|
||||
) : <div />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { inclusive, targetGroup } = this.state;
|
||||
const {
|
||||
api,
|
||||
isOwner,
|
||||
association,
|
||||
associations,
|
||||
contacts,
|
||||
groups,
|
||||
station
|
||||
} = this.props;
|
||||
|
||||
const groupPath = association['group-path'];
|
||||
const ownedUnmanagedVillage =
|
||||
isOwner &&
|
||||
!contacts[groupPath];
|
||||
|
||||
if (!ownedUnmanagedVillage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'w-100 fl mt3'} style={{ maxWidth: '29rem' }}>
|
||||
<p className="f8 mt3 lh-copy db">Convert Chat</p>
|
||||
<p className="f9 gray2 db mb4">
|
||||
Convert this chat into a group with associated chat, or select a
|
||||
group to add this chat to.
|
||||
</p>
|
||||
<InviteSearch
|
||||
groups={groups}
|
||||
contacts={contacts}
|
||||
associations={associations}
|
||||
groupResults={true}
|
||||
shipResults={false}
|
||||
invites={{
|
||||
groups: targetGroup ? [targetGroup] : [],
|
||||
ships: []
|
||||
}}
|
||||
setInvite={this.changeTargetGroup.bind(this)}
|
||||
/>
|
||||
{this.renderInclusiveToggle()}
|
||||
<a onClick={() => {
|
||||
changeLoading(true, true, 'Converting to group...', () => {
|
||||
api.chat.groupify(
|
||||
station, targetGroup, inclusive
|
||||
).then(() => {
|
||||
changeLoading(false, false, '', () => {});
|
||||
});
|
||||
});
|
||||
}}
|
||||
className={
|
||||
'dib f9 black gray4-d bg-gray0-d ba pa2 mt4 b--black ' +
|
||||
'b--gray1-d pointer'
|
||||
}>Convert to group</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { InviteSearch } from '../../../../components/InviteSearch';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { InviteSearch } from '~/views/components/InviteSearch';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
|
||||
export class InviteElement extends Component {
|
||||
constructor(props) {
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { OverlaySigil } from './overlay-sigil';
|
||||
import MessageContent from './message-content';
|
||||
import { uxToHex, cite, writeText } from '../../../../lib/util';
|
||||
import { uxToHex, cite, writeText } from '~/logic/lib/util';
|
||||
import moment from 'moment';
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
|
||||
|
||||
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 (
|
||||
<div className={'cf w-100 mb3 ' + ((props.isDisabled) ? 'o-30' : '')}>
|
||||
<p className="f8 lh-copy">Change color</p>
|
||||
<p className="f9 gray2 db mb4">Give this chat a color when viewing group channels</p>
|
||||
<div className="relative w-100 flex"
|
||||
style={{ maxWidth: '10rem' }}
|
||||
>
|
||||
<div className="absolute"
|
||||
style={{
|
||||
height: 16,
|
||||
width: 16,
|
||||
backgroundColor: state.color,
|
||||
top: 13,
|
||||
left: 11
|
||||
}} />
|
||||
<input
|
||||
className={'pl7 f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={state.color}
|
||||
disabled={props.isDisabled}
|
||||
onChange={this.changeColor}
|
||||
onBlur={this.submitColor} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import React, { Component } from '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 (
|
||||
<div className={'w-100 mb3 fl ' + ((isDisabled) ? 'o-30' : '')}>
|
||||
<p className="f8 lh-copy">{title}</p>
|
||||
<p className="f9 gray2 db mb4">{description}</p>
|
||||
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
|
||||
<input
|
||||
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
type="text"
|
||||
value={this.state.value}
|
||||
disabled={isDisabled}
|
||||
onChange={(e) => {
|
||||
this.setState({ value: e.target.value });
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!isDisabled) {
|
||||
setValue(this.state.value || '');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { MetadataColor } from './metadata-color';
|
||||
import { MetadataInput } from './metadata-input';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
|
||||
|
||||
export const MetadataSettings = (props) => {
|
||||
const {
|
||||
isOwner,
|
||||
association,
|
||||
changeLoading,
|
||||
api,
|
||||
station
|
||||
} = 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 (
|
||||
<div className="cf mt6">
|
||||
<MetadataInput
|
||||
title='Rename'
|
||||
description='Change the name of this chat'
|
||||
isDisabled={!isOwner}
|
||||
initialValue={title}
|
||||
setValue={(val) => {
|
||||
changeLoading(false, true, 'Editing chat...', () => {
|
||||
api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
val,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
uxToHex(association.metadata.color)
|
||||
).then(() => {
|
||||
changeLoading(false, false, '', () => {});
|
||||
});
|
||||
});
|
||||
}} />
|
||||
<MetadataInput
|
||||
title='Change description'
|
||||
description='Change the description of this chat'
|
||||
isDisabled={!isOwner}
|
||||
initialValue={description}
|
||||
setValue={(val) => {
|
||||
changeLoading(false, true, 'Editing chat...', () => {
|
||||
api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
val,
|
||||
association.metadata['date-created'],
|
||||
uxToHex(association.metadata.color)
|
||||
).then(() => {
|
||||
changeLoading(false, false, '', () => {});
|
||||
});
|
||||
});
|
||||
}} />
|
||||
<MetadataColor
|
||||
initialValue={color}
|
||||
isDisabled={!isOwner}
|
||||
setValue={(val) => {
|
||||
changeLoading(false, true, 'Editing chat...', () => {
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
val
|
||||
).then(() => {
|
||||
changeLoading(false, false, '', () => {});
|
||||
});
|
||||
});
|
||||
}} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Sigil } from '../../../../lib/sigil';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import {
|
||||
ProfileOverlay,
|
||||
OVERLAY_HEIGHT
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { cite } from '../../../../lib/util';
|
||||
import { Sigil } from '../../../../lib/sigil';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
|
||||
export const OVERLAY_HEIGHT = 250;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import S3Client from '../../../../lib/s3';
|
||||
import S3Client from '~/logic/lib/s3';
|
||||
|
||||
export class S3Upload extends Component {
|
||||
constructor(props) {
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { InviteSearch } from '../../../components/InviteSearch';
|
||||
import { InviteSearch } from '~/views/components/InviteSearch';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { deSig } from '../../../lib/util';
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
|
||||
export class NewDmScreen extends Component {
|
||||
constructor(props) {
|
@ -1,8 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import { InviteSearch } from '../../../components/InviteSearch';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import { InviteSearch } from '~/views/components/InviteSearch';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { deSig } from '../../../lib/util';
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
|
||||
export class NewScreen extends Component {
|
||||
constructor(props) {
|
145
pkg/interface/src/views/apps/chat/components/settings.js
Normal file
145
pkg/interface/src/views/apps/chat/components/settings.js
Normal file
@ -0,0 +1,145 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { ChatHeader } from './lib/chat-header';
|
||||
import { MetadataSettings } from './lib/metadata-settings';
|
||||
import { DeleteButton } from './lib/delete-button';
|
||||
import { GroupifyButton } from './lib/groupify-button';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { ChatTabBar } from './lib/chat-tabbar';
|
||||
import SidebarSwitcher from '~/views/components/SidebarSwitch';
|
||||
|
||||
|
||||
export class SettingsScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
awaiting: false,
|
||||
type: 'Editing chat...'
|
||||
};
|
||||
|
||||
this.changeLoading = this.changeLoading.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.state.isLoading && (this.props.station in this.props.inbox)) {
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props, state } = this;
|
||||
if (state.isLoading && !(props.station in props.inbox)) {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
}, () => {
|
||||
props.history.push('/~chat');
|
||||
});
|
||||
} else if (state.isLoading && (props.station in props.inbox)) {
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
changeLoading(isLoading, awaiting, type, closure) {
|
||||
this.setState({
|
||||
isLoading,
|
||||
awaiting,
|
||||
type
|
||||
}, closure);
|
||||
}
|
||||
|
||||
renderLoading() {
|
||||
return (
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes="absolute right-2 bottom-2 ba pa2 b--gray1-d"
|
||||
text={this.state.type}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderNormal() {
|
||||
const { state } = this;
|
||||
const {
|
||||
associations,
|
||||
association,
|
||||
contacts,
|
||||
groups,
|
||||
api,
|
||||
station,
|
||||
match
|
||||
} = this.props;
|
||||
const isOwner = deSig(match.params.ship) === window.ship;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h2 className="f8 pb2">Chat Settings</h2>
|
||||
<GroupifyButton
|
||||
isOwner={isOwner}
|
||||
association={association}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
groups={groups}
|
||||
api={api}
|
||||
changeLoading={this.changeLoading} />
|
||||
<DeleteButton
|
||||
isOwner={isOwner}
|
||||
changeLoading={this.changeLoading}
|
||||
station={station}
|
||||
api={api} />
|
||||
<MetadataSettings
|
||||
isOwner={isOwner}
|
||||
changeLoading={this.changeLoading}
|
||||
api={api}
|
||||
association={association}
|
||||
station={station} />
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes="absolute right-2 bottom-2 ba pa2 b--gray1-d"
|
||||
text={this.state.type}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
const {
|
||||
api,
|
||||
group,
|
||||
association,
|
||||
station,
|
||||
popout,
|
||||
sidebarShown,
|
||||
match,
|
||||
location
|
||||
} = this.props;
|
||||
|
||||
const isInPopout = popout ? "popout/" : "";
|
||||
const title =
|
||||
( association &&
|
||||
('metadata' in association) &&
|
||||
(association.metadata.title !== '')
|
||||
) ? association.metadata.title : station.substr(1);
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column white-d">
|
||||
<ChatHeader
|
||||
match={match}
|
||||
location={location}
|
||||
api={api}
|
||||
group={group}
|
||||
association={association}
|
||||
station={station}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout} />
|
||||
<div className="w-100 pl3 mt4 cf">
|
||||
{(state.isLoading) ? this.renderLoading() : this.renderNormal() }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Welcome from './lib/welcome';
|
||||
import { alphabetiseAssociations } from '../../../lib/util';
|
||||
import SidebarInvite from '../../../components/SidebarInvite';
|
||||
import { alphabetiseAssociations } from '~/logic/lib/util';
|
||||
import SidebarInvite from '~/views/components/SidebarInvite';
|
||||
import { GroupItem } from './lib/group-item';
|
||||
|
||||
export class Sidebar extends Component {
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ErrorBoundary from '../../../components/ErrorBoundary';
|
||||
import ErrorBoundary from '~/views/components/ErrorBoundary';
|
||||
|
||||
export class Skeleton extends Component {
|
||||
render() {
|
@ -30,7 +30,7 @@ export default class DojoApp extends Component {
|
||||
componentDidMount() {
|
||||
document.title = 'OS1 - Dojo';
|
||||
|
||||
const channel = new this.props.channel();
|
||||
const channel = new window.channel();
|
||||
this.api = new Api(this.props.ship, channel);
|
||||
this.store.api = this.api;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { cite } from '../../../lib/util';
|
||||
import { Spinner } from '../../../components/Spinner';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
|
||||
export class Input extends Component {
|
||||
constructor(props) {
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user