mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Fixing UserManger releted tests
Summary: Some tests were not compatible with the new ACUser search component. Test Plan: Existing Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3643
This commit is contained in:
parent
a5744dadfb
commit
0af379db7d
@ -1,11 +1,8 @@
|
|||||||
import {ACResults, ACIndex, ACItem, buildHighlightedDom, normalizeText} from "app/client/lib/ACIndex";
|
import {ACIndex, ACItem, ACResults, buildHighlightedDom, normalizeText} from "app/client/lib/ACIndex";
|
||||||
import {cssSelectItem} from "app/client/lib/ACSelect";
|
import {cssSelectItem} from "app/client/lib/ACSelect";
|
||||||
import {Autocomplete, IAutocompleteOptions} from "app/client/lib/autocomplete";
|
import {Autocomplete, IAutocompleteOptions} from "app/client/lib/autocomplete";
|
||||||
import {cssMenuItem} from "popweasel";
|
import {colors, testId, theme} from "app/client/ui2018/cssVars";
|
||||||
|
|
||||||
import {testId, colors, theme} from "app/client/ui2018/cssVars";
|
|
||||||
import {menuCssClass} from "app/client/ui2018/menus";
|
import {menuCssClass} from "app/client/ui2018/menus";
|
||||||
import {dom, DomElementArg, Holder, IDisposableOwner, Observable, styled, computed, Computed} from "grainjs";
|
|
||||||
import {
|
import {
|
||||||
cssEmailInput,
|
cssEmailInput,
|
||||||
cssEmailInputContainer,
|
cssEmailInputContainer,
|
||||||
@ -17,6 +14,8 @@ import {
|
|||||||
cssMemberText,
|
cssMemberText,
|
||||||
} from "app/client/ui/UserItem";
|
} from "app/client/ui/UserItem";
|
||||||
import {createUserImage, cssUserImage} from "app/client/ui/UserImage";
|
import {createUserImage, cssUserImage} from "app/client/ui/UserImage";
|
||||||
|
import {Computed, computed, dom, DomElementArg, Holder, IDisposableOwner, Observable, styled} from "grainjs";
|
||||||
|
import {cssMenuItem} from "popweasel";
|
||||||
|
|
||||||
export interface ACUserItem extends ACItem {
|
export interface ACUserItem extends ACItem {
|
||||||
value: string;
|
value: string;
|
||||||
@ -33,16 +32,17 @@ export function buildACMemberEmail(
|
|||||||
options: {
|
options: {
|
||||||
acIndex: ACIndex<ACUserItem>;
|
acIndex: ACIndex<ACUserItem>;
|
||||||
emailObs: Observable<string>;
|
emailObs: Observable<string>;
|
||||||
save: (value: string) => Promise<void> | void;
|
save: (value: string) => void;
|
||||||
isInputValid: Observable<boolean>;
|
|
||||||
prompt?: {email: string},
|
prompt?: {email: string},
|
||||||
},
|
},
|
||||||
...args: DomElementArg[]
|
...args: DomElementArg[]
|
||||||
) {
|
) {
|
||||||
const { acIndex, emailObs, save, isInputValid, prompt } = options;
|
const {acIndex, emailObs, save, prompt} = options;
|
||||||
const acHolder = Holder.create<Autocomplete<ACUserItem>>(owner);
|
const acHolder = Holder.create<Autocomplete<ACUserItem>>(owner);
|
||||||
let emailInput: HTMLInputElement;
|
let emailInput: HTMLInputElement;
|
||||||
|
|
||||||
|
const isValid = Observable.create(owner, true);
|
||||||
|
|
||||||
const isOpen = () => !acHolder.isEmpty();
|
const isOpen = () => !acHolder.isEmpty();
|
||||||
const acOpen = () => acHolder.isEmpty() && Autocomplete.create(acHolder, emailInput, acOptions);
|
const acOpen = () => acHolder.isEmpty() && Autocomplete.create(acHolder, emailInput, acOptions);
|
||||||
const acClose = () => acHolder.clear();
|
const acClose = () => acHolder.clear();
|
||||||
@ -52,7 +52,7 @@ export function buildACMemberEmail(
|
|||||||
emailInput.value = emailObs.get();
|
emailInput.value = emailObs.get();
|
||||||
emailInput.focus();
|
emailInput.focus();
|
||||||
};
|
};
|
||||||
const openOrCommit = () => {
|
const onEnter = () => {
|
||||||
isOpen() ? commitIfValid() : acOpen();
|
isOpen() ? commitIfValid() : acOpen();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,9 +62,11 @@ export function buildACMemberEmail(
|
|||||||
emailObs.set(item.value);
|
emailObs.set(item.value);
|
||||||
}
|
}
|
||||||
emailInput.setCustomValidity("");
|
emailInput.setCustomValidity("");
|
||||||
|
isValid.set(emailInput.checkValidity());
|
||||||
|
|
||||||
const selectedEmail = item?.value || emailObs.get();
|
const selectedEmail = item?.value || emailObs.get();
|
||||||
try {
|
try {
|
||||||
if (selectedEmail && isInputValid.get()) {
|
if (selectedEmail && isValid.get()) {
|
||||||
save(emailObs.get());
|
save(emailObs.get());
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@ -79,7 +81,7 @@ export function buildACMemberEmail(
|
|||||||
const cleanText = normalizeText(text);
|
const cleanText = normalizeText(text);
|
||||||
const items = results.items
|
const items = results.items
|
||||||
.filter(item => item.cleanText.includes(cleanText))
|
.filter(item => item.cleanText.includes(cleanText))
|
||||||
.sort((a,b) => a.cleanText.localeCompare(b.cleanText));
|
.sort((a, b) => a.cleanText.localeCompare(b.cleanText));
|
||||||
results.items = items;
|
results.items = items;
|
||||||
if (!results.items.length) {
|
if (!results.items.length) {
|
||||||
const newObject = {
|
const newObject = {
|
||||||
@ -121,7 +123,7 @@ export function buildACMemberEmail(
|
|||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
const enableAdd: Computed<boolean> = computed((use) => Boolean(use(emailObs) && use(isInputValid)));
|
const enableAdd: Computed<boolean> = computed((use) => Boolean(use(emailObs) && use(isValid)));
|
||||||
|
|
||||||
const acOptions: IAutocompleteOptions<ACUserItem> = {
|
const acOptions: IAutocompleteOptions<ACUserItem> = {
|
||||||
attach: null,
|
attach: null,
|
||||||
@ -136,7 +138,7 @@ export function buildACMemberEmail(
|
|||||||
cssMailIcon("Mail"),
|
cssMailIcon("Mail"),
|
||||||
(emailInput = cssEmailInput(
|
(emailInput = cssEmailInput(
|
||||||
emailObs,
|
emailObs,
|
||||||
{onInput: true, isValid: isInputValid},
|
{onInput: true, isValid},
|
||||||
{type: "email", placeholder: "Enter email address"},
|
{type: "email", placeholder: "Enter email address"},
|
||||||
dom.on("input", acOpen),
|
dom.on("input", acOpen),
|
||||||
dom.on("focus", acOpen),
|
dom.on("focus", acOpen),
|
||||||
@ -144,51 +146,43 @@ export function buildACMemberEmail(
|
|||||||
dom.on("blur", acClose),
|
dom.on("blur", acClose),
|
||||||
dom.onKeyDown({
|
dom.onKeyDown({
|
||||||
Escape: finish,
|
Escape: finish,
|
||||||
Enter: openOrCommit,
|
Enter: onEnter,
|
||||||
ArrowDown: acOpen,
|
ArrowDown: acOpen,
|
||||||
Tab: commitIfValid,
|
Tab: commitIfValid,
|
||||||
}),
|
}),
|
||||||
...args
|
|
||||||
)),
|
)),
|
||||||
cssEmailInputContainer.cls('-green', enableAdd),
|
cssEmailInputContainer.cls('-green', enableAdd),
|
||||||
|
...args
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reset custom validity that we sometimes set.
|
||||||
|
owner.autoDispose(emailObs.addListener(() => emailInput.setCustomValidity("")));
|
||||||
|
|
||||||
if (prompt) { setTimeout(() => emailInput.focus(), 0); }
|
if (prompt) { setTimeout(() => emailInput.focus(), 0); }
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cssMemberPrimaryPlus = styled(
|
const cssMemberPrimaryPlus = styled(cssMemberPrimary, `
|
||||||
cssMemberPrimary,
|
|
||||||
`
|
|
||||||
.${cssSelectItem.className}.selected & {
|
.${cssSelectItem.className}.selected & {
|
||||||
color: ${theme.menuItemSelectedFg};
|
color: ${theme.menuItemSelectedFg};
|
||||||
}
|
}
|
||||||
`
|
`);
|
||||||
);
|
|
||||||
|
|
||||||
const cssMemberSecondaryPlus = styled(
|
const cssMemberSecondaryPlus = styled(cssMemberSecondary, `
|
||||||
cssMemberSecondary,
|
|
||||||
`
|
|
||||||
.${cssSelectItem.className}.selected & {
|
.${cssSelectItem.className}.selected & {
|
||||||
color: ${theme.menuItemSelectedFg};
|
color: ${theme.menuItemSelectedFg};
|
||||||
}
|
}
|
||||||
`
|
`);
|
||||||
);
|
|
||||||
|
|
||||||
const cssMatchText = styled(
|
const cssMatchText = styled("span", `
|
||||||
"span",
|
|
||||||
`
|
|
||||||
color: ${theme.autocompleteMatchText};
|
color: ${theme.autocompleteMatchText};
|
||||||
.${cssSelectItem.className}.selected & {
|
.${cssSelectItem.className}.selected & {
|
||||||
color: ${theme.autocompleteSelectedMatchText};
|
color: ${theme.autocompleteSelectedMatchText};
|
||||||
}
|
}
|
||||||
`
|
`);
|
||||||
);
|
|
||||||
|
|
||||||
const cssUserImagePlus = styled(
|
const cssUserImagePlus = styled(cssUserImage, `
|
||||||
cssUserImage,
|
|
||||||
`
|
|
||||||
background-color: ${colors.lightGreen};
|
background-color: ${colors.lightGreen};
|
||||||
margin: auto 0;
|
margin: auto 0;
|
||||||
|
|
||||||
@ -200,5 +194,4 @@ const cssUserImagePlus = styled(
|
|||||||
background-color: ${theme.menuItemIconSelectedFg};
|
background-color: ${theme.menuItemIconSelectedFg};
|
||||||
color: ${theme.menuItemSelectedBg};
|
color: ${theme.menuItemSelectedBg};
|
||||||
}
|
}
|
||||||
`
|
`);
|
||||||
);
|
|
||||||
|
@ -21,6 +21,11 @@ export interface IAutocompleteOptions<Item extends ACItem> {
|
|||||||
// Popper options for positioning the popup.
|
// Popper options for positioning the popup.
|
||||||
popperOptions?: Partial<PopperOptions>;
|
popperOptions?: Partial<PopperOptions>;
|
||||||
|
|
||||||
|
// To which element to append the popup content. Null means triggerElem.parentNode; string is
|
||||||
|
// a selector for the closest matching ancestor of triggerElem, e.g. 'body'.
|
||||||
|
// Defaults to the document body.
|
||||||
|
attach?: Element|string|null;
|
||||||
|
|
||||||
// Given a search term, return the list of Items to render.
|
// Given a search term, return the list of Items to render.
|
||||||
search(searchText: string): Promise<ACResults<Item>>;
|
search(searchText: string): Promise<ACResults<Item>>;
|
||||||
|
|
||||||
@ -32,10 +37,6 @@ export interface IAutocompleteOptions<Item extends ACItem> {
|
|||||||
|
|
||||||
// A callback triggered when user clicks one of the choices.
|
// A callback triggered when user clicks one of the choices.
|
||||||
onClick?(): void;
|
onClick?(): void;
|
||||||
|
|
||||||
// To which element to append the popup content. Null means triggerElem.parentNode and is the
|
|
||||||
// default; string is a selector for the closest matching ancestor of triggerElem, e.g. 'body'.
|
|
||||||
attach?: Element|string|null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,6 +32,8 @@ export interface UserManagerModel {
|
|||||||
activeUser: FullUser|null; // Populated if current user is logged in.
|
activeUser: FullUser|null; // Populated if current user is logged in.
|
||||||
gristDoc: GristDoc|null; // Populated if there is an open document.
|
gristDoc: GristDoc|null; // Populated if there is an open document.
|
||||||
|
|
||||||
|
// Analyze the relation that users have to the resource or site.
|
||||||
|
annotate(): void;
|
||||||
// Resets all unsaved changes
|
// Resets all unsaved changes
|
||||||
reset(): void;
|
reset(): void;
|
||||||
// Recreate annotations, factoring in any changes on the back-end.
|
// Recreate annotations, factoring in any changes on the back-end.
|
||||||
@ -253,7 +255,6 @@ export class UserManagerModelImpl extends Disposable implements UserManagerModel
|
|||||||
return member.email === this.activeUser?.email;
|
return member.email === this.activeUser?.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Analyze the relation that users have to the resource or site.
|
|
||||||
public annotate() {
|
public annotate() {
|
||||||
// Only attempt for documents for now.
|
// Only attempt for documents for now.
|
||||||
// TODO: extend to workspaces.
|
// TODO: extend to workspaces.
|
||||||
|
@ -10,7 +10,7 @@ import {isLongerThan} from 'app/common/gutil';
|
|||||||
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 {Organization, PermissionData, UserAPI} from 'app/common/UserAPI';
|
import {Organization, PermissionData, UserAPI} from 'app/common/UserAPI';
|
||||||
import {Computed, Disposable, keyframes, observable, Observable, dom, DomElementArg, styled} from 'grainjs';
|
import {Computed, Disposable, dom, DomElementArg, keyframes, Observable, observable, styled} from 'grainjs';
|
||||||
import pick = require('lodash/pick');
|
import pick = require('lodash/pick');
|
||||||
|
|
||||||
import {ACIndexImpl, normalizeText} from 'app/client/lib/ACIndex';
|
import {ACIndexImpl, normalizeText} from 'app/client/lib/ACIndex';
|
||||||
@ -204,9 +204,8 @@ export class UserManager extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public buildDom() {
|
public buildDom() {
|
||||||
const acmemberEmail = this.autoDispose(new ACMemberEmail(
|
const acMemberEmail = this.autoDispose(new ACMemberEmail(
|
||||||
this._onAdd.bind(this),
|
this._onAdd.bind(this),
|
||||||
(member) => this._model.isActiveUser(member),
|
|
||||||
this._model.membersEdited.get(),
|
this._model.membersEdited.get(),
|
||||||
this._options.prompt,
|
this._options.prompt,
|
||||||
));
|
));
|
||||||
@ -219,7 +218,7 @@ export class UserManager extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
acmemberEmail.buildDom(),
|
acMemberEmail.buildDom(),
|
||||||
this._buildOptionsDom(),
|
this._buildOptionsDom(),
|
||||||
this._dom = shadowScroll(
|
this._dom = shadowScroll(
|
||||||
testId('um-members'),
|
testId('um-members'),
|
||||||
@ -531,7 +530,7 @@ function getUserItem(member: IEditableMember): ACUserItem {
|
|||||||
name: member.name,
|
name: member.name,
|
||||||
picture: member?.picture,
|
picture: member?.picture,
|
||||||
id: member.id,
|
id: member.id,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -539,11 +538,9 @@ function getUserItem(member: IEditableMember): ACUserItem {
|
|||||||
*/
|
*/
|
||||||
export class ACMemberEmail extends Disposable {
|
export class ACMemberEmail extends Disposable {
|
||||||
private _email = this.autoDispose(observable<string>(""));
|
private _email = this.autoDispose(observable<string>(""));
|
||||||
private _isValid = this.autoDispose(observable<boolean>(false));
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _onAdd: (email: string, role: roles.NonGuestRole) => void,
|
private _onAdd: (email: string, role: roles.NonGuestRole) => void,
|
||||||
private _isActiveUser: (member: IEditableMember) => boolean,
|
|
||||||
private _members: Array<IEditableMember>,
|
private _members: Array<IEditableMember>,
|
||||||
private _prompt?: {email: string}
|
private _prompt?: {email: string}
|
||||||
) {
|
) {
|
||||||
@ -562,7 +559,6 @@ export class ACMemberEmail extends Disposable {
|
|||||||
acIndex,
|
acIndex,
|
||||||
emailObs: this._email,
|
emailObs: this._email,
|
||||||
save: this._handleSave.bind(this),
|
save: this._handleSave.bind(this),
|
||||||
isInputValid: this._isValid,
|
|
||||||
prompt: this._prompt,
|
prompt: this._prompt,
|
||||||
},
|
},
|
||||||
testId('um-member-new')
|
testId('um-member-new')
|
||||||
@ -570,12 +566,7 @@ export class ACMemberEmail extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleSave(selectedEmail: string) {
|
private _handleSave(selectedEmail: string) {
|
||||||
const member = this._members.find(member => member.email === selectedEmail);
|
|
||||||
if (!member) {
|
|
||||||
this._onAdd(selectedEmail, roles.VIEWER);
|
this._onAdd(selectedEmail, roles.VIEWER);
|
||||||
} else if (!this._isActiveUser(member)) {
|
|
||||||
member?.effectiveAccess.set(roles.VIEWER);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user