mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Add additional org items to AppHeader
Summary: Adds links to manage team and go to billing account in the org menu (opened by clicking the dropdown in the top-left corner of Grist). Tweaks some wording of items in both AppHeader and AccountWidget, and adds a link to create a new team site to the Site Switcher in both menus. Also tweaks the UI of UserManager by adding an animation when the manager is opened from the doc access dialog. Test Plan: Browser tests. Reviewers: dsagal Reviewed By: dsagal Subscribers: dsagal Differential Revision: https://phab.getgrist.com/D3121
This commit is contained in:
parent
1178d35237
commit
59699bf446
@ -5,7 +5,8 @@ import {ShareAnnotations, ShareAnnotator} from 'app/common/ShareAnnotator';
|
|||||||
import {normalizeEmail} from 'app/common/emails';
|
import {normalizeEmail} from 'app/common/emails';
|
||||||
import {GristLoadConfig} from 'app/common/gristUrls';
|
import {GristLoadConfig} from 'app/common/gristUrls';
|
||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
import {ANONYMOUS_USER_EMAIL, EVERYONE_EMAIL, PermissionData, PermissionDelta, UserAPI} from 'app/common/UserAPI';
|
import {ANONYMOUS_USER_EMAIL, EVERYONE_EMAIL, Organization, PermissionData, PermissionDelta,
|
||||||
|
UserAPI, Workspace} from 'app/common/UserAPI';
|
||||||
import {getRealAccess} from 'app/common/UserAPI';
|
import {getRealAccess} from 'app/common/UserAPI';
|
||||||
import {computed, Computed, Disposable, obsArray, ObsArray, observable, Observable} from 'grainjs';
|
import {computed, Computed, Disposable, obsArray, ObsArray, observable, Observable} from 'grainjs';
|
||||||
import some = require('lodash/some');
|
import some = require('lodash/some');
|
||||||
@ -42,6 +43,8 @@ export interface UserManagerModel {
|
|||||||
|
|
||||||
export type ResourceType = 'organization'|'workspace'|'document';
|
export type ResourceType = 'organization'|'workspace'|'document';
|
||||||
|
|
||||||
|
export type Resource = Organization|Workspace|Document;
|
||||||
|
|
||||||
export interface IEditableMember {
|
export interface IEditableMember {
|
||||||
id: number; // Newly invited members do not have ids and are represented by -1
|
id: number; // Newly invited members do not have ids and are represented by -1
|
||||||
name: string;
|
name: string;
|
||||||
@ -116,8 +119,6 @@ export class UserManagerModelImpl extends Disposable implements UserManagerModel
|
|||||||
|
|
||||||
public isOrg: boolean = this.resourceType === 'organization';
|
public isOrg: boolean = this.resourceType === 'organization';
|
||||||
|
|
||||||
private _shareAnnotator?: ShareAnnotator;
|
|
||||||
|
|
||||||
// Checks if any members were added/removed/changed, if the max inherited role changed or if the
|
// Checks if any members were added/removed/changed, if the max inherited role changed or if the
|
||||||
// anonymous access setting changed to enable the confirm button to write changes to the server.
|
// anonymous access setting changed to enable the confirm button to write changes to the server.
|
||||||
public readonly isAnythingChanged: Computed<boolean> = this.autoDispose(computed<boolean>((use) => {
|
public readonly isAnythingChanged: Computed<boolean> = this.autoDispose(computed<boolean>((use) => {
|
||||||
@ -128,6 +129,8 @@ export class UserManagerModelImpl extends Disposable implements UserManagerModel
|
|||||||
(this.publicMember ? isMemberChangedFn(this.publicMember) : false);
|
(this.publicMember ? isMemberChangedFn(this.publicMember) : false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
private _shareAnnotator?: ShareAnnotator;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public initData: PermissionData,
|
public initData: PermissionData,
|
||||||
public resourceType: ResourceType,
|
public resourceType: ResourceType,
|
||||||
|
@ -12,10 +12,10 @@ import {menu, menuDivider, menuItem, menuItemLink, menuSubHeader} from 'app/clie
|
|||||||
import {commonUrls} from 'app/common/gristUrls';
|
import {commonUrls} from 'app/common/gristUrls';
|
||||||
import {FullUser} from 'app/common/LoginSessionAPI';
|
import {FullUser} from 'app/common/LoginSessionAPI';
|
||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
import {getOrgName, Organization, SUPPORT_EMAIL} from 'app/common/UserAPI';
|
import {Organization, SUPPORT_EMAIL} from 'app/common/UserAPI';
|
||||||
import {Disposable, dom, DomElementArg, styled} from 'grainjs';
|
import {Disposable, dom, DomElementArg, styled} from 'grainjs';
|
||||||
import {cssMenuItem} from 'popweasel';
|
import {cssMenuItem} from 'popweasel';
|
||||||
import {cssOrgCheckmark, cssOrgSelected} from 'app/client/ui/AppHeader';
|
import {buildSiteSwitcher} from 'app/client/ui/SiteSwitcher';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the user-icon that opens the account menu. When no user is logged in, render a Sign-in
|
* Render the user-icon that opens the account menu. When no user is logged in, render a Sign-in
|
||||||
@ -49,13 +49,15 @@ export class AccountWidget extends Disposable {
|
|||||||
*/
|
*/
|
||||||
private _makeAccountMenu(user: FullUser|null): DomElementArg[] {
|
private _makeAccountMenu(user: FullUser|null): DomElementArg[] {
|
||||||
// Opens the user-manager for the org.
|
// Opens the user-manager for the org.
|
||||||
|
// TODO: Factor out manageUsers, and related UI code, since AppHeader also uses it.
|
||||||
const manageUsers = async (org: Organization) => {
|
const manageUsers = async (org: Organization) => {
|
||||||
const api = this._appModel.api;
|
const api = this._appModel.api;
|
||||||
(await loadUserManager()).showUserManagerModal(api, {
|
(await loadUserManager()).showUserManagerModal(api, {
|
||||||
permissionData: api.getOrgAccess(org.id),
|
permissionData: api.getOrgAccess(org.id),
|
||||||
activeEmail: user ? user.email : null,
|
activeEmail: user ? user.email : null,
|
||||||
resourceType: 'organization',
|
resourceType: 'organization',
|
||||||
resourceId: org.id
|
resourceId: org.id,
|
||||||
|
resource: org,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,7 +107,7 @@ export class AccountWidget extends Disposable {
|
|||||||
|
|
||||||
// Show 'Organization Settings' when on a home page of a valid org.
|
// Show 'Organization Settings' when on a home page of a valid org.
|
||||||
(!this._docPageModel && currentOrg && !currentOrg.owner ?
|
(!this._docPageModel && currentOrg && !currentOrg.owner ?
|
||||||
menuItem(() => manageUsers(currentOrg), 'Manage Users', testId('dm-org-access'),
|
menuItem(() => manageUsers(currentOrg), 'Manage Team', testId('dm-org-access'),
|
||||||
dom.cls('disabled', !roles.canEditAccess(currentOrg.access))) :
|
dom.cls('disabled', !roles.canEditAccess(currentOrg.access))) :
|
||||||
// Don't show on doc pages, or for personal orgs.
|
// Don't show on doc pages, or for personal orgs.
|
||||||
null),
|
null),
|
||||||
@ -144,16 +146,8 @@ export class AccountWidget extends Disposable {
|
|||||||
|
|
||||||
dom.maybe((use) => use(orgs).length > 0, () => [
|
dom.maybe((use) => use(orgs).length > 0, () => [
|
||||||
menuDivider(),
|
menuDivider(),
|
||||||
menuSubHeader('Switch Sites'),
|
buildSiteSwitcher(this._appModel),
|
||||||
]),
|
]),
|
||||||
dom.forEach(orgs, (org) =>
|
|
||||||
menuItemLink(urlState().setLinkUrl({org: org.domain || undefined}),
|
|
||||||
cssOrgSelected.cls('', this._appModel.currentOrg ? org.id === this._appModel.currentOrg.id : false),
|
|
||||||
getOrgName(org),
|
|
||||||
cssOrgCheckmark('Tick', testId('usermenu-org-tick')),
|
|
||||||
testId('usermenu-org'),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,20 +4,44 @@ import {cssLeftPane} from 'app/client/ui/PagePanels';
|
|||||||
import {colors, testId, vars} from 'app/client/ui2018/cssVars';
|
import {colors, testId, vars} from 'app/client/ui2018/cssVars';
|
||||||
import * as version from 'app/common/version';
|
import * as version from 'app/common/version';
|
||||||
import {BindableValue, Disposable, dom, styled} from "grainjs";
|
import {BindableValue, Disposable, dom, styled} from "grainjs";
|
||||||
import {menu, menuDivider, menuItemLink, menuSubHeader} from 'app/client/ui2018/menus';
|
import {menu, menuDivider, menuItem, menuItemLink, menuSubHeader} from 'app/client/ui2018/menus';
|
||||||
import {getOrgName} from 'app/common/UserAPI';
|
import {Organization, SUPPORT_EMAIL} from 'app/common/UserAPI';
|
||||||
import {AppModel} from 'app/client/models/AppModel';
|
import {AppModel} from 'app/client/models/AppModel';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
|
import {DocPageModel} from 'app/client/models/DocPageModel';
|
||||||
|
import * as roles from 'app/common/roles';
|
||||||
|
import {loadUserManager} from 'app/client/lib/imports';
|
||||||
|
import {buildSiteSwitcher} from 'app/client/ui/SiteSwitcher';
|
||||||
|
|
||||||
|
|
||||||
export class AppHeader extends Disposable {
|
export class AppHeader extends Disposable {
|
||||||
constructor(private _orgName: BindableValue<string>, private _appModel: AppModel) {
|
constructor(private _orgName: BindableValue<string>, private _appModel: AppModel,
|
||||||
|
private _docPageModel?: DocPageModel) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildDom() {
|
public buildDom() {
|
||||||
const theme = getTheme(this._appModel.topAppModel.productFlavor);
|
const theme = getTheme(this._appModel.topAppModel.productFlavor);
|
||||||
|
|
||||||
|
const user = this._appModel.currentValidUser;
|
||||||
|
const orgs = this._appModel.topAppModel.orgs;
|
||||||
|
const currentOrg = this._appModel.currentOrg;
|
||||||
|
const isTeamSite = Boolean(currentOrg && !currentOrg.owner);
|
||||||
|
const isBillingManager = Boolean(currentOrg && currentOrg.billingAccount &&
|
||||||
|
(currentOrg.billingAccount.isManager || user?.email === SUPPORT_EMAIL));
|
||||||
|
|
||||||
|
// Opens the user-manager for the org.
|
||||||
|
const manageUsers = async (org: Organization) => {
|
||||||
|
const api = this._appModel.api;
|
||||||
|
(await loadUserManager()).showUserManagerModal(api, {
|
||||||
|
permissionData: api.getOrgAccess(org.id),
|
||||||
|
activeEmail: user ? user.email : null,
|
||||||
|
resourceType: 'organization',
|
||||||
|
resourceId: org.id,
|
||||||
|
resource: org,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return cssAppHeader(
|
return cssAppHeader(
|
||||||
cssAppHeader.cls('-widelogo', theme.wideLogo || false),
|
cssAppHeader.cls('-widelogo', theme.wideLogo || false),
|
||||||
// Show version when hovering over the application icon.
|
// Show version when hovering over the application icon.
|
||||||
@ -29,46 +53,38 @@ export class AppHeader extends Disposable {
|
|||||||
cssOrg(
|
cssOrg(
|
||||||
cssOrgName(dom.text(this._orgName)),
|
cssOrgName(dom.text(this._orgName)),
|
||||||
this._orgName && cssDropdownIcon('Dropdown'),
|
this._orgName && cssDropdownIcon('Dropdown'),
|
||||||
menu(() => this._makeOrgMenu(), {placement: 'bottom-start'}),
|
menu(() => [
|
||||||
|
menuSubHeader(`${isTeamSite ? 'Team' : 'Personal'} Site`, testId('orgmenu-title')),
|
||||||
|
menuItemLink(urlState().setLinkUrl({}), 'Home Page', testId('orgmenu-home-page')),
|
||||||
|
|
||||||
|
// Show 'Organization Settings' when on a home page of a valid org.
|
||||||
|
(!this._docPageModel && currentOrg && !currentOrg.owner ?
|
||||||
|
menuItem(() => manageUsers(currentOrg), 'Manage Team', testId('orgmenu-manage-team'),
|
||||||
|
dom.cls('disabled', !roles.canEditAccess(currentOrg.access))) :
|
||||||
|
// Don't show on doc pages, or for personal orgs.
|
||||||
|
null),
|
||||||
|
|
||||||
|
// Show link to billing pages.
|
||||||
|
currentOrg && !currentOrg.owner ?
|
||||||
|
// For links, disabling with just a class is hard; easier to just not make it a link.
|
||||||
|
// TODO weasel menus should support disabling menuItemLink.
|
||||||
|
(isBillingManager ?
|
||||||
|
menuItemLink(urlState().setLinkUrl({billing: 'billing'}), 'Billing Account') :
|
||||||
|
menuItem(() => null, 'Billing Account', dom.cls('disabled', true), testId('orgmenu-billing'))
|
||||||
|
) :
|
||||||
|
null,
|
||||||
|
|
||||||
|
dom.maybe((use) => use(orgs).length > 0, () => [
|
||||||
|
menuDivider(),
|
||||||
|
buildSiteSwitcher(this._appModel),
|
||||||
|
]),
|
||||||
|
], { placement: 'bottom-start' }),
|
||||||
testId('dm-org'),
|
testId('dm-org'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _makeOrgMenu() {
|
|
||||||
const orgs = this._appModel.topAppModel.orgs;
|
|
||||||
|
|
||||||
return [
|
|
||||||
menuItemLink(urlState().setLinkUrl({}), 'Go to Home Page', testId('orgmenu-home-page')),
|
|
||||||
menuDivider(),
|
|
||||||
menuSubHeader('Switch Sites'),
|
|
||||||
dom.forEach(orgs, (org) =>
|
|
||||||
menuItemLink(urlState().setLinkUrl({org: org.domain || undefined}),
|
|
||||||
cssOrgSelected.cls('', this._appModel.currentOrg ? org.id === this._appModel.currentOrg.id : false),
|
|
||||||
getOrgName(org),
|
|
||||||
cssOrgCheckmark('Tick', testId('orgmenu-org-tick')),
|
|
||||||
testId('orgmenu-org'),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cssOrgSelected = styled('div', `
|
|
||||||
background-color: ${colors.dark};
|
|
||||||
color: ${colors.light};
|
|
||||||
`);
|
|
||||||
|
|
||||||
export const cssOrgCheckmark = styled(icon, `
|
|
||||||
flex: none;
|
|
||||||
margin-left: 16px;
|
|
||||||
--icon-color: ${colors.light};
|
|
||||||
display: none;
|
|
||||||
.${cssOrgSelected.className} > & {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const cssAppHeader = styled('div', `
|
const cssAppHeader = styled('div', `
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -136,7 +136,7 @@ function pagePanelsDoc(owner: IDisposableOwner, appModel: AppModel, appObj: App)
|
|||||||
leftPanel: {
|
leftPanel: {
|
||||||
panelWidth: leftPanelWidth,
|
panelWidth: leftPanelWidth,
|
||||||
panelOpen: leftPanelOpen,
|
panelOpen: leftPanelOpen,
|
||||||
header: dom.create(AppHeader, appModel.currentOrgName || pageModel.currentOrgName, appModel),
|
header: dom.create(AppHeader, appModel.currentOrgName || pageModel.currentOrgName, appModel, pageModel),
|
||||||
content: pageModel.createLeftPane(leftPanelOpen),
|
content: pageModel.createLeftPane(leftPanelOpen),
|
||||||
},
|
},
|
||||||
rightPanel: {
|
rightPanel: {
|
||||||
|
53
app/client/ui/SiteSwitcher.ts
Normal file
53
app/client/ui/SiteSwitcher.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import {commonUrls} from 'app/common/gristUrls';
|
||||||
|
import {getOrgName} from 'app/common/UserAPI';
|
||||||
|
import {dom, makeTestId, styled} from 'grainjs';
|
||||||
|
import {AppModel} from 'app/client/models/AppModel';
|
||||||
|
import {urlState} from 'app/client/models/gristUrlState';
|
||||||
|
import {menuIcon, menuItemLink, menuSubHeader} from 'app/client/ui2018/menus';
|
||||||
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
|
import {colors} from 'app/client/ui2018/cssVars';
|
||||||
|
|
||||||
|
const testId = makeTestId('test-site-switcher-');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a menu sub-section that displays a list of orgs/sites that the current
|
||||||
|
* valid user has access to, with buttons to navigate to them.
|
||||||
|
*
|
||||||
|
* Used by AppHeader and AccountWidget.
|
||||||
|
*/
|
||||||
|
export function buildSiteSwitcher(appModel: AppModel) {
|
||||||
|
const orgs = appModel.topAppModel.orgs;
|
||||||
|
|
||||||
|
return [
|
||||||
|
menuSubHeader('Switch Sites'),
|
||||||
|
dom.forEach(orgs, (org) =>
|
||||||
|
menuItemLink(urlState().setLinkUrl({ org: org.domain || undefined }),
|
||||||
|
cssOrgSelected.cls('', appModel.currentOrg ? org.id === appModel.currentOrg.id : false),
|
||||||
|
getOrgName(org),
|
||||||
|
cssOrgCheckmark('Tick', testId('org-tick')),
|
||||||
|
testId('org'),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
menuItemLink(
|
||||||
|
{ href: commonUrls.createTeamSite },
|
||||||
|
menuIcon('Plus'),
|
||||||
|
'Create new team site',
|
||||||
|
testId('create-new-site'),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssOrgSelected = styled('div', `
|
||||||
|
background-color: ${colors.dark};
|
||||||
|
color: ${colors.light};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssOrgCheckmark = styled(icon, `
|
||||||
|
flex: none;
|
||||||
|
margin-left: 16px;
|
||||||
|
--icon-color: ${colors.light};
|
||||||
|
display: none;
|
||||||
|
.${cssOrgSelected.className} > & {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`);
|
@ -8,8 +8,8 @@
|
|||||||
import {commonUrls} from 'app/common/gristUrls';
|
import {commonUrls} from 'app/common/gristUrls';
|
||||||
import {FullUser} from 'app/common/LoginSessionAPI';
|
import {FullUser} from 'app/common/LoginSessionAPI';
|
||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
import {PermissionData, UserAPI} from 'app/common/UserAPI';
|
import {Organization, PermissionData, UserAPI} from 'app/common/UserAPI';
|
||||||
import {computed, Computed, Disposable, observable, Observable} from 'grainjs';
|
import {computed, Computed, Disposable, keyframes, observable, Observable} from 'grainjs';
|
||||||
import {dom, DomElementArg, styled} from 'grainjs';
|
import {dom, DomElementArg, styled} from 'grainjs';
|
||||||
import pick = require('lodash/pick');
|
import pick = require('lodash/pick');
|
||||||
import {cssMenuItem} from 'popweasel';
|
import {cssMenuItem} from 'popweasel';
|
||||||
@ -20,7 +20,8 @@ import {AppModel} from 'app/client/models/AppModel';
|
|||||||
import {DocPageModel} from 'app/client/models/DocPageModel';
|
import {DocPageModel} from 'app/client/models/DocPageModel';
|
||||||
import {reportError} from 'app/client/models/errors';
|
import {reportError} from 'app/client/models/errors';
|
||||||
import {urlState} from 'app/client/models/gristUrlState';
|
import {urlState} from 'app/client/models/gristUrlState';
|
||||||
import {IEditableMember, IMemberSelectOption, IOrgMemberSelectOption} from 'app/client/models/UserManagerModel';
|
import {IEditableMember, IMemberSelectOption, IOrgMemberSelectOption,
|
||||||
|
Resource} from 'app/client/models/UserManagerModel';
|
||||||
import {UserManagerModel, UserManagerModelImpl} from 'app/client/models/UserManagerModel';
|
import {UserManagerModel, UserManagerModelImpl} from 'app/client/models/UserManagerModel';
|
||||||
import {getResourceParent, ResourceType} from 'app/client/models/UserManagerModel';
|
import {getResourceParent, ResourceType} from 'app/client/models/UserManagerModel';
|
||||||
import {shadowScroll} from 'app/client/ui/shadowScroll';
|
import {shadowScroll} from 'app/client/ui/shadowScroll';
|
||||||
@ -41,6 +42,7 @@ export interface IUserManagerOptions {
|
|||||||
activeEmail: string|null;
|
activeEmail: string|null;
|
||||||
resourceType: ResourceType;
|
resourceType: ResourceType;
|
||||||
resourceId: string|number;
|
resourceId: string|number;
|
||||||
|
resource?: Resource;
|
||||||
docPageModel?: DocPageModel;
|
docPageModel?: DocPageModel;
|
||||||
appModel?: AppModel; // If present, we offer access to a nested team-level dialog.
|
appModel?: AppModel; // If present, we offer access to a nested team-level dialog.
|
||||||
linkToCopy?: string;
|
linkToCopy?: string;
|
||||||
@ -49,6 +51,7 @@ export interface IUserManagerOptions {
|
|||||||
prompt?: { // If set, user manager should open with this email filled in and ready to go.
|
prompt?: { // If set, user manager should open with this email filled in and ready to go.
|
||||||
email: string;
|
email: string;
|
||||||
};
|
};
|
||||||
|
showAnimation?: boolean; // If true, animates opening of the modal. Defaults to false.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an instance of UserManagerModel given IUserManagerOptions. Makes the async call for the
|
// Returns an instance of UserManagerModel given IUserManagerOptions. Makes the async call for the
|
||||||
@ -91,10 +94,10 @@ export function showUserManagerModal(userApi: UserAPI, options: IUserManagerOpti
|
|||||||
modal(ctl => [
|
modal(ctl => [
|
||||||
// We set the padding to 0 since the body scroll shadows extend to the edge of the modal.
|
// We set the padding to 0 since the body scroll shadows extend to the edge of the modal.
|
||||||
{ style: 'padding: 0;' },
|
{ style: 'padding: 0;' },
|
||||||
|
options.showAnimation ? dom.cls(cssAnimatedModal.className) : null,
|
||||||
cssModalTitle(
|
cssModalTitle(
|
||||||
{ style: 'margin: 40px 64px 0 64px;' },
|
{ style: 'margin: 40px 64px 0 64px;' },
|
||||||
`Invite people to ${renderType(options.resourceType)}`,
|
renderTitle(options.resourceType, options.resource),
|
||||||
(options.resourceType === 'document' ? makeCopyBtn(options.linkToCopy, cssCopyBtn.cls('-header')) : null),
|
(options.resourceType === 'document' ? makeCopyBtn(options.linkToCopy, cssCopyBtn.cls('-header')) : null),
|
||||||
testId('um-header')
|
testId('um-header')
|
||||||
),
|
),
|
||||||
@ -433,7 +436,9 @@ export class MemberEmail extends Disposable {
|
|||||||
modifiers: {
|
modifiers: {
|
||||||
offset: { enabled: true, offset: -40 }
|
offset: { enabled: true, offset: -40 }
|
||||||
},
|
},
|
||||||
stretchToSelector: `.${cssEmailInputContainer.className}`
|
stretchToSelector: `.${cssEmailInputContainer.className}`,
|
||||||
|
attach: null,
|
||||||
|
boundaries: 'document' as any, // TODO: Update weasel.js types to allow 'document'.
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
cssEmailInputContainer.cls('-green', enableAdd),
|
cssEmailInputContainer.cls('-green', enableAdd),
|
||||||
@ -507,8 +512,10 @@ async function manageTeam(appModel: AppModel,
|
|||||||
activeEmail: user ? user.email : null,
|
activeEmail: user ? user.email : null,
|
||||||
resourceType: 'organization',
|
resourceType: 'organization',
|
||||||
resourceId: currentOrg.id,
|
resourceId: currentOrg.id,
|
||||||
|
resource: currentOrg,
|
||||||
onSave,
|
onSave,
|
||||||
prompt,
|
prompt,
|
||||||
|
showAnimation: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -602,7 +609,40 @@ const cssAccessLink = styled(cssLink, `
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Render the name "organization" as "team site" in UI
|
const cssOrgName = styled('div', `
|
||||||
function renderType(resourceType: ResourceType): string {
|
font-size: ${vars.largeFontSize};
|
||||||
return resourceType === 'organization' ? 'team site' : resourceType;
|
`);
|
||||||
|
|
||||||
|
const cssOrgDomain = styled('span', `
|
||||||
|
color: ${colors.lightGreen};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssFadeInFromTop = keyframes(`
|
||||||
|
from {top: -250px; opacity: 0}
|
||||||
|
to {top: 0; opacity: 1}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssAnimatedModal = styled('div', `
|
||||||
|
animation-name: ${cssFadeInFromTop};
|
||||||
|
animation-duration: 0.4s;
|
||||||
|
position: relative;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Render the UserManager title for `resourceType` (e.g. org as "team site").
|
||||||
|
function renderTitle(resourceType: ResourceType, resource?: Resource) {
|
||||||
|
switch (resourceType) {
|
||||||
|
case 'organization': {
|
||||||
|
return [
|
||||||
|
'Manage members of team site',
|
||||||
|
!resource ? null : cssOrgName(
|
||||||
|
`${(resource as Organization).name} (`,
|
||||||
|
cssOrgDomain(`${(resource as Organization).domain}.getgrist.com`),
|
||||||
|
')',
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return `Invite people to ${resourceType}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ export const MIN_URLID_PREFIX_LENGTH = 12;
|
|||||||
export const commonUrls = {
|
export const commonUrls = {
|
||||||
help: "https://support.getgrist.com",
|
help: "https://support.getgrist.com",
|
||||||
plans: "https://www.getgrist.com/pricing",
|
plans: "https://www.getgrist.com/pricing",
|
||||||
|
createTeamSite: "https://www.getgrist.com/create-team-site",
|
||||||
|
|
||||||
efcrConnect: 'https://efc-r.com/connect',
|
efcrConnect: 'https://efc-r.com/connect',
|
||||||
efcrHelp: 'https://www.nioxus.info/eFCR-Help',
|
efcrHelp: 'https://www.nioxus.info/eFCR-Help',
|
||||||
|
@ -1682,7 +1682,7 @@ async function openAccountMenu() {
|
|||||||
// Since the AccountWidget loads orgs and the user data asynchronously, the menu
|
// Since the AccountWidget loads orgs and the user data asynchronously, the menu
|
||||||
// can expand itself causing the click to land on a wrong button.
|
// can expand itself causing the click to land on a wrong button.
|
||||||
await waitForServer();
|
await waitForServer();
|
||||||
await driver.findWait('.test-usermenu-org', 1000);
|
await driver.findWait('.test-site-switcher-org', 1000);
|
||||||
await driver.sleep(250); // There's still some jitter (scroll-bar? other user accounts?)
|
await driver.sleep(250); // There's still some jitter (scroll-bar? other user accounts?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user