Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2865: Various notification improvements #3531

Merged
merged 7 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@
"@types/json-stringify-safe": "^5.0.0",
"@types/lodash": "^4.14.171",
"@types/mustache": "^4.1.2",
"@types/notifyjs-browser": "0.0.0",
Copy link
Contributor Author

@fregante fregante May 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop outdated types (requires review)

I think that Notify.js’ types are somehow part of the public Alert brick’s config and the whole object was being passed as was to the notifier. However we kind of broke that months ago when we dropped the library. In practice I don’t think this was used for anything more than changing the className/type.

Now I'm making the last step of replacing the className setting with just type.

There's no migration path and I don't think it was ever necessary since error events should use error type, and so on…

Copy link
Contributor

@twschiller twschiller May 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm making the last step of replacing the className setting with just type.

It's probably fine, but I'd have to see if anyone (probably not) is using the following affordance in production: https://github.com/pixiebrix/pixiebrix-extension/pull/3531/files#r885082918

However we kind of broke that months ago when we dropped the library. In practice I don’t think this was used for anything more than changing the className/type.

Given your comment here, it sounds like if anyone was using it, we would have heard about it? If no one is, I will need to drop the support from menu item extension

What you're referring to is it that the old code destructured className? Or what exactly are you referring to?

{ message, config: { className } }: MessageConfig

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. When I changed library months ago I mapped the className to the type property because that's how our code was using the library.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. When I changed library months ago I mapped the className to the type property because that's how our code was using the library.

Re-read some of our old documentation. With the old library you could pass props for controlling whether it was dismissible, etc. It wasn't very well documented and wasn't exposed in the Page Editor, though

"@types/nunjucks": "^3.1.5",
"@types/object-hash": "^2.1.1",
"@types/papaparse": "^5.2.6",
Expand Down
30 changes: 11 additions & 19 deletions src/blocks/renderers/customForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@

import React from "react";
import { Renderer } from "@/types";
import { BlockArg, BlockOptions, ComponentRef, Schema, UiSchema } from "@/core";
import { BlockArg, ComponentRef, Schema, UiSchema } from "@/core";
import JsonSchemaForm from "@rjsf/bootstrap-4";
import { JsonObject } from "type-fest";
import { dataStore } from "@/background/messenger/api";
import reportError from "@/telemetry/reportError";
import { notifyResult } from "@/utils/notify";

import notify from "@/utils/notify";
import custom from "@/blocks/renderers/customForm.css?loadAsUrl";
import BootstrapStylesheet from "./BootstrapStylesheet";
import ImageCropWidget from "@/components/formBuilder/ImageCropWidget";
Expand Down Expand Up @@ -99,10 +97,11 @@ export class CustomFormRenderer extends Renderer {
},
};

async render(
{ recordId, schema, uiSchema }: BlockArg,
{ logger }: BlockOptions
): Promise<ComponentRef> {
async render({
recordId,
schema,
uiSchema,
}: BlockArg): Promise<ComponentRef> {
const formData = await dataStore.get(recordId);

console.debug("Building panel for record: [[ %s ]]", recordId);
Expand All @@ -117,19 +116,12 @@ export class CustomFormRenderer extends Renderer {
async onSubmit(values: JsonObject) {
try {
await dataStore.set(recordId, values);
notifyResult(logger.context.extensionId, {
message: "Saved record",
config: {
className: "success",
},
});
notify.success("Saved record");
} catch (error) {
reportError(error);
notifyResult(logger.context.extensionId, {
notify.error({
error,
message: "Error saving record",
config: {
className: "error",
},
reportError: false,
});
}
},
Expand Down
8 changes: 7 additions & 1 deletion src/contentScript/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import pDefer from "p-defer";
const SIDEBAR_WIDTH_PX = 400;
const PANEL_CONTAINER_SELECTOR = "#" + PANEL_FRAME_ID;
export const PANEL_HIDING_EVENT = "pixiebrix:hideSidebar";
export const SIDEBAR_WIDTH_CSS_PROPERTY = "--pb-sidebar-margin-right";

let renderSequenceNumber = 0;

Expand Down Expand Up @@ -80,11 +81,16 @@ function storeOriginalCSS() {

function adjustDocumentStyle(): void {
const $html = getHTMLElement();
$html.css("margin-right", `${originalMarginRight + SIDEBAR_WIDTH_PX}px`);
$html.css(
SIDEBAR_WIDTH_CSS_PROPERTY,
`${originalMarginRight + SIDEBAR_WIDTH_PX}px`
);
$html.css("margin-right", `var(${SIDEBAR_WIDTH_CSS_PROPERTY})`);
}

function restoreDocumentStyle(): void {
const $html = getHTMLElement();
$html.css(SIDEBAR_WIDTH_CSS_PROPERTY, "");
$html.css("margin-right", originalMarginRight);
}

Expand Down
45 changes: 27 additions & 18 deletions src/extensionPoints/menuItemExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import { uuidv4 } from "@/types/helpers";
import { checkAvailable } from "@/blocks/available";
import { castArray, cloneDeep, debounce, merge, once } from "lodash";
import { castArray, cloneDeep, debounce, once, pick } from "lodash";
import {
InitialValues,
reduceExtensionPipeline,
Expand Down Expand Up @@ -56,7 +56,7 @@ import { reportEvent } from "@/telemetry/events";
import notify, {
DEFAULT_ACTION_RESULTS,
MessageConfig,
notifyResult,
showNotification,
} from "@/utils/notify";
import { getNavigationId } from "@/contentScript/context";
import { rejectOnCancelled } from "@/utils";
Expand Down Expand Up @@ -105,7 +105,7 @@ export type MenuItemExtensionConfig = {
action: BlockConfig | BlockPipeline;

/**
* (Experimental) condition to determine whether or not to show the menu item
* (Experimental) condition to determine whether to show the menu item
* @see if
*/
if?: BlockConfig | BlockPipeline;
Expand All @@ -121,8 +121,17 @@ export type MenuItemExtensionConfig = {
*/
dynamicCaption?: boolean;

/**
* (Experimental) message to show on error running the extension
*/
onError?: MessageConfig;
/**
* (Experimental) message to show if the user cancelled the action (e.g., cancelled a form, or the Cancel brick ran)
*/
onCancel?: MessageConfig;
/**
* (Experimental) message to show on success when running the extension
*/
onSuccess?: MessageConfig;
};

Expand Down Expand Up @@ -460,9 +469,9 @@ export abstract class MenuItemExtensionPoint extends ExtensionPoint<MenuItemExte
caption,
dynamicCaption = false,
action: actionConfig,
onCancel,
onError,
onSuccess,
onCancel = {},
onError = {},
onSuccess = {},
icon = { id: "box", size: 18 },
} = extension.config;

Expand Down Expand Up @@ -557,22 +566,22 @@ export abstract class MenuItemExtensionPoint extends ExtensionPoint<MenuItemExte

extensionLogger.info("Successfully ran menu action");

notifyResult(
extension.id,
merge({}, onSuccess, DEFAULT_ACTION_RESULTS.success)
);
showNotification({
...DEFAULT_ACTION_RESULTS.success,
...pick(onSuccess, "message", "type"),
});
} catch (error) {
if (hasCancelRootCause(error)) {
notifyResult(
extension.id,
merge({}, onCancel, DEFAULT_ACTION_RESULTS.cancel)
);
showNotification({
...DEFAULT_ACTION_RESULTS.cancel,
...pick(onCancel, "message", "type"),
});
} else {
extensionLogger.error(error);
notifyResult(
extension.id,
merge({}, onError, DEFAULT_ACTION_RESULTS.error)
);
showNotification({
...DEFAULT_ACTION_RESULTS.error,
...pick(onError, "message", "type"),
});
}
}
});
Expand Down
9 changes: 6 additions & 3 deletions src/extensionPoints/quickBarExtension.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ import { castArray, cloneDeep, isEmpty } from "lodash";
import { checkAvailable, testMatchPatterns } from "@/blocks/available";
import { BusinessError, hasCancelRootCause } from "@/errors";
import reportError from "@/telemetry/reportError";
import notify, { DEFAULT_ACTION_RESULTS, notifyResult } from "@/utils/notify";
import notify, {
DEFAULT_ACTION_RESULTS,
showNotification,
} from "@/utils/notify";
import { reportEvent } from "@/telemetry/events";
import { selectEventData } from "@/telemetry/deployments";
import { selectExtensionContext } from "@/extensionPoints/helpers";
Expand Down Expand Up @@ -233,10 +236,10 @@ export abstract class QuickBarExtensionPoint extends ExtensionPoint<QuickBarConf
});
} catch (error) {
if (hasCancelRootCause(error)) {
notifyResult(extension.id, DEFAULT_ACTION_RESULTS.cancel);
showNotification(DEFAULT_ACTION_RESULTS.cancel);
} else {
extensionLogger.error(error);
notifyResult(extension.id, DEFAULT_ACTION_RESULTS.error);
showNotification(DEFAULT_ACTION_RESULTS.error);
}
}
},
Expand Down
9 changes: 9 additions & 0 deletions src/utils/notify.module.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
.message {
align-self: center;
}

.closeButton {
all: initial;
border: solid 2px #efefff;
border-radius: 0.4em;
line-height: 1.4em;
width: 1.5em;

flex-shrink: 0;
align-self: center;

padding-bottom: 0.1em; // Better vertical alignment
text-align: center;
margin-left: 0.5em;
margin-right: -10px; // In px because it has to undo/match the component’s px spacing
cursor: pointer;

&:hover,
Expand Down
51 changes: 21 additions & 30 deletions src/utils/notify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import reportError from "@/telemetry/reportError";
import { Except, RequireAtLeastOne } from "type-fest";
import { getErrorMessage } from "@/errors";
import { truncate } from "lodash";
import { SIDEBAR_WIDTH_CSS_PROPERTY } from "@/contentScript/sidebar";

const MINIMUM_NOTIFICATION_DURATION = 2000;

Expand Down Expand Up @@ -71,8 +72,8 @@ const Message: React.VoidFunctionComponent<{
id: string;
dismissable: boolean;
}> = ({ message, id, dismissable }) => (
<span>
{message}
<>
<span className={styles.message}>{message}</span>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always place close button on the right + less margin-right

Before

Screen Shot 4

After

Screen Shot 3

{dismissable ? (
<button
className={styles.closeButton}
Expand All @@ -83,12 +84,12 @@ const Message: React.VoidFunctionComponent<{
×
</button>
) : undefined}
</span>
</>
);

function getMessageDisplayTime(message: string): number {
const wpm = 100; // 180 is the average words read per minute, make it slower
return Math.min(
return Math.max(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix Notification timer (it was being ignored)

Fixed: All the notifications without a specified duration would last 2 seconds regardless of length

MINIMUM_NOTIFICATION_DURATION,
(message.split(" ").length / wpm) * 60_000
);
Expand All @@ -115,8 +116,6 @@ export function showNotification({
/** Only errors are reported by default */
reportError: willReport = type === "error",
}: RequireAtLeastOne<Notification, "message" | "error">): string {
const options = { id, duration };

if (error) {
if (!message) {
message = getErrorMessage(error);
Expand All @@ -136,16 +135,24 @@ export function showNotification({
type = "error";
}

const options = {
id,
duration,
// Keep the notification centered on the document even when the sidebar is open
style: { marginLeft: `calc(var(${SIDEBAR_WIDTH_CSS_PROPERTY}, 0) * -1)` },
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep notifications centered even with sidebar open

Before

This covers our own UI

Screen Shot 8

After

Keeps the notification out of the sidebar, which could theoretically have its own notifications

Screen Shot 7

Demo

Screen.Recording.mov

};
const component = <Message {...{ message, id, dismissable }} />;

switch (type) {
case "error":
case "success":
case "loading":
// eslint-disable-next-line security/detect-object-injection -- Filtered
toast[type](<Message {...{ message, id, dismissable }} />, options);
toast[type](component, options);
break;

default:
toast(<Message {...{ message, id, dismissable }} />, options);
toast(component, options);
}

if (willReport) {
Expand All @@ -162,38 +169,22 @@ export function hideNotification(id: string): void {
export const DEFAULT_ACTION_RESULTS = {
error: {
message: "Error running action",
config: {
className: "error",
},
type: "error",
reportError: false,
},
cancel: {
message: "The action was cancelled",
config: {
className: "info",
},
type: "info",
},
success: {
message: "Successfully ran action",
config: {
className: "success",
},
type: "success",
},
};
} as const;

export interface MessageConfig {
message: string;
config: Partial<NotificationOptions>;
}

export function notifyResult(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped, showNotification takes its place. extensionId wasn't used and now it just matches that function

extensionId: string,
{ message, config: { className } }: MessageConfig
): void {
showNotification({
message,
type: className as NotificationType,
reportError: false,
});
type: NotificationType;
}

// Private method to prevent adding logic to the `notify.*` helpers.
Expand Down