aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js55
1 files changed, 46 insertions, 9 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index 6f97ca5..bdab271 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -21,6 +21,7 @@ import {
} from './hub-client.js';
import { startIdleWatch, markActive } from './idle.js';
import { GroupPage } from './group-page.js';
+import { parseGroupLink, groupLinkRoute, findLinkedGroup } from './group-link.js';
import { lazy } from './lazy.js';
import { ConnectionPool } from './connection-pool.js';
import { MusicPlayerBar } from './music-player.js';
@@ -766,6 +767,9 @@ function App() {
const [needsHub, setNeedsHub] = useState(
platform.isNative && !platform.hubBase());
const [groups, setGroups] = useState([]);
+ // Whether `/mine` has answered for this sign-in. A `#/name@owner` link can
+ // only be called unknown once the list it is looked up in has arrived.
+ const [groupsLoaded, setGroupsLoaded] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const [notifications, setNotifications] = useState([]);
const [unreadCount, setUnreadCount] = useState(0);
@@ -1008,11 +1012,13 @@ function App() {
if (!user) {
nodeKeyAskedForRef.current = null;
setGroups([]); setNotifications([]); setUnreadCount(0); setHasNodeKey(false);
+ setGroupsLoaded(false);
return;
}
hubFetch('/v1/groups/mine', { token: user.token })
.then(data => setGroups(data.groups || []))
- .catch(() => setGroups([]));
+ .catch(() => setGroups([]))
+ .finally(() => setGroupsLoaded(true));
hubFetch('/v1/users/me/preferences', { token: user.token })
.then(async (prefs) => {
setUserPrefs(prefs || {});
@@ -1233,11 +1239,36 @@ function App() {
// out as the only way back.
const refreshAuth = useCallback(() => refreshAccessToken(), []);
+ // A group is reached by its id (`#/group/<id>`, what every link inside the
+ // application uses) or by its handle (`#/name@owner[/path]`, the one a person
+ // types or shares — group-link.js). Both open the same page; `groupRoute` is
+ // the first form whichever was used, so the sidebar and the page see one.
+ const groupLink = route.startsWith('/group/') ? null : parseGroupLink(route);
+ const linkedGroup = groupLink ? findLinkedGroup(groups, groupLink) : null;
+ const groupId = route.startsWith('/group/') ? route.slice(7)
+ : linkedGroup ? linkedGroup.id : null;
+ const groupRoute = groupId ? '/group/' + groupId : route;
+ const shownGroup = groupId ? groups.find(g => g.id === groupId) : null;
+ const linkPath = groupLink && linkedGroup ? groupLink.path : '';
+ // The address shows the handle, which is the link worth copying. `replace`,
+ // so this is not a history entry; and only once the path a link named has
+ // been acted on (`onLinkOpened`), so a reload does not download the file a
+ // second time.
+ const shownGroupRoute = shownGroup ? groupLinkRoute(shownGroup) : null;
+ useEffect(() => {
+ if (shownGroupRoute && !linkPath && route !== shownGroupRoute) {
+ window.location.replace('#' + shownGroupRoute);
+ }
+ }, [route, shownGroupRoute, linkPath]);
+ const onLinkOpened = useCallback(() => {
+ if (shownGroupRoute) window.location.replace('#' + shownGroupRoute);
+ }, [shownGroupRoute]);
+
let page;
// A desktop build with no hub configured cannot do anything at all, so it
- // asks before showing a sign-in form that could not work. Deliberately not
- // defaulted to meshbay.org: a client that picks its own hub is a client that
- // can be pointed at one.
+ // asks before showing a sign-in form that could not work. The field comes
+ // filled with meshbay.org but is still asked: a client that picks its own hub
+ // is a client that can be pointed at one.
if (needsHub) {
page = html`<${FirstRunPage} onSet=${() => setNeedsHub(false)} />`;
} else if (!deviceTried) {
@@ -1282,17 +1313,23 @@ function App() {
}} />`;
} else if (route === '/node' && platform.capabilities.nodeAdmin && hasNodeKey) {
page = html`<${LazyNodePage} groups=${groups} token=${user.token} username=${user.username} />`;
- } else if (route.startsWith('/group/')) {
- const groupId = route.slice(7);
- const group = groups.find(g => g.id === groupId);
+ } else if (groupId) {
page = html`<${GroupPage}
- groupId=${groupId} group=${group} token=${user.token}
+ groupId=${groupId} group=${shownGroup} token=${user.token}
+ openPath=${linkPath} onLinkOpened=${onLinkOpened}
username=${user.username} userId=${user.userId}
userPrefs=${userPrefs}
onRefreshAuth=${refreshAuth} onJoined=${dismissGroupNotifications}
onGroupUpdated=${updateGroup} onPresence=${notePresence}
onLeft=${handleLeftGroup}
onPlayQueue=${handlePlayQueue} onStopMusic=${handleStopMusic} />`;
+ } else if (groupLink) {
+ // Not among this account's groups — or not yet known to be. The same words
+ // whether it does not exist or is someone else's: the list it was looked
+ // up in is this account's own, so there is nothing else to tell.
+ page = groupsLoaded
+ ? html`<div class="page-content"><p class="page-message">${t('group.link_unknown')}</p></div>`
+ : html`<div class="page-content"><p class="page-message"><span class="spinner"></span></p></div>`;
} else if (route === '/admin') {
page = (user.role === 'moderator' || user.role === 'admin')
? html`<${LazyAdminPage} token=${user.token} role=${user.role} />`
@@ -1334,7 +1371,7 @@ function App() {
groups=${groups}
presence=${presence}
indexProgressPct=${indexProgressPct}
- route=${route}
+ route=${groupRoute}
menuOpen=${menuOpen}
role=${user.role}
allowPublicGroups=${allowPublicGroups}