diff --git a/pkg/arvo/app/chat-view.hoon b/pkg/arvo/app/chat-view.hoon index 5bc53c4289..7fdb6aa0b6 100644 --- a/pkg/arvo/app/chat-view.hoon +++ b/pkg/arvo/app/chat-view.hoon @@ -220,20 +220,29 @@ == :: %delete - =/ group-path (group-from-chat app-path.act) ?> ?=(^ app-path.act) + :: always just delete the chat from chat-store + :: + :+ (chat-hook-poke [%remove app-path.act]) + (chat-poke [%delete app-path.act]) + :: if we still have metadata for the chat, remove it, and the associated + :: group if it's unmanaged + :: + :: we aren't guaranteed to have metadata: the chat might have been + :: deleted by the host, which pushes metadata deletion down to us. + :: + =/ group-path=(unit path) + (maybe-group-from-chat app-path.act) + ?~ group-path ~ + =* group u.group-path %- zing - :~ :~ (chat-hook-poke [%remove app-path.act]) - (chat-poke [%delete app-path.act]) - == + :~ ?. (is-creator group %chat app-path.act) ~ + [(metadata-poke [%remove group [%chat app-path.act]])]~ :: - ?. (is-creator group-path %chat app-path.act) ~ - [(metadata-poke [%remove group-path [%chat app-path.act]])]~ - :: - ?: (is-managed group-path) ~ - :~ (group-poke [%unbundle group-path]) - (metadata-hook-poke [%remove group-path]) - (metadata-store-poke [%remove group-path [%chat app-path.act]]) + ?: (is-managed group) ~ + :~ (group-poke [%unbundle group]) + (metadata-hook-poke [%remove group]) + (metadata-store-poke [%remove group [%chat app-path.act]]) == == :: @@ -248,6 +257,8 @@ :: %groupify ?> ?=([%'~' ^] app-path.act) + :: retrieve old data + :: =/ data=(unit mailbox) (scry-for (unit mailbox) %chat-store [%mailbox app-path.act]) ?~ data @@ -265,26 +276,52 @@ =/ encoded-path=@ta (scot %t (spat app-path.act)) /metadata/[encoded-path]/chat/[encoded-path] - =/ new-path=^path (slag 1 `path`app-path.act) - =/ members=(set ship) - %+ fall - (group-scry app-path.act) - *(set ship) - =/ cards-1=(list card) - (poke-chat-view-action %delete app-path.act) - =/ cards-2=(list card) + :: figure out new data + :: + =/ chat-path=^path (slag 1 `path`app-path.act) + :: group-path: the group to associate with the chat + :: members: members of group, if it's new + :: new-members: new members of group, if it already exists + :: + =/ [group-path=path members=(set ship) new-members=(set ship)] + ?~ existing.act + [chat-path who.u.permission ~] + :+ group-path.u.existing.act + ~ + ?. inclusive.u.existing.act ~ + %- ~(dif in who.u.permission) + ~| [%groupifying-with-nonexistent-group group-path.u.existing.act] + %- need + (group-scry group-path.u.existing.act) + :: make changes + :: + ;: weld + :: delete the old chat + :: + (poke-chat-view-action %delete app-path.act) + :: + :: create the new chat. if needed, creates the new group. + :: %- poke-chat-view-action :* %create title.metadata description.metadata - new-path - new-path + chat-path + group-path %village members & == - %+ snoc (weld cards-1 cards-2) - (chat-poke %messages new-path envelopes.u.data) + :: + :: if needed, add members to the existing group + :: + ?~ new-members ~ + [(group-poke [%add new-members group-path])]~ + :: + :: import messages into the new chat + :: + [(chat-poke %messages chat-path envelopes.u.data)]~ + == == :: ++ create-chat @@ -387,13 +424,13 @@ =. pax ;:(weld /=chat-store/(scot %da now.bol)/mailbox pax /noun) .^((unit mailbox) %gx pax) :: - ++ group-from-chat + ++ maybe-group-from-chat |= app-path=path - ^- path + ^- (unit path) ?. .^(? %gu (scot %p our.bol) %metadata-store (scot %da now.bol) ~) ?: ?=([@ ^] app-path) ~& [%assuming-ported-legacy-chat app-path] - [%'~' app-path] + `[%'~' app-path] ~& [%weird-chat app-path] !! =/ resource-indices @@ -404,8 +441,15 @@ (scot %da now.bol) /resource-indices == - =/ groups=(set path) (~(got by resource-indices) [%chat app-path]) - (snag 0 ~(tap in groups)) + =/ groups=(set path) + %+ fall + (~(get by resource-indices) [%chat app-path]) + *(set path) + ?~ groups ~ + `n.groups + :: + ++ group-from-chat + (cork maybe-group-from-chat need) :: ++ is-managed |= =path diff --git a/pkg/arvo/lib/chat-json.hoon b/pkg/arvo/lib/chat-json.hoon index 4cc4f8b237..349d3de887 100644 --- a/pkg/arvo/lib/chat-json.hoon +++ b/pkg/arvo/lib/chat-json.hoon @@ -283,7 +283,8 @@ == :: ++ groupify - (ot [%app-path pa] ~) + =- (ot [%app-path pa] [%existing -] ~) + (mu (ot [%group-path pa] [%inclusive bo] ~)) :: ++ sec =, dejs:format diff --git a/pkg/arvo/sur/chat-view.hoon b/pkg/arvo/sur/chat-view.hoon index 9712fdcd84..73b4d0d1ab 100644 --- a/pkg/arvo/sur/chat-view.hoon +++ b/pkg/arvo/sur/chat-view.hoon @@ -1,7 +1,14 @@ /- *rw-security |% +$ chat-view-action - $% $: %create + $% :: %create: create a new chat + :: + :: if :app-path and :group-path are different, :members must be empty, + :: as the :group-path is assumed to exist. + :: if :app-path and :group-path are identical, and the :group-path + :: doesn't yet exist, will create a new group with :members. + :: + $: %create title=@t description=@t app-path=path @@ -18,6 +25,10 @@ :: and invite the current whitelist to that group. :: existing messages get moved over. :: - [%groupify app-path=path] + :: if :existing is provided, associates chat with that group instead + :: of creating a new one. :inclusive indicates whether or not to add + :: chat members to the group, if they aren't there already. + :: + [%groupify app-path=path existing=(unit [group-path=path inclusive=?])] == -- diff --git a/pkg/interface/chat/src/js/api.js b/pkg/interface/chat/src/js/api.js index ab93771974..d4ce7f4d5f 100644 --- a/pkg/interface/chat/src/js/api.js +++ b/pkg/interface/chat/src/js/api.js @@ -175,8 +175,15 @@ class UrbitApi { }); } - chatViewGroupify(path) { - return this.chatViewAction({ groupify: { 'app-path': path } }); + chatViewGroupify(path, group = null, inclusive = false) { + let action = { groupify: { 'app-path': path, existing: null } }; + if (group) { + action.groupify.existing = { + 'group-path': group, + inclusive: inclusive + } + } + return this.chatViewAction(action); } inviteAction(data) { diff --git a/pkg/interface/chat/src/js/components/lib/invite-element.js b/pkg/interface/chat/src/js/components/lib/invite-element.js index dc15fbc797..278156b0e2 100644 --- a/pkg/interface/chat/src/js/components/lib/invite-element.js +++ b/pkg/interface/chat/src/js/components/lib/invite-element.js @@ -65,6 +65,7 @@ export class InviteElement extends Component { groups={{}} contacts={props.contacts} groupResults={false} + shipResults={true} invites={{ groups: [], ships: this.state.members diff --git a/pkg/interface/chat/src/js/components/lib/invite-search.js b/pkg/interface/chat/src/js/components/lib/invite-search.js index f33e74b1bd..0c2cf63f61 100644 --- a/pkg/interface/chat/src/js/components/lib/invite-search.js +++ b/pkg/interface/chat/src/js/components/lib/invite-search.js @@ -96,37 +96,37 @@ export class InviteSearch extends Component { }); } - let shipMatches = this.state.peers.filter(e => { - return e.includes(searchTerm) && !this.props.invites.ships.includes(e); - }); - - for (let contact of this.state.contacts.keys()) { - let thisContact = this.state.contacts.get(contact); - let match = thisContact.filter(e => { - return e.toLowerCase().includes(searchTerm); + let shipMatches = []; + if (this.props.shipResults) { + shipMatches = this.state.peers.filter(e => { + return e.includes(searchTerm) && !this.props.invites.ships.includes(e); }); - if (match.length > 0) { - if (!(contact in shipMatches)) { - shipMatches.push(contact); + + for (let contact of this.state.contacts.keys()) { + let thisContact = this.state.contacts.get(contact); + let match = thisContact.filter(e => { + return e.toLowerCase().includes(searchTerm); + }); + if (match.length > 0) { + if (!(contact in shipMatches)) { + shipMatches.push(contact); + } } } + + let isValid = true; + if (!urbitOb.isValidPatp("~" + searchTerm)) { + isValid = false; + } + + if (shipMatches.length === 0 && isValid) { + shipMatches.push(searchTerm); + } } this.setState({ searchResults: { groups: groupMatches, ships: shipMatches } }); - - let isValid = true; - if (!urbitOb.isValidPatp("~" + searchTerm)) { - isValid = false; - } - - if (shipMatches.length === 0 && isValid) { - shipMatches.push(searchTerm); - this.setState({ - searchResults: { groups: groupMatches, ships: shipMatches } - }); - } } } @@ -203,6 +203,18 @@ export class InviteSearch extends Component { let participants =
; let searchResults = ; + let placeholder = ''; + if (props.shipResults) { + placeholder = 'ships'; + } + if (props.groupResults) { + if (placeholder.length > 0) { + placeholder = placeholder + ' or '; + } + placeholder = placeholder + 'existing groups'; + } + placeholder = 'Search for ' + placeholder; + let invErrElem = ; if (state.inviteError) { invErrElem = ( @@ -357,7 +369,7 @@ export class InviteSearch extends Component { "f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 w-100" + " db focus-b--black focus-b--white-d" } - placeholder="Search for ships or existing groups" + placeholder={placeholder} disabled={searchDisabled} rows={1} spellCheck={false} diff --git a/pkg/interface/chat/src/js/components/new.js b/pkg/interface/chat/src/js/components/new.js index 610cb5b7d1..d64b3d263a 100644 --- a/pkg/interface/chat/src/js/components/new.js +++ b/pkg/interface/chat/src/js/components/new.js @@ -263,6 +263,7 @@ export class NewScreen extends Component { contacts={props.contacts} associations={props.associations} groupResults={true} + shipResults={true} invites={{ groups: state.groups, ships: state.ships diff --git a/pkg/interface/chat/src/js/components/root.js b/pkg/interface/chat/src/js/components/root.js index 2c6e9794d6..d58252d9ab 100644 --- a/pkg/interface/chat/src/js/components/root.js +++ b/pkg/interface/chat/src/js/components/root.js @@ -280,6 +280,9 @@ export class Root extends Component { station={station} association={association} permission={permission} + groups={state.groups || {}} + contacts={state.contacts || {}} + associations={associations.contacts} api={api} station={station} group={group} diff --git a/pkg/interface/chat/src/js/components/settings.js b/pkg/interface/chat/src/js/components/settings.js index 8cc8c11d6a..9ba1045b89 100644 --- a/pkg/interface/chat/src/js/components/settings.js +++ b/pkg/interface/chat/src/js/components/settings.js @@ -5,6 +5,7 @@ import { Route, Link } from "react-router-dom"; import { ChatTabBar } from '/components/lib/chat-tabbar'; +import { InviteSearch } from '/components/lib/invite-search'; import SidebarSwitcher from './lib/icons/icon-sidebar-switch'; @@ -16,10 +17,15 @@ export class SettingsScreen extends Component { isLoading: false, title: "", description: "", - color: "" + color: "", + // groupify settings + targetGroup: null, + inclusive: false }; 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); @@ -58,6 +64,18 @@ export class SettingsScreen extends Component { } } + changeTargetGroup(target) { + if (target.groups.length === 1) { + this.setState({ targetGroup: target.groups[0] }); + } else { + this.setState({ targetGroup: null }); + } + } + + changeInclusive(event) { + this.setState({ inclusive: !!event.target.checked }); + } + changeTitle() { this.setState({title: event.target.value}) } @@ -122,7 +140,9 @@ export class SettingsScreen extends Component { groupifyChat() { const { props, state } = this; - props.api.chatView.groupify(props.station); + props.api.chatView.groupify( + props.station, state.targetGroup, state.inclusive + ); props.api.setSpinner(true); this.setState({ @@ -161,7 +181,6 @@ export class SettingsScreen extends Component { const { props, state } = this; const chatOwner = (deSig(props.match.params.ship) === window.ship); - console.log(chatOwner, props.match.params.ship, window.ship); const ownedUnmanagedVillage = chatOwner && @@ -171,15 +190,55 @@ export class SettingsScreen extends Component { if (!ownedUnmanagedVillage) { return null; } else { + let inclusiveToggle = + if (state.targetGroup) { + //TODO toggle component into /lib + let inclusiveClasses = state.inclusive + ? "relative checked bg-green2 br3 h1 toggle v-mid z-0" + : "relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0"; + inclusiveToggle = ( ++ Add chat members to the group if they aren't in it yet +
+Convert Chat
- Convert this chat into a group with associated chat. + Convert this chat into a group with associated chat, or select a + group to add this chat to.
+