mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Site Switcher and Other Sites
Summary: A new section, Other Sites, will now be shown on the All Documents page when: - A user is on a personal site and has access to other team sites. - A user is on a public site with view access only. In addition, a site switcher is now available by clicking the site name in the top-left section of the UI next to the Grist logo. It works much like the switcher in the Account menu. Test Plan: Browser tests. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2979
This commit is contained in:
@@ -8,7 +8,7 @@ import {GristLoadConfig} from 'app/common/gristUrls';
|
||||
import {FullUser} from 'app/common/LoginSessionAPI';
|
||||
import {LocalPlugin} from 'app/common/plugin';
|
||||
import {getOrgName, Organization, OrgError, UserAPI, UserAPIImpl} from 'app/common/UserAPI';
|
||||
import {Computed, Disposable, Observable, subscribe} from 'grainjs';
|
||||
import {bundleChanges, Computed, Disposable, Observable, subscribe} from 'grainjs';
|
||||
|
||||
export {reportError} from 'app/client/models/errors';
|
||||
|
||||
@@ -29,6 +29,9 @@ export interface TopAppModel {
|
||||
// different parts of the code aren't using different users/orgs while the switch is pending.
|
||||
appObs: Observable<AppModel|null>;
|
||||
|
||||
orgs: Observable<Organization[]>;
|
||||
users: Observable<FullUser[]>;
|
||||
|
||||
// Reinitialize the app. This is called when org or user changes.
|
||||
initialize(): void;
|
||||
|
||||
@@ -68,6 +71,8 @@ export class TopAppModelImpl extends Disposable implements TopAppModel {
|
||||
public readonly currentSubdomain = Computed.create(this, urlState().state, (use, s) => s.org);
|
||||
public readonly notifier = Notifier.create(this);
|
||||
public readonly appObs = Observable.create<AppModel|null>(this, null);
|
||||
public readonly orgs = Observable.create<Organization[]>(this, []);
|
||||
public readonly users = Observable.create<FullUser[]>(this, []);
|
||||
public readonly plugins: LocalPlugin[] = [];
|
||||
private readonly _gristConfig?: GristLoadConfig;
|
||||
|
||||
@@ -85,6 +90,8 @@ export class TopAppModelImpl extends Disposable implements TopAppModel {
|
||||
// and the FullUser to use for it (the user may change when switching orgs).
|
||||
this.autoDispose(subscribe(this.currentSubdomain, (use) => this.initialize()));
|
||||
this.plugins = this._gristConfig?.plugins || [];
|
||||
|
||||
this._fetchUsersAndOrgs().catch(reportError);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
@@ -146,6 +153,15 @@ export class TopAppModelImpl extends Disposable implements TopAppModel {
|
||||
AppModelImpl.create(this.appObs, this, null, null, {error: err.message, status: err.status || 500});
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchUsersAndOrgs() {
|
||||
const data = await this.api.getSessionAll();
|
||||
if (this.isDisposed()) { return; }
|
||||
bundleChanges(() => {
|
||||
this.users.set(data.users);
|
||||
this.orgs.set(data.orgs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AppModelImpl extends Disposable implements AppModel {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {IHomePage} from 'app/common/gristUrls';
|
||||
import {isLongerThan} from 'app/common/gutil';
|
||||
import {SortPref, UserOrgPrefs, ViewPref} from 'app/common/Prefs';
|
||||
import * as roles from 'app/common/roles';
|
||||
import {Document, Workspace} from 'app/common/UserAPI';
|
||||
import {Document, Organization, Workspace} from 'app/common/UserAPI';
|
||||
import {bundleChanges, Computed, Disposable, Observable, subscribe} from 'grainjs';
|
||||
import * as moment from 'moment';
|
||||
import flatten = require('lodash/flatten');
|
||||
@@ -63,6 +63,10 @@ export interface HomeModel {
|
||||
// List of featured templates from templateWorkspaces.
|
||||
featuredTemplates: Observable<Document[]>;
|
||||
|
||||
// List of other sites (orgs) user can access. Only populated on All Documents, and only when
|
||||
// the current org is a personal org, or the current org is view access only.
|
||||
otherSites: Observable<Organization[]>;
|
||||
|
||||
currentSort: Observable<SortPref>;
|
||||
currentView: Observable<ViewPref>;
|
||||
importSources: Observable<ImportSourceElement[]>;
|
||||
@@ -118,6 +122,21 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings
|
||||
return sortBy(featuredTemplates, (t) => t.name.toLowerCase());
|
||||
});
|
||||
|
||||
public readonly otherSites = Computed.create(this, this.currentPage, this.app.topAppModel.orgs,
|
||||
(_use, page, orgs) => {
|
||||
if (page !== 'all') { return []; }
|
||||
|
||||
const currentOrg = this._app.currentOrg;
|
||||
if (!currentOrg) { return []; }
|
||||
|
||||
const isPersonalOrg = currentOrg.owner;
|
||||
if (!isPersonalOrg && (currentOrg.access !== 'viewers' || !currentOrg.public)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return orgs.filter(org => org.id !== currentOrg.id);
|
||||
});
|
||||
|
||||
public readonly currentSort: Observable<SortPref>;
|
||||
public readonly currentView: Observable<ViewPref>;
|
||||
|
||||
@@ -255,16 +274,10 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings
|
||||
this.loading.set(true);
|
||||
const currentPage = this.currentPage.get();
|
||||
const promises = [
|
||||
this._fetchWorkspaces(org.id, false).catch(reportError), // workspaces
|
||||
currentPage === 'trash' ? this._fetchWorkspaces(org.id, true).catch(reportError) : null, // trash
|
||||
null // templates
|
||||
];
|
||||
|
||||
const shouldFetchTemplates = ['all', 'templates'].includes(currentPage);
|
||||
if (shouldFetchTemplates) {
|
||||
const onlyFeatured = currentPage === 'all';
|
||||
promises[2] = this._fetchTemplates(onlyFeatured);
|
||||
}
|
||||
this._fetchWorkspaces(org.id, false).catch(reportError),
|
||||
currentPage === 'trash' ? this._fetchWorkspaces(org.id, true).catch(reportError) : null,
|
||||
this._maybeFetchTemplates(),
|
||||
] as const;
|
||||
|
||||
const promise = Promise.all(promises);
|
||||
if (await isLongerThan(promise, DELAY_BEFORE_SPINNER_MS)) {
|
||||
@@ -327,9 +340,19 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings
|
||||
ws.name.toLowerCase()]);
|
||||
}
|
||||
|
||||
private async _fetchTemplates(onlyFeatured: boolean) {
|
||||
/**
|
||||
* Fetches templates if on the Templates or All Documents page.
|
||||
*
|
||||
* Only fetches featured (pinned) templates on the All Documents page.
|
||||
*/
|
||||
private async _maybeFetchTemplates(): Promise<Workspace[] | null> {
|
||||
const currentPage = this.currentPage.get();
|
||||
const shouldFetchTemplates = ['all', 'templates'].includes(currentPage);
|
||||
if (!shouldFetchTemplates) { return null; }
|
||||
|
||||
let templateWss: Workspace[] = [];
|
||||
try {
|
||||
const onlyFeatured = currentPage === 'all';
|
||||
templateWss = await this._app.api.getTemplates(onlyFeatured);
|
||||
} catch {
|
||||
// If the org doesn't exist (404), return nothing and don't report error to user.
|
||||
|
||||
Reference in New Issue
Block a user