(core) Changing shortcuts for adding and removing rows

Summary:
New shortcuts for removing and adding rows.
For adding a row we now have Mod+(Shift)+Enter
For removing rows we now have Mod+Delete/Mod+Backspace

Before removing rows, the user is prompted to confirm, this prompt
can be dismissed and this setting can be remembered. User needs
to confirm only when using shortcut.

Old shortcuts are still active and shows information about this change.
This information is shown only once, after this shortcuts have default
behavior (zooming).
New users don't see this explanation.

Test Plan: Updated

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3655
This commit is contained in:
Jarosław Sadziński
2022-10-21 12:55:01 +02:00
parent 0a8ce2178a
commit 6460c22a89
21 changed files with 582 additions and 105 deletions

View File

@@ -11,7 +11,7 @@ import {Features, isLegacyPlan, Product} from 'app/common/Features';
import {GristLoadConfig} from 'app/common/gristUrls';
import {FullUser} from 'app/common/LoginSessionAPI';
import {LocalPlugin} from 'app/common/plugin';
import {UserPrefs} from 'app/common/Prefs';
import {DeprecationWarning, DismissedPopup, UserPrefs} from 'app/common/Prefs';
import {isOwner} from 'app/common/roles';
import {getTagManagerScript} from 'app/common/tagManager';
import {getDefaultThemePrefs, Theme, ThemeAppearance, ThemeColors, ThemePrefs,
@@ -22,6 +22,7 @@ import {getOrgName, Organization, OrgError, UserAPI, UserAPIImpl} from 'app/comm
import {getUserPrefObs, getUserPrefsObs} from 'app/client/models/UserPrefs';
import {bundleChanges, Computed, Disposable, Observable, subscribe} from 'grainjs';
// Reexported for convenience.
export {reportError} from 'app/client/models/errors';
export type PageType = "doc" | "home" | "billing" | "welcome";
@@ -60,8 +61,10 @@ export interface TopAppModel {
fetchUsersAndOrgs(): Promise<void>;
}
// AppModel is specific to the currently loaded organization and active user. It gets rebuilt when
// we switch the current organization or the current user.
/**
* AppModel is specific to the currently loaded organization and active user. It gets rebuilt when
* we switch the current organization or the current user.
*/
export interface AppModel {
topAppModel: TopAppModel;
api: UserAPI;
@@ -83,6 +86,14 @@ export interface AppModel {
userPrefsObs: Observable<UserPrefs>;
themePrefs: Observable<ThemePrefs>;
currentTheme: Computed<Theme>;
/**
* Popups that user has seen.
*/
dismissedPopups: Observable<DismissedPopup[]>;
/**
* Deprecation messages that user has seen.
*/
deprecatedWarnings: Observable<DeprecationWarning[]>;
pageType: Observable<PageType>;
@@ -222,6 +233,12 @@ export class AppModelImpl extends Disposable implements AppModel {
}) as Observable<ThemePrefs>;
public readonly currentTheme = this._getCurrentThemeObs();
public readonly dismissedPopups =
getUserPrefObs(this.userPrefsObs, 'dismissedPopups', { defaultValue: [] }) as Observable<DismissedPopup[]>;
public readonly deprecatedWarnings = getUserPrefObs(this.userPrefsObs, 'seenDeprecatedWarnings',
{ defaultValue: []}) as Observable<DeprecationWarning[]>;
// Get the current PageType from the URL.
public readonly pageType: Observable<PageType> = Computed.create(this, urlState().state,
(use, state) => (state.doc ? "doc" : (state.billing ? "billing" : (state.welcome ? "welcome" : "home"))));

View File

@@ -26,7 +26,7 @@ interface INotifier {
getFullAppErrors(): IAppError[];
}
interface INotification extends Expirable {
export interface INotification extends Expirable {
expire(): Promise<void>;
}
@@ -34,11 +34,19 @@ export interface IProgress extends Expirable {
setProgress(percent: number): void;
}
/**
* Custom action to be shown as a notification with a handler.
*/
export interface CustomAction { label: string, action: () => void }
/**
* A string, or a function that builds dom.
*/
export type MessageType = string | (() => DomElementArg);
// Identifies supported actions. These are implemented in NotifyUI.
export type NotifyAction = 'upgrade' | 'renew' | 'personal' | 'report-problem' | 'ask-for-help';
export type NotifyAction = 'upgrade' | 'renew' | 'personal' | 'report-problem' | 'ask-for-help' | CustomAction;
export interface INotifyOptions {
message: string | (() => DomElementArg); // A string, or a function that builds dom.
message: MessageType; // A string, or a function that builds dom.
timestamp?: number;
title?: string;
canUserClose?: boolean;
@@ -224,7 +232,7 @@ export class Notifier extends Disposable implements INotifier {
* Additional option level, can be used to style the notification to like a success, warning,
* info or error message.
*/
public createUserMessage(message: string, options: Partial<INotifyOptions> = {}): INotification {
public createUserMessage(message: MessageType, options: Partial<INotifyOptions> = {}): INotification {
const timestamp = Date.now();
if (options.actions && options.actions.includes('ask-for-help')) {
// If user should be able to ask for help, add this error to the notifier dropdown too for a

View File

@@ -1,6 +1,6 @@
import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals';
import * as log from 'app/client/lib/log';
import {INotifyOptions, Notifier} from 'app/client/models/NotifyModel';
import {INotification, INotifyOptions, MessageType, Notifier} from 'app/client/models/NotifyModel';
import {ApiErrorDetails} from 'app/common/ApiError';
import {fetchFromHome, pageHasHome} from 'app/common/urlUtils';
import isError = require('lodash/isError');
@@ -44,9 +44,9 @@ export function getAppErrors(): string[] {
/**
* Shows normal notification without any styling or icon.
*/
export function reportMessage(msg: string, options?: Partial<INotifyOptions>) {
export function reportMessage(msg: MessageType, options?: Partial<INotifyOptions>): INotification|undefined {
if (_notifier && !_notifier.isDisposed()) {
_notifier.createUserMessage(msg, {
return _notifier.createUserMessage(msg, {
...options
});
}
@@ -60,14 +60,14 @@ export function reportWarning(msg: string, options?: Partial<INotifyOptions>) {
options = {level: 'warning', ...options};
log.warn(`${options.level}: `, msg);
_logError(msg);
reportMessage(msg, options);
return reportMessage(msg, options);
}
/**
* Shows success toast notification (with green styling).
*/
export function reportSuccess(msg: string, options?: Partial<INotifyOptions>) {
reportMessage(msg, {level: 'success', ...options});
export function reportSuccess(msg: MessageType, options?: Partial<INotifyOptions>) {
return reportMessage(msg, {level: 'success', ...options});
}
/**