mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-05 13:55:54 +03:00
chat-js: update to global store
This commit is contained in:
parent
85aa12a5a9
commit
535f415ebd
@ -85,8 +85,8 @@ export default class App extends React.Component {
|
||||
render={ p => (
|
||||
<LaunchApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -94,8 +94,9 @@ export default class App extends React.Component {
|
||||
<Route path="/~chat" render={ p => (
|
||||
<ChatApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -105,6 +106,7 @@ export default class App extends React.Component {
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
subscription={this.subscription}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -112,8 +114,9 @@ export default class App extends React.Component {
|
||||
<Route path="/~groups" render={ p => (
|
||||
<GroupsApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
@ -132,8 +135,9 @@ export default class App extends React.Component {
|
||||
<Route path="/~publish" render={ p => (
|
||||
<PublishApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
{...state}
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,68 +1,62 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import React from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
|
||||
import ChatApi from '../../api/chat';
|
||||
import ChatStore from '../../store/chat';
|
||||
import ChatSubscription from '../../subscription/chat';
|
||||
import "./css/custom.css";
|
||||
|
||||
import { Skeleton } from "./components/skeleton";
|
||||
import { Sidebar } from "./components/sidebar";
|
||||
import { ChatScreen } from "./components/chat";
|
||||
import { MemberScreen } from "./components/member";
|
||||
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 './css/custom.css';
|
||||
type ChatAppProps = StoreState & {
|
||||
ship: PatpNoSig;
|
||||
api: GlobalApi;
|
||||
subscription: GlobalSubscription;
|
||||
};
|
||||
|
||||
import { Skeleton } from './components/skeleton';
|
||||
import { Sidebar } from './components/sidebar';
|
||||
import { ChatScreen } from './components/chat';
|
||||
import { MemberScreen } from './components/member';
|
||||
import { SettingsScreen } from './components/settings';
|
||||
import { NewScreen } from './components/new';
|
||||
import { JoinScreen } from './components/join';
|
||||
import { NewDmScreen } from './components/new-dm';
|
||||
export default class ChatApp extends React.Component<ChatAppProps, {}> {
|
||||
totalUnreads = 0;
|
||||
|
||||
export default class ChatApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new ChatStore();
|
||||
this.state = this.store.state;
|
||||
this.totalUnreads = 0;
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
resetControllers() {
|
||||
this.api = null;
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = 'OS1 - Chat';
|
||||
document.title = "OS1 - Chat";
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
new Image().src = "/~landscape/img/Spinner.png";
|
||||
|
||||
this.props.subscription.startApp('chat');
|
||||
|
||||
this.store.setStateHandler(this.setState.bind(this));
|
||||
const channel = new this.props.channel();
|
||||
this.api = new ChatApi(this.props.ship, channel, this.store);
|
||||
|
||||
this.subscription = new ChatSubscription(this.store, this.api, channel);
|
||||
this.subscription.start();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.delete();
|
||||
this.store.clear();
|
||||
this.store.setStateHandler(() => {});
|
||||
this.resetControllers();
|
||||
this.props.subscription.stopApp('chat');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, props } = this;
|
||||
const { props } = this;
|
||||
|
||||
const messagePreviews = {};
|
||||
const unreads = {};
|
||||
let totalUnreads = 0;
|
||||
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
|
||||
const associations = state.associations ? state.associations : { chat: {}, contacts: {} };
|
||||
const associations = props.associations
|
||||
? props.associations
|
||||
: { chat: {}, contacts: {} };
|
||||
|
||||
Object.keys(state.inbox).forEach((stat) => {
|
||||
const envelopes = state.inbox[stat].envelopes;
|
||||
Object.keys(props.inbox).forEach((stat) => {
|
||||
const envelopes = props.inbox[stat].envelopes;
|
||||
|
||||
if (envelopes.length === 0) {
|
||||
messagePreviews[stat] = false;
|
||||
@ -70,42 +64,61 @@ export default class ChatApp extends React.Component {
|
||||
messagePreviews[stat] = envelopes[0];
|
||||
}
|
||||
|
||||
const unread = Math.max(state.inbox[stat].config.length - state.inbox[stat].config.read, 0);
|
||||
const unread = Math.max(
|
||||
props.inbox[stat].config.length - props.inbox[stat].config.read,
|
||||
0
|
||||
);
|
||||
unreads[stat] = Boolean(unread);
|
||||
if (unread &&
|
||||
(selectedGroups.length === 0 || selectedGroups.map(((e) => {
|
||||
return e[0];
|
||||
})).includes(associations.chat?.[stat]?.['group-path']) ||
|
||||
associations.chat?.[stat]?.['group-path'].startsWith('/~/'))) {
|
||||
totalUnreads += unread;
|
||||
if (
|
||||
unread &&
|
||||
(selectedGroups.length === 0 ||
|
||||
selectedGroups
|
||||
.map((e) => {
|
||||
return e[0];
|
||||
})
|
||||
.includes(associations.chat?.[stat]?.["group-path"]) ||
|
||||
associations.chat?.[stat]?.["group-path"].startsWith("/~/"))
|
||||
) {
|
||||
totalUnreads += unread;
|
||||
}
|
||||
});
|
||||
|
||||
if (totalUnreads !== this.totalUnreads) {
|
||||
document.title = totalUnreads > 0 ? `OS1 - Chat (${totalUnreads})` : 'OS1 - Chat';
|
||||
document.title =
|
||||
totalUnreads > 0 ? `OS1 - Chat (${totalUnreads})` : "OS1 - Chat";
|
||||
this.totalUnreads = totalUnreads;
|
||||
}
|
||||
|
||||
const invites = state.invites ? state.invites : { '/chat': {}, '/contacts': {} };
|
||||
|
||||
const contacts = state.contacts ? state.contacts : {};
|
||||
const s3 = state.s3 ? state.s3 : {};
|
||||
const {
|
||||
invites,
|
||||
s3,
|
||||
sidebarShown,
|
||||
inbox,
|
||||
contacts,
|
||||
permissions,
|
||||
chatSynced,
|
||||
api,
|
||||
chatInitialized,
|
||||
pendingMessages
|
||||
} = props;
|
||||
|
||||
const renderChannelSidebar = (props, station) => (
|
||||
const renderChannelSidebar = (props, station?) => (
|
||||
<Sidebar
|
||||
inbox={state.inbox}
|
||||
inbox={inbox}
|
||||
messagePreviews={messagePreviews}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
invites={invites['/chat'] || {}}
|
||||
invites={invites["/chat"] || {}}
|
||||
unreads={unreads}
|
||||
api={this.api}
|
||||
api={api}
|
||||
station={station}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
@ -117,14 +130,14 @@ export default class ChatApp extends React.Component {
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
chatHideonMobile={true}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
>
|
||||
<div className="h-100 w-100 overflow-x-hidden flex flex-column bg-white bg-gray0-d">
|
||||
<div className="pl3 pr3 pt2 dt pb3 w-100 h-100">
|
||||
<p className="f8 pt3 gray2 w-100 h-100 dtc v-mid tc">
|
||||
Select, create, or join a chat to begin.
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
@ -143,15 +156,15 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
>
|
||||
<NewDmScreen
|
||||
api={this.api}
|
||||
inbox={state.inbox || {}}
|
||||
permissions={state.permissions || {}}
|
||||
contacts={state.contacts || {}}
|
||||
api={api}
|
||||
inbox={inbox}
|
||||
permissions={permissions || {}}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
chatSynced={state.chatSynced || {}}
|
||||
chatSynced={chatSynced || {}}
|
||||
autoCreate={ship}
|
||||
{...props}
|
||||
/>
|
||||
@ -169,15 +182,15 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
>
|
||||
<NewScreen
|
||||
api={this.api}
|
||||
inbox={state.inbox || {}}
|
||||
permissions={state.permissions || {}}
|
||||
contacts={state.contacts || {}}
|
||||
api={api}
|
||||
inbox={inbox || {}}
|
||||
permissions={permissions || {}}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
chatSynced={state.chatSynced || {}}
|
||||
chatSynced={chatSynced || {}}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -188,11 +201,10 @@ export default class ChatApp extends React.Component {
|
||||
exact
|
||||
path="/~chat/join/(~)?/:ship?/:station?"
|
||||
render={(props) => {
|
||||
let station =
|
||||
`/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes("/~/");
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
station = "/~" + station;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -201,13 +213,13 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
>
|
||||
<JoinScreen
|
||||
api={this.api}
|
||||
inbox={state.inbox}
|
||||
api={api}
|
||||
inbox={inbox}
|
||||
autoJoin={station}
|
||||
chatSynced={state.chatSynced || {}}
|
||||
chatSynced={chatSynced || {}}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -218,40 +230,41 @@ export default class ChatApp extends React.Component {
|
||||
exact
|
||||
path="/~chat/(popout)?/room/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station =
|
||||
`/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes("/~/");
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
station = "/~" + station;
|
||||
}
|
||||
const mailbox = state.inbox[station] || {
|
||||
const mailbox = inbox[station] || {
|
||||
config: {
|
||||
read: 0,
|
||||
length: 0
|
||||
length: 0,
|
||||
},
|
||||
envelopes: []
|
||||
envelopes: [],
|
||||
};
|
||||
|
||||
let roomContacts = {};
|
||||
const associatedGroup =
|
||||
station in associations['chat'] &&
|
||||
'group-path' in associations.chat[station]
|
||||
? associations.chat[station]['group-path']
|
||||
: '';
|
||||
station in associations["chat"] &&
|
||||
"group-path" in associations.chat[station]
|
||||
? associations.chat[station]["group-path"]
|
||||
: "";
|
||||
|
||||
if ((associations.chat[station]) && (associatedGroup in contacts)) {
|
||||
if (associations.chat[station] && associatedGroup in contacts) {
|
||||
roomContacts = contacts[associatedGroup];
|
||||
}
|
||||
|
||||
const association =
|
||||
station in associations['chat'] ? associations.chat[station] : {};
|
||||
station in associations["chat"] ? associations.chat[station] : {};
|
||||
|
||||
const permission =
|
||||
station in state.permissions ? state.permissions[station] : {
|
||||
who: new Set([]),
|
||||
kind: 'white'
|
||||
};
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
station in permissions
|
||||
? permissions[station]
|
||||
: {
|
||||
who: new Set([]),
|
||||
kind: "white",
|
||||
};
|
||||
const popout = props.match.url.includes("/popout/");
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
@ -259,26 +272,25 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<ChatScreen
|
||||
chatSynced={state.chatSynced}
|
||||
chatSynced={chatSynced || {}}
|
||||
station={station}
|
||||
association={association}
|
||||
api={this.api}
|
||||
subscription={this.subscription}
|
||||
api={api}
|
||||
read={mailbox.config.read}
|
||||
length={mailbox.config.length}
|
||||
envelopes={mailbox.envelopes}
|
||||
inbox={state.inbox}
|
||||
inbox={inbox}
|
||||
contacts={roomContacts}
|
||||
permission={permission}
|
||||
pendingMessages={state.pendingMessages}
|
||||
pendingMessages={pendingMessages}
|
||||
s3={s3}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
chatInitialized={state.chatInitialized}
|
||||
sidebarShown={sidebarShown}
|
||||
chatInitialized={chatInitialized}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
@ -290,39 +302,39 @@ export default class ChatApp extends React.Component {
|
||||
path="/~chat/(popout)?/members/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
const sig = props.match.url.includes("/~/");
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
station = "/~" + station;
|
||||
}
|
||||
|
||||
const permission = state.permissions[station] || {
|
||||
kind: '',
|
||||
who: new Set([])
|
||||
const permission = permissions[station] || {
|
||||
kind: "",
|
||||
who: new Set([]),
|
||||
};
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
const popout = props.match.url.includes("/popout/");
|
||||
|
||||
const association =
|
||||
station in associations['chat'] ? associations.chat[station] : {};
|
||||
station in associations["chat"] ? associations.chat[station] : {};
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
popout={popout}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<MemberScreen
|
||||
{...props}
|
||||
api={this.api}
|
||||
api={api}
|
||||
station={station}
|
||||
association={association}
|
||||
permission={permission}
|
||||
contacts={contacts}
|
||||
permissions={state.permissions}
|
||||
permissions={permissions}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
@ -332,22 +344,21 @@ export default class ChatApp extends React.Component {
|
||||
exact
|
||||
path="/~chat/(popout)?/settings/(~)?/:ship/:station+"
|
||||
render={(props) => {
|
||||
let station =
|
||||
`/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes('/~/');
|
||||
let station = `/${props.match.params.ship}/${props.match.params.station}`;
|
||||
const sig = props.match.url.includes("/~/");
|
||||
if (sig) {
|
||||
station = '/~' + station;
|
||||
station = "/~" + station;
|
||||
}
|
||||
|
||||
const popout = props.match.url.includes('/popout/');
|
||||
const popout = props.match.url.includes("/popout/");
|
||||
|
||||
const permission = state.permissions[station] || {
|
||||
kind: '',
|
||||
who: new Set([])
|
||||
const permission = permissions[station] || {
|
||||
kind: "",
|
||||
who: new Set([]),
|
||||
};
|
||||
|
||||
const association =
|
||||
station in associations['chat'] ? associations.chat[station] : {};
|
||||
station in associations["chat"] ? associations.chat[station] : {};
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
@ -355,7 +366,7 @@ export default class ChatApp extends React.Component {
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
>
|
||||
<SettingsScreen
|
||||
@ -363,19 +374,19 @@ export default class ChatApp extends React.Component {
|
||||
station={station}
|
||||
association={association}
|
||||
permission={permission}
|
||||
permissions={state.permissions || {}}
|
||||
contacts={state.contacts || {}}
|
||||
permissions={permissions || {}}
|
||||
contacts={contacts || {}}
|
||||
associations={associations.contacts}
|
||||
api={this.api}
|
||||
inbox={state.inbox}
|
||||
api={api}
|
||||
inbox={inbox}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebarShown={sidebarShown}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
@ -1,19 +1,26 @@
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React, { Component } from "react";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, RouteComponentProps } from "react-router-dom";
|
||||
|
||||
import { ResubscribeElement } from './lib/resubscribe-element';
|
||||
import { BacklogElement } from './lib/backlog-element';
|
||||
import { Message } from './lib/message';
|
||||
import { SidebarSwitcher } from '../../../components/SidebarSwitch';
|
||||
import { ChatTabBar } from './lib/chat-tabbar';
|
||||
import { ChatInput } from './lib/chat-input';
|
||||
import { UnreadNotice } from './lib/unread-notice';
|
||||
import { deSig } from '../../../lib/util';
|
||||
import { ResubscribeElement } from "./lib/resubscribe-element";
|
||||
import { BacklogElement } from "./lib/backlog-element";
|
||||
import { Message } from "./lib/message";
|
||||
import { SidebarSwitcher } from "../../../components/SidebarSwitch";
|
||||
import { ChatTabBar } from "./lib/chat-tabbar";
|
||||
import { ChatInput } from "./lib/chat-input";
|
||||
import { UnreadNotice } from "./lib/unread-notice";
|
||||
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";
|
||||
|
||||
function getNumPending(props) {
|
||||
function getNumPending(props: any) {
|
||||
const result = props.pendingMessages.has(props.station)
|
||||
? props.pendingMessages.get(props.station).length
|
||||
: 0;
|
||||
@ -25,26 +32,32 @@ const DEFAULT_BACKLOG_SIZE = 300;
|
||||
const MAX_BACKLOG_SIZE = 1000;
|
||||
|
||||
function scrollIsAtTop(container) {
|
||||
if ((navigator.userAgent.includes("Safari") &&
|
||||
if (
|
||||
(navigator.userAgent.includes("Safari") &&
|
||||
navigator.userAgent.includes("Chrome")) ||
|
||||
navigator.userAgent.includes("Firefox")
|
||||
navigator.userAgent.includes("Firefox")
|
||||
) {
|
||||
return container.scrollTop === 0;
|
||||
} else if (navigator.userAgent.includes("Safari")) {
|
||||
return container.scrollHeight + Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10;
|
||||
return (
|
||||
container.scrollHeight + Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function scrollIsAtBottom(container) {
|
||||
if ((navigator.userAgent.includes("Safari") &&
|
||||
if (
|
||||
(navigator.userAgent.includes("Safari") &&
|
||||
navigator.userAgent.includes("Chrome")) ||
|
||||
navigator.userAgent.includes("Firefox")
|
||||
navigator.userAgent.includes("Firefox")
|
||||
) {
|
||||
return container.scrollHeight - Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10;
|
||||
return (
|
||||
container.scrollHeight - Math.round(container.scrollTop) <=
|
||||
container.clientHeight + 10
|
||||
);
|
||||
} else if (navigator.userAgent.includes("Safari")) {
|
||||
return container.scrollTop === 0;
|
||||
} else {
|
||||
@ -52,7 +65,50 @@ function scrollIsAtBottom(container) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatScreen extends Component {
|
||||
type IMessage = Envelope & { pending?: boolean };
|
||||
|
||||
type ChatScreenProps = RouteComponentProps<{
|
||||
ship: Patp;
|
||||
station: string;
|
||||
}> & {
|
||||
chatSynced: ChatHookUpdate;
|
||||
station: any;
|
||||
association: Association;
|
||||
api: GlobalApi;
|
||||
read: number;
|
||||
length: number;
|
||||
inbox: Inbox;
|
||||
contacts: Contacts;
|
||||
permission: any;
|
||||
pendingMessages: Map<Path, Envelope[]>;
|
||||
s3: any;
|
||||
popout: boolean;
|
||||
sidebarShown: boolean;
|
||||
chatInitialized: boolean;
|
||||
envelopes: Envelope[];
|
||||
};
|
||||
|
||||
interface ChatScreenState {
|
||||
numPages: number;
|
||||
scrollLocked: boolean;
|
||||
read: number;
|
||||
active: boolean;
|
||||
lastScrollHeight: number | null;
|
||||
}
|
||||
|
||||
export class ChatScreen extends Component<ChatScreenProps, ChatScreenState> {
|
||||
hasAskedForMessages = false;
|
||||
lastNumPending = 0;
|
||||
|
||||
scrollContainer: HTMLElement | null = null;
|
||||
|
||||
unreadMarker = null;
|
||||
scrolledToMarker = false;
|
||||
|
||||
activityTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
scrollElement: HTMLElement | null = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -65,29 +121,22 @@ export class ChatScreen extends Component {
|
||||
lastScrollHeight: null,
|
||||
};
|
||||
|
||||
this.hasAskedForMessages = false;
|
||||
this.lastNumPending = 0;
|
||||
|
||||
this.scrollContainer = null;
|
||||
this.onScroll = this.onScroll.bind(this);
|
||||
|
||||
this.unreadMarker = null;
|
||||
this.scrolledToMarker = false;
|
||||
this.setUnreadMarker = this.setUnreadMarker.bind(this);
|
||||
|
||||
this.activityTimeout = true;
|
||||
this.handleActivity = this.handleActivity.bind(this);
|
||||
this.setInactive = this.setInactive.bind(this);
|
||||
|
||||
moment.updateLocale('en', {
|
||||
moment.updateLocale("en", {
|
||||
calendar: {
|
||||
sameDay: '[Today]',
|
||||
nextDay: '[Tomorrow]',
|
||||
nextWeek: 'dddd',
|
||||
lastDay: '[Yesterday]',
|
||||
lastWeek: '[Last] dddd',
|
||||
sameElse: 'DD/MM/YYYY'
|
||||
}
|
||||
sameDay: "[Today]",
|
||||
nextDay: "[Tomorrow]",
|
||||
nextWeek: "dddd",
|
||||
lastDay: "[Yesterday]",
|
||||
lastWeek: "[Last] dddd",
|
||||
sameElse: "DD/MM/YYYY",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -104,17 +153,17 @@ export class ChatScreen extends Component {
|
||||
document.removeEventListener("mousedown", this.handleActivity, false);
|
||||
document.removeEventListener("keypress", this.handleActivity, false);
|
||||
document.removeEventListener("touchmove", this.handleActivity, false);
|
||||
if(this.activityTimeout) {
|
||||
if (this.activityTimeout) {
|
||||
clearTimeout(this.activityTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
handleActivity() {
|
||||
if(!this.state.active) {
|
||||
if (!this.state.active) {
|
||||
this.setState({ active: true });
|
||||
}
|
||||
|
||||
if(this.activityTimeout) {
|
||||
if (this.activityTimeout) {
|
||||
clearTimeout(this.activityTimeout);
|
||||
}
|
||||
|
||||
@ -139,13 +188,13 @@ export class ChatScreen extends Component {
|
||||
const unreadUnloaded = unread - props.envelopes.length;
|
||||
const excessUnread = unreadUnloaded > MAX_BACKLOG_SIZE;
|
||||
|
||||
if(!excessUnread && unreadUnloaded + 20 > DEFAULT_BACKLOG_SIZE) {
|
||||
if (!excessUnread && unreadUnloaded + 20 > DEFAULT_BACKLOG_SIZE) {
|
||||
this.askForMessages(unreadUnloaded + 20);
|
||||
} else {
|
||||
this.askForMessages(DEFAULT_BACKLOG_SIZE);
|
||||
}
|
||||
|
||||
if(excessUnread || props.read === props.length){
|
||||
if (excessUnread || props.read === props.length) {
|
||||
this.scrolledToMarker = true;
|
||||
this.setState(
|
||||
{
|
||||
@ -156,7 +205,7 @@ export class ChatScreen extends Component {
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.setState({ scrollLocked: true, numPages: Math.ceil(unread/100) });
|
||||
this.setState({ scrollLocked: true, numPages: Math.ceil(unread / 100) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,34 +217,36 @@ export class ChatScreen extends Component {
|
||||
prevProps.match.params.ship !== props.match.params.ship
|
||||
) {
|
||||
this.receivedNewChat();
|
||||
} else if (props.chatInitialized &&
|
||||
!(props.station in props.inbox) &&
|
||||
(Boolean(props.chatSynced) && !(props.station in props.chatSynced))) {
|
||||
props.history.push('/~chat');
|
||||
} else if (
|
||||
props.envelopes.length >= prevProps.envelopes.length + 10
|
||||
props.chatInitialized &&
|
||||
!(props.station in props.inbox) &&
|
||||
Boolean(props.chatSynced) &&
|
||||
!(props.station in props.chatSynced)
|
||||
) {
|
||||
props.history.push("/~chat");
|
||||
} else if (props.envelopes.length >= prevProps.envelopes.length + 10) {
|
||||
this.hasAskedForMessages = false;
|
||||
} else if(props.length !== prevProps.length &&
|
||||
prevProps.length === prevState.read &&
|
||||
state.active
|
||||
} else if (
|
||||
props.length !== prevProps.length &&
|
||||
prevProps.length === prevState.read &&
|
||||
state.active
|
||||
) {
|
||||
this.setState({ read: props.length });
|
||||
this.props.api.chat.read(this.props.station);
|
||||
}
|
||||
|
||||
if(!prevProps.chatInitialized && props.chatInitialized) {
|
||||
if (!prevProps.chatInitialized && props.chatInitialized) {
|
||||
this.receivedNewChat();
|
||||
}
|
||||
|
||||
if (
|
||||
(props.length !== prevProps.length ||
|
||||
props.envelopes.length !== prevProps.envelopes.length ||
|
||||
getNumPending(props) !== this.lastNumPending ||
|
||||
state.numPages !== prevState.numPages)
|
||||
props.length !== prevProps.length ||
|
||||
props.envelopes.length !== prevProps.envelopes.length ||
|
||||
getNumPending(props) !== this.lastNumPending ||
|
||||
state.numPages !== prevState.numPages
|
||||
) {
|
||||
this.scrollToBottom();
|
||||
if(navigator.userAgent.includes("Firefox")) {
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
this.recalculateScrollTop();
|
||||
}
|
||||
|
||||
@ -219,7 +270,7 @@ export class ChatScreen extends Component {
|
||||
if (start > 0) {
|
||||
const end = start + size < props.length ? start + size : props.length;
|
||||
this.hasAskedForMessages = true;
|
||||
props.subscription.fetchMessages(start + 1, end, props.station);
|
||||
props.api.chat.fetchMessages(start + 1, end, props.station);
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,31 +282,31 @@ export class ChatScreen extends Component {
|
||||
|
||||
// Restore chat position on FF when new messages come in
|
||||
recalculateScrollTop() {
|
||||
if(!this.scrollContainer) {
|
||||
const { lastScrollHeight } = this.state;
|
||||
if (!this.scrollContainer || !lastScrollHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lastScrollHeight } = this.state;
|
||||
const target = this.scrollContainer;
|
||||
const newScrollTop = this.scrollContainer.scrollHeight - lastScrollHeight;
|
||||
if(target.scrollTop !== 0 || newScrollTop === target.scrollTop) {
|
||||
if (target.scrollTop !== 0 || newScrollTop === target.scrollTop) {
|
||||
return;
|
||||
}
|
||||
target.scrollTop = target.scrollHeight - lastScrollHeight;
|
||||
}
|
||||
|
||||
onScroll(e) {
|
||||
if(scrollIsAtTop(e.target)) {
|
||||
if (scrollIsAtTop(e.target)) {
|
||||
// Save scroll position for FF
|
||||
if (navigator.userAgent.includes('Firefox')) {
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
this.setState({
|
||||
lastScrollHeight: e.target.scrollHeight
|
||||
lastScrollHeight: e.target.scrollHeight,
|
||||
});
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
numPages: this.state.numPages + 1,
|
||||
scrollLocked: true
|
||||
scrollLocked: true,
|
||||
},
|
||||
() => {
|
||||
this.askForMessages(DEFAULT_BACKLOG_SIZE);
|
||||
@ -265,21 +316,20 @@ export class ChatScreen extends Component {
|
||||
this.dismissUnread();
|
||||
this.setState({
|
||||
numPages: 1,
|
||||
scrollLocked: false
|
||||
scrollLocked: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setUnreadMarker(ref) {
|
||||
if(ref && !this.scrolledToMarker) {
|
||||
if (ref && !this.scrolledToMarker) {
|
||||
this.setState({ scrollLocked: true }, () => {
|
||||
ref.scrollIntoView({ block: 'center' });
|
||||
if(ref.offsetParent &&
|
||||
scrollIsAtBottom(ref.offsetParent)) {
|
||||
ref.scrollIntoView({ block: "center" });
|
||||
if (ref.offsetParent && scrollIsAtBottom(ref.offsetParent)) {
|
||||
this.dismissUnread();
|
||||
this.setState({
|
||||
numPages: 1,
|
||||
scrollLocked: false
|
||||
scrollLocked: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -298,38 +348,32 @@ export class ChatScreen extends Component {
|
||||
|
||||
const { props, state } = this;
|
||||
|
||||
let messages = props.envelopes.slice(0);
|
||||
let messages: IMessage[] = props.envelopes.slice(0);
|
||||
const lastMsgNum = messages.length > 0 ? messages.length : 0;
|
||||
|
||||
if (messages.length > 100 * state.numPages) {
|
||||
messages = messages.slice(0, 100 * state.numPages);
|
||||
}
|
||||
|
||||
const pendingMessages = props.pendingMessages.has(props.station)
|
||||
? props.pendingMessages.get(props.station)
|
||||
: [];
|
||||
|
||||
pendingMessages.map((value) => {
|
||||
return (value.pending = true);
|
||||
});
|
||||
const pendingMessages: IMessage[] = (
|
||||
props.pendingMessages.get(props.station) || []
|
||||
).map((value) => ({ ...value, pending: true }));
|
||||
|
||||
messages = pendingMessages.concat(messages);
|
||||
|
||||
const messageElements = messages.map((msg, i) => {
|
||||
// Render sigil if previous message is not by the same sender
|
||||
const aut = ['author'];
|
||||
const aut = ["author"];
|
||||
const renderSigil =
|
||||
_.get(messages[i + 1], aut) !==
|
||||
_.get(msg, aut, msg.author);
|
||||
_.get(messages[i + 1], aut) !== _.get(msg, aut, msg.author);
|
||||
const paddingTop = renderSigil;
|
||||
const paddingBot =
|
||||
_.get(messages[i - 1], aut) !==
|
||||
_.get(msg, aut, msg.author);
|
||||
_.get(messages[i - 1], aut) !== _.get(msg, aut, msg.author);
|
||||
|
||||
const when = ['when'];
|
||||
const when = ["when"];
|
||||
const dayBreak =
|
||||
moment(_.get(messages[i+1], when)).format('YYYY.MM.DD') !==
|
||||
moment(_.get(messages[i], when)).format('YYYY.MM.DD');
|
||||
moment(_.get(messages[i + 1], when)).format("YYYY.MM.DD") !==
|
||||
moment(_.get(messages[i], when)).format("YYYY.MM.DD");
|
||||
|
||||
const messageElem = (
|
||||
<Message
|
||||
@ -343,33 +387,39 @@ export class ChatScreen extends Component {
|
||||
group={props.association}
|
||||
/>
|
||||
);
|
||||
if(unread > 0 && i === unread - 1) {
|
||||
if (unread > 0 && i === unread - 1) {
|
||||
return (
|
||||
<>
|
||||
{messageElem}
|
||||
<div key={'unreads'+ msg.uid} ref={this.setUnreadMarker} className="mv2 green2 flex items-center f9">
|
||||
<div
|
||||
key={"unreads" + msg.uid}
|
||||
ref={this.setUnreadMarker}
|
||||
className="mv2 green2 flex items-center f9"
|
||||
>
|
||||
<hr className="dn-s ma0 w2 b--green2 bt-0" />
|
||||
<p className="mh4">
|
||||
New messages below
|
||||
</p>
|
||||
<p className="mh4">New messages below</p>
|
||||
<hr className="ma0 flex-grow-1 b--green2 bt-0" />
|
||||
{ dayBreak && (
|
||||
<p className="gray2 mh4">
|
||||
{moment(_.get(messages[i], when)).calendar()}
|
||||
</p>
|
||||
{dayBreak && (
|
||||
<p className="gray2 mh4">
|
||||
{moment(_.get(messages[i], when)).calendar()}
|
||||
</p>
|
||||
)}
|
||||
<hr style={{ width: 'calc(50% - 48px)' }} className="b--green2 ma0 bt-0" />
|
||||
<hr
|
||||
style={{ width: "calc(50% - 48px)" }}
|
||||
className="b--green2 ma0 bt-0"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else if(dayBreak) {
|
||||
} else if (dayBreak) {
|
||||
return (
|
||||
<>
|
||||
{messageElem}
|
||||
<div key={'daybreak' + msg.uid} className="pv3 gray2 b--gray2 flex items-center justify-center f9 ">
|
||||
<p>
|
||||
{moment(_.get(messages[i], when)).calendar()}
|
||||
</p>
|
||||
<div
|
||||
key={"daybreak" + msg.uid}
|
||||
className="pv3 gray2 b--gray2 flex items-center justify-center f9 "
|
||||
>
|
||||
<p>{moment(_.get(messages[i], when)).calendar()}</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -378,47 +428,47 @@ export class ChatScreen extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
if (navigator.userAgent.includes('Firefox')) {
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
return (
|
||||
<div className="relative overflow-y-scroll h-100" onScroll={this.onScroll}
|
||||
ref={(e) => {
|
||||
this.scrollContainer = e;
|
||||
}}
|
||||
<div
|
||||
className="relative overflow-y-scroll h-100"
|
||||
onScroll={this.onScroll}
|
||||
ref={(e) => {
|
||||
this.scrollContainer = e;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="bg-white bg-gray0-d pt3 pb2 flex flex-column-reverse"
|
||||
style={{ resize: 'vertical' }}
|
||||
style={{ resize: "vertical" }}
|
||||
>
|
||||
<div
|
||||
ref={(el) => {
|
||||
this.scrollElement = el;
|
||||
}}
|
||||
></div>
|
||||
{(props.chatInitialized &&
|
||||
!(props.station in props.inbox)) && (
|
||||
<BacklogElement />
|
||||
{props.chatInitialized && !(props.station in props.inbox) && (
|
||||
<BacklogElement />
|
||||
)}
|
||||
{props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
messages.length > 0 ? (
|
||||
<ResubscribeElement
|
||||
api={props.api}
|
||||
host={props.match.params.ship}
|
||||
station={props.station}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
{(
|
||||
props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
(messages.length > 0)
|
||||
) ? (
|
||||
<ResubscribeElement
|
||||
api={props.api}
|
||||
host={props.match.params.ship}
|
||||
station={props.station}
|
||||
/>
|
||||
) : (<div />)
|
||||
}
|
||||
{messageElements}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className="overflow-y-scroll bg-white bg-gray0-d pt3 pb2 flex flex-column-reverse relative"
|
||||
style={{ height: '100%', resize: 'vertical' }}
|
||||
style={{ height: "100%", resize: "vertical" }}
|
||||
onScroll={this.onScroll}
|
||||
>
|
||||
<div
|
||||
@ -426,26 +476,24 @@ ref={(e) => {
|
||||
this.scrollElement = el;
|
||||
}}
|
||||
></div>
|
||||
{(props.chatInitialized &&
|
||||
!(props.station in props.inbox)) && (
|
||||
<BacklogElement />
|
||||
{props.chatInitialized && !(props.station in props.inbox) && (
|
||||
<BacklogElement />
|
||||
)}
|
||||
{props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
messages.length > 0 ? (
|
||||
<ResubscribeElement
|
||||
api={props.api}
|
||||
host={props.match.params.ship}
|
||||
station={props.station}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
{(
|
||||
props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
(messages.length > 0)
|
||||
) ? (
|
||||
<ResubscribeElement
|
||||
api={props.api}
|
||||
host={props.match.params.ship}
|
||||
station={props.station}
|
||||
/>
|
||||
) : (<div />)
|
||||
}
|
||||
{messageElements}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -457,16 +505,16 @@ ref={(e) => {
|
||||
|
||||
const group = Array.from(props.permission.who.values());
|
||||
|
||||
const isinPopout = props.popout ? 'popout/' : '';
|
||||
const isinPopout = props.popout ? "popout/" : "";
|
||||
|
||||
const ownerContact = (window.ship in props.contacts)
|
||||
? props.contacts[window.ship] : false;
|
||||
const ownerContact =
|
||||
window.ship in props.contacts ? props.contacts[window.ship] : false;
|
||||
|
||||
let title = props.station.substr(1);
|
||||
|
||||
if (props.association && 'metadata' in props.association) {
|
||||
if (props.association && "metadata" in props.association) {
|
||||
title =
|
||||
props.association.metadata.title !== ''
|
||||
props.association.metadata.title !== ""
|
||||
? props.association.metadata.title
|
||||
: props.station.substr(1);
|
||||
}
|
||||
@ -475,8 +523,8 @@ ref={(e) => {
|
||||
|
||||
const unreadMsg = unread > 0 && messages[unread - 1];
|
||||
|
||||
|
||||
const showUnreadNotice = props.length !== props.read && props.read === state.read;
|
||||
const showUnreadNotice =
|
||||
props.length !== props.read && props.read === state.read;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -485,14 +533,16 @@ ref={(e) => {
|
||||
>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: '1rem' }}
|
||||
style={{ height: "1rem" }}
|
||||
>
|
||||
<Link to="/~chat/">{'⟵ All Chats'}</Link>
|
||||
<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 '}
|
||||
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
|
||||
@ -500,13 +550,16 @@ ref={(e) => {
|
||||
popout={props.popout}
|
||||
api={props.api}
|
||||
/>
|
||||
<Link to={'/~chat/' + isinPopout + 'room' + props.station}
|
||||
className="pt2 white-d"
|
||||
<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' }}
|
||||
className={
|
||||
"dib f9 fw4 lh-solid v-top " +
|
||||
(title === props.station.substr(1) ? "mono" : "")
|
||||
}
|
||||
style={{ width: "max-content" }}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
@ -520,13 +573,13 @@ ref={(e) => {
|
||||
api={props.api}
|
||||
/>
|
||||
</div>
|
||||
{ !!unreadMsg && showUnreadNotice && (
|
||||
{!!unreadMsg && showUnreadNotice && (
|
||||
<UnreadNotice
|
||||
unread={unread}
|
||||
unreadMsg={unreadMsg}
|
||||
onRead={() => this.dismissUnread()}
|
||||
/>
|
||||
) }
|
||||
)}
|
||||
{this.chatWindow(unread)}
|
||||
<ChatInput
|
||||
api={props.api}
|
@ -45,7 +45,7 @@ export class JoinScreen extends Component {
|
||||
this.setState({
|
||||
station,
|
||||
awaiting: true
|
||||
}, () => props.api.chatView.join(ship, station, true));
|
||||
}, () => props.api.chat.join(ship, station, true));
|
||||
}
|
||||
|
||||
if (state.station in props.inbox ||
|
||||
@ -78,7 +78,7 @@ export class JoinScreen extends Component {
|
||||
station,
|
||||
awaiting: true
|
||||
}, () => {
|
||||
props.api.chatView.join(ship, station, true);
|
||||
props.api.chat.join(ship, station, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
|
||||
export class ResubscribeElement extends Component {
|
||||
onClickResubscribe() {
|
||||
this.props.api.chatHook.addSynced(
|
||||
this.props.api.chat.addSynced(
|
||||
this.props.host,
|
||||
this.props.station,
|
||||
true);
|
||||
|
@ -2,11 +2,11 @@ import React, { Component } from 'react';
|
||||
|
||||
export class SidebarInvite extends Component {
|
||||
onAccept() {
|
||||
this.props.api.invite.accept(this.props.uid);
|
||||
this.props.api.invite.accept('/chat', this.props.uid);
|
||||
}
|
||||
|
||||
onDecline() {
|
||||
this.props.api.invite.decline(this.props.uid);
|
||||
this.props.api.invite.decline('/chat', this.props.uid);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -63,7 +63,7 @@ export class NewDmScreen extends Component {
|
||||
},
|
||||
() => {
|
||||
const groupPath = station;
|
||||
props.api.chatView.create(
|
||||
props.api.chat.create(
|
||||
`~${window.ship} <-> ~${state.ship}`,
|
||||
'',
|
||||
station,
|
||||
|
@ -146,7 +146,7 @@ export class NewScreen extends Component {
|
||||
if (state.groups.length > 0) {
|
||||
groupPath = state.groups[0];
|
||||
}
|
||||
const submit = props.api.chatView.create(
|
||||
const submit = props.api.chat.create(
|
||||
state.title,
|
||||
state.description,
|
||||
appPath,
|
||||
|
@ -108,7 +108,8 @@ export class SettingsScreen extends Component {
|
||||
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.add(
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
@ -133,7 +134,7 @@ export class SettingsScreen extends Component {
|
||||
? 'Deleting chat...'
|
||||
: 'Leaving chat...'
|
||||
}, (() => {
|
||||
props.api.chatView.delete(props.station);
|
||||
props.api.chat.delete(props.station);
|
||||
}));
|
||||
}
|
||||
|
||||
@ -145,7 +146,7 @@ export class SettingsScreen extends Component {
|
||||
awaiting: true,
|
||||
type: 'Converting chat...'
|
||||
}, (() => {
|
||||
props.api.chatView.groupify(
|
||||
props.api.chat.groupify(
|
||||
props.station, state.targetGroup, state.inclusive
|
||||
).then(() => this.setState({ awaiting: false }));
|
||||
}));
|
||||
@ -278,7 +279,8 @@ export class SettingsScreen extends Component {
|
||||
onBlur={() => {
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.add(
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
state.title,
|
||||
@ -307,7 +309,8 @@ export class SettingsScreen extends Component {
|
||||
onBlur={() => {
|
||||
if (chatOwner) {
|
||||
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
|
||||
props.api.metadata.add(
|
||||
props.api.metadata.metadataAdd(
|
||||
'chat',
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
|
@ -10,50 +10,28 @@ import Tiles from './components/tiles';
|
||||
import Welcome from './components/welcome';
|
||||
|
||||
export default class LaunchApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new LaunchStore();
|
||||
this.state = this.store.state;
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
resetControllers() {
|
||||
this.api = null;
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.title = 'OS1 - Home';
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
|
||||
this.store.setStateHandler(this.setState.bind(this));
|
||||
const channel = new this.props.channel();
|
||||
this.api = new LaunchApi(this.props.ship, channel, this.store);
|
||||
|
||||
this.subscription = new LaunchSubscription(this.store, this.api, channel);
|
||||
this.subscription.start();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.delete();
|
||||
this.store.clear();
|
||||
this.store.setStateHandler(() => {});
|
||||
this.resetControllers();
|
||||
}
|
||||
componentWillUnmount() {}
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
const { props } = this;
|
||||
|
||||
return (
|
||||
<div className='v-mid ph2 dtc-m dtc-l dtc-xl flex justify-between flex-wrap' style={{ maxWidth: '40rem' }}>
|
||||
<Welcome firstTime={state.launch.firstTime} api={this.api} />
|
||||
<Welcome firstTime={props.launch.firstTime} api={props.api} />
|
||||
<Tiles
|
||||
tiles={state.launch.tiles}
|
||||
tileOrdering={state.launch.tileOrdering}
|
||||
tiles={props.launch.tiles}
|
||||
tileOrdering={props.launch.tileOrdering}
|
||||
api={this.api}
|
||||
location={state.location}
|
||||
weather={state.weather}
|
||||
location={props.location}
|
||||
weather={props.weather}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,51 +0,0 @@
|
||||
import ContactReducer from '../reducers/contact-update';
|
||||
import ChatReducer from '../reducers/chat-update';
|
||||
import InviteReducer from '../reducers/invite-update';
|
||||
import PermissionReducer from '../reducers/permission-update';
|
||||
import MetadataReducer from '../reducers/metadata-update';
|
||||
import S3Reducer from '../reducers/s3-update';
|
||||
import LocalReducer from '../reducers/local';
|
||||
|
||||
import BaseStore from './base';
|
||||
|
||||
export default class ChatStore extends BaseStore {
|
||||
constructor() {
|
||||
super();
|
||||
this.permissionReducer = new PermissionReducer();
|
||||
this.contactReducer = new ContactReducer();
|
||||
this.chatReducer = new ChatReducer();
|
||||
this.inviteReducer = new InviteReducer();
|
||||
this.s3Reducer = new S3Reducer();
|
||||
this.metadataReducer = new MetadataReducer();
|
||||
this.localReducer = new LocalReducer();
|
||||
}
|
||||
|
||||
initialState() {
|
||||
return {
|
||||
inbox: {},
|
||||
chatSynced: null,
|
||||
contacts: {},
|
||||
permissions: {},
|
||||
invites: {},
|
||||
associations: {
|
||||
chat: {},
|
||||
contacts: {}
|
||||
},
|
||||
sidebarShown: true,
|
||||
pendingMessages: new Map([]),
|
||||
chatInitialized: false,
|
||||
s3: {}
|
||||
};
|
||||
}
|
||||
|
||||
reduce(data, state) {
|
||||
this.permissionReducer.reduce(data, this.state);
|
||||
this.contactReducer.reduce(data, this.state);
|
||||
this.chatReducer.reduce(data, this.state);
|
||||
this.inviteReducer.reduce(data, this.state);
|
||||
this.metadataReducer.reduce(data, this.state);
|
||||
this.localReducer.reduce(data, this.state);
|
||||
this.s3Reducer.reduce(data, this.state);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user