mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Show a welcome card when a user opens an example for the first time.
Summary: - The card includes an image, a brief description, and a link to the tutorial. - The left panel includes a link to the tutorial, and a button to reopen card. - Card is collapsed and expanded with a little animation. - Add a seenExamples pref for whether an example has been seen. - Store the pref in localStorage for anon user. Separately, added clearing of prefs of test users between tests, to avoid tests affecting unrelated tests. Test Plan: Added a browser test. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2602
This commit is contained in:
parent
526fda4eba
commit
166143557a
@ -57,22 +57,31 @@ export function transition<T>(obs: BindableValue<T>, trans: ITransitionLogic<T>)
|
||||
});
|
||||
|
||||
// Call prepare() with transitions turned off.
|
||||
const prior = elem.style.transitionProperty;
|
||||
elem.style.transitionProperty = 'none';
|
||||
prepare(elem, val);
|
||||
|
||||
// Recompute styles while transitions are off. See https://stackoverflow.com/a/16575811/328565
|
||||
// for explanation and https://stackoverflow.com/a/31862081/328565 for the recommendation used
|
||||
// here to trigger a style computation without a reflow.
|
||||
window.getComputedStyle(elem).opacity; // tslint:disable-line:no-unused-expression
|
||||
|
||||
// Restore transitions before run().
|
||||
elem.style.transitionProperty = prior;
|
||||
prepareForTransition(elem, () => prepare(elem, val));
|
||||
}
|
||||
run(elem, val);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call prepare() with transitions turned off. This allows preparing an element before another
|
||||
* change to properties actually gets animated using the element's transition settings.
|
||||
*/
|
||||
export function prepareForTransition(elem: HTMLElement, prepare: () => void) {
|
||||
const prior = elem.style.transitionProperty;
|
||||
elem.style.transitionProperty = 'none';
|
||||
prepare();
|
||||
|
||||
// Recompute styles while transitions are off. See https://stackoverflow.com/a/16575811/328565
|
||||
// for explanation and https://stackoverflow.com/a/31862081/328565 for the recommendation used
|
||||
// here to trigger a style computation without a reflow.
|
||||
window.getComputedStyle(elem).opacity; // tslint:disable-line:no-unused-expression
|
||||
|
||||
// Restore transitions.
|
||||
elem.style.transitionProperty = prior;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper for waiting for an active transition to end. Beyond listening to 'transitionend', it
|
||||
* does a few things:
|
||||
@ -86,7 +95,7 @@ export function transition<T>(obs: BindableValue<T>, trans: ITransitionLogic<T>)
|
||||
* When the transition ends, TransitionWatcher disposes itself. Its onDispose() method allows
|
||||
* registering callbacks.
|
||||
*/
|
||||
class TransitionWatcher extends Disposable {
|
||||
export class TransitionWatcher extends Disposable {
|
||||
private _propertyName: string;
|
||||
private _durationMs: number;
|
||||
private _timer: ReturnType<typeof setTimeout>;
|
||||
|
@ -18,6 +18,11 @@ export type UserPrefs = Prefs;
|
||||
export interface UserOrgPrefs extends Prefs {
|
||||
docMenuSort?: SortPref;
|
||||
docMenuView?: ViewPref;
|
||||
|
||||
// List of example docs that the user has seen and dismissed the welcome card for.
|
||||
// The numbers are the `id` from IExampleInfo in app/client/ui/ExampleInfo.
|
||||
// By living in UserOrgPrefs, this applies only to the examples-containing org.
|
||||
seenExamples?: number[];
|
||||
}
|
||||
|
||||
export type OrgPrefs = Prefs;
|
||||
|
@ -336,6 +336,21 @@ export class HomeDBManager extends EventEmitter {
|
||||
throw new Error(`Cannot testGetId(${name})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all user preferences associated with the given email addresses.
|
||||
* For use in tests.
|
||||
*/
|
||||
public async testClearUserPrefs(emails: string[]) {
|
||||
return await this._connection.transaction(async manager => {
|
||||
for (const email of emails) {
|
||||
const user = await this.getUserByLogin(email, undefined, manager);
|
||||
if (user) {
|
||||
await manager.delete(Pref, {userId: user.id});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getUserByKey(apiKey: string): Promise<User|undefined> {
|
||||
return User.findOne({apiKey});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user