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.
|
// Call prepare() with transitions turned off.
|
||||||
|
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;
|
const prior = elem.style.transitionProperty;
|
||||||
elem.style.transitionProperty = 'none';
|
elem.style.transitionProperty = 'none';
|
||||||
prepare(elem, val);
|
prepare();
|
||||||
|
|
||||||
// Recompute styles while transitions are off. See https://stackoverflow.com/a/16575811/328565
|
// 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
|
// for explanation and https://stackoverflow.com/a/31862081/328565 for the recommendation used
|
||||||
// here to trigger a style computation without a reflow.
|
// here to trigger a style computation without a reflow.
|
||||||
window.getComputedStyle(elem).opacity; // tslint:disable-line:no-unused-expression
|
window.getComputedStyle(elem).opacity; // tslint:disable-line:no-unused-expression
|
||||||
|
|
||||||
// Restore transitions before run().
|
// Restore transitions.
|
||||||
elem.style.transitionProperty = prior;
|
elem.style.transitionProperty = prior;
|
||||||
}
|
|
||||||
run(elem, val);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for waiting for an active transition to end. Beyond listening to 'transitionend', it
|
* Helper for waiting for an active transition to end. Beyond listening to 'transitionend', it
|
||||||
* does a few things:
|
* 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
|
* When the transition ends, TransitionWatcher disposes itself. Its onDispose() method allows
|
||||||
* registering callbacks.
|
* registering callbacks.
|
||||||
*/
|
*/
|
||||||
class TransitionWatcher extends Disposable {
|
export class TransitionWatcher extends Disposable {
|
||||||
private _propertyName: string;
|
private _propertyName: string;
|
||||||
private _durationMs: number;
|
private _durationMs: number;
|
||||||
private _timer: ReturnType<typeof setTimeout>;
|
private _timer: ReturnType<typeof setTimeout>;
|
||||||
|
@ -18,6 +18,11 @@ export type UserPrefs = Prefs;
|
|||||||
export interface UserOrgPrefs extends Prefs {
|
export interface UserOrgPrefs extends Prefs {
|
||||||
docMenuSort?: SortPref;
|
docMenuSort?: SortPref;
|
||||||
docMenuView?: ViewPref;
|
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;
|
export type OrgPrefs = Prefs;
|
||||||
|
@ -336,6 +336,21 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
throw new Error(`Cannot testGetId(${name})`);
|
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> {
|
public getUserByKey(apiKey: string): Promise<User|undefined> {
|
||||||
return User.findOne({apiKey});
|
return User.findOne({apiKey});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user