5674 adds ability to view truncated text in notification toast as a dialog

This commit is contained in:
Seyi Adebajo 2018-05-21 16:31:20 -07:00
parent 4cf03ed9e2
commit b47317dcd2
7 changed files with 160 additions and 38 deletions

View File

@ -66,6 +66,7 @@ import { emptyRegexSource } from 'wherehows-web/utils/validators/regexp';
import { NonIdLogicalType } from 'wherehows-web/constants/datasets/compliance';
import { pick } from 'lodash';
import { trackableEvent, TrackableEventCategory } from 'wherehows-web/constants/analytics/event-tracking';
import { notificationDialogActionFactory } from 'wherehows-web/utils/notifications/notifications';
const {
complianceDataException,
@ -860,7 +861,7 @@ export default class DatasetCompliance extends Component {
* @return {Promise<void>}
*/
showPurgeExemptionWarning(this: DatasetCompliance): Promise<void> {
const dialogActions = <IConfirmOptions['dialogActions']>{};
const { dialogActions, dismissedOrConfirmed } = notificationDialogActionFactory();
get(this, 'notifications').notify(NotificationEvent.confirm, {
header: 'Confirm purge exemption',
@ -869,10 +870,7 @@ export default class DatasetCompliance extends Component {
dialogActions
});
return new Promise((resolve, reject): void => {
dialogActions['didConfirm'] = (): void => resolve();
dialogActions['didDismiss'] = (): void => reject();
});
return dismissedOrConfirmed;
}
/**
@ -1127,9 +1125,9 @@ export default class DatasetCompliance extends Component {
/**
* Handles post processing tasks after the purge policy step has been completed
* @returns {(Promise<void | {}>)}
* @returns {(Promise<void>)}
*/
didEditPurgePolicy(this: DatasetCompliance): Promise<void | {}> {
didEditPurgePolicy(this: DatasetCompliance): Promise<void> {
const { complianceType = null } = get(this, 'complianceInfo') || {};
if (!complianceType) {

View File

@ -2,6 +2,8 @@ import Service from '@ember/service';
import { setProperties, get, set } from '@ember/object';
import { delay } from 'wherehows-web/utils/promise-delay';
import { action } from '@ember-decorators/object';
import { fleece } from 'wherehows-web/utils/object';
import { notificationDialogActionFactory } from 'wherehows-web/utils/notifications/notifications';
/**
* Flag indicating the current notification queue is being processed
@ -10,7 +12,7 @@ import { action } from '@ember-decorators/object';
let isBuffering = false;
/**
* String literal of available notifications
* String enum of available notifications
*/
export enum NotificationEvent {
success = 'success',
@ -20,9 +22,12 @@ export enum NotificationEvent {
}
/**
* String literal for notification types
* String enum of notification types
*/
type NotificationType = 'modal' | 'toast';
enum NotificationType {
Modal = 'modal',
Toast = 'toast'
}
/**
* Describes the proxy handler for INotifications
@ -37,10 +42,15 @@ interface INotificationHandlerTrap<T, K extends keyof T> {
* @interface IConfirmOptions
*/
export interface IConfirmOptions {
// Header text for the confirmation dialog
header: string;
// Content to be displayed in the confirmation dialog
content: string;
dismissButtonText?: string;
confirmButtonText?: string;
// Text for button to dismiss dialog action, if false, button will not be rendered
dismissButtonText?: string | false;
// Text for button to confirm dialog action, if false, button will not be rendered
confirmButtonText?: string | false;
// Action handlers for dialog button on dismissal or otherwise
dialogActions: {
didConfirm: () => any;
didDismiss: () => any;
@ -64,7 +74,7 @@ interface IToast {
interface INotification {
// The properties for the notification
props: IConfirmOptions | IToast;
// The type if the notification
// The type of the notification
type: NotificationType;
// Object holding the queue state for the notification
notificationResolution: INotificationResolver;
@ -119,7 +129,7 @@ const makeToast = (props: IToast): INotification => {
return {
props,
type: 'toast',
type: NotificationType.Toast,
notificationResolution
};
};
@ -131,18 +141,27 @@ const notificationHandlers: INotificationHandler = {
* @return {INotification}
*/
confirm(props: IConfirmOptions): INotification {
let notificationResolution: INotificationResolver = {
const notificationResolution: INotificationResolver = {
get queueAwaiter() {
return createNotificationAwaiter(this);
}
};
// Set default values for button text if none are provided by consumer
props = { dismissButtonText: 'No', confirmButtonText: 'Yes', ...props };
const { dismissButtonText, confirmButtonText } = props;
// Removes dismiss or confirm buttons if set to false
let resolvedProps: IConfirmOptions =
dismissButtonText === false
? <IConfirmOptions>fleece<IConfirmOptions, 'dismissButtonText'>(['dismissButtonText'])(props)
: props;
resolvedProps =
confirmButtonText === false
? <IConfirmOptions>fleece<IConfirmOptions, 'confirmButtonText'>(['confirmButtonText'])(props)
: props;
return {
props,
type: 'modal',
props: resolvedProps,
type: NotificationType.Modal,
notificationResolution
};
},
@ -223,7 +242,7 @@ const asyncDequeue = function(notificationsQueue: Array<INotification>) {
* @param {INotification} notification
* @param {Array<INotification>} notificationsQueue
*/
const enqueue = (notification: INotification, notificationsQueue: Array<INotification>) => {
const enqueue = (notification: INotification, notificationsQueue: Array<INotification>): void => {
notificationsQueue.unshift(notification);
asyncDequeue(notificationsQueue);
};
@ -263,12 +282,14 @@ export default class Notifications extends Service {
* @param {INotification} notification
*/
setCurrentNotification = async (notification: INotification) => {
if (notification.type === 'modal') {
setProperties<Notifications, 'modal' | 'isShowingModal'>(this, { modal: notification, isShowingModal: true });
const { type, props } = notification;
if (type === NotificationType.Modal) {
setProperties(this, { modal: notification, isShowingModal: true });
} else {
const { props } = notification;
const toastDelay = delay((<IToast>props).duration);
setProperties<Notifications, 'toast' | 'isShowingToast'>(this, { toast: notification, isShowingToast: true });
setProperties(this, { toast: notification, isShowingToast: true });
if (!(<IToast>props).isSticky) {
await toastDelay;
@ -297,21 +318,24 @@ export default class Notifications extends Service {
/**
* Removes the current toast from view and invokes the notification resolution resolver
* @memberof Notifications
*/
@action
dismissToast(this: Notifications) {
dismissToast() {
const {
notificationResolution: { onComplete }
}: INotification = get(this, 'toast');
set(this, 'isShowingToast', false);
onComplete && onComplete();
}
/**
* Ignores the modal, invokes the user supplied didDismiss callback
* @memberof Notifications
*/
@action
dismissModal(this: Notifications) {
dismissModal() {
const {
props,
notificationResolution: { onComplete }
@ -327,13 +351,15 @@ export default class Notifications extends Service {
/**
* Confirms the dialog and invokes the user supplied didConfirm callback
* @memberof Notifications
*/
@action
confirmModal(this: Notifications) {
confirmModal() {
const {
props,
notificationResolution: { onComplete }
}: INotification = get(this, 'modal');
if ((<IConfirmOptions>props).dialogActions) {
const { didConfirm } = (<IConfirmOptions>props).dialogActions;
set(this, 'isShowingModal', false);
@ -341,4 +367,27 @@ export default class Notifications extends Service {
onComplete && onComplete();
}
}
/**
* Renders a dialog with the full text of the last IToast instance content,
* with the option to dismiss the modal
* @memberof Notifications
*/
@action
async showContentDetail() {
const { dialogActions } = notificationDialogActionFactory();
const {
props: { content }
} = get(this, 'toast');
this.notify(NotificationEvent.confirm, {
header: 'Notification Detail',
content,
dialogActions,
dismissButtonText: false,
confirmButtonText: 'Dismiss'
});
this.dismissToast.call(this);
}
}

View File

@ -2,6 +2,9 @@
/// Contains the states --info, --success, --error
/// Also includes a __dismiss element when applicable i.e.shown
.notification-toast {
$right-index: 7;
$right-region-width: item-spacing($right-index);
position: fixed;
text-align: left;
border-radius: 2px;
@ -13,14 +16,14 @@
background: set-color(white, base);
padding: 0;
overflow: hidden;
height: 96px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .1), 0 6px 9px rgba(0, 0, 0, .2);
height: item-spacing(5) * 5;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 6px 9px rgba(0, 0, 0, 0.2);
margin-top: item-spacing(3);
&__content {
overflow: hidden;
height: 100%;
padding: item-spacing(4 7 4 8);
padding: item-spacing(5 $right-index 5 8);
&::before {
content: '';
@ -32,6 +35,12 @@
z-index: 2;
}
&__msg {
overflow: hidden;
height: 100%;
margin: 0;
}
&--info {
&::before {
background-color: set-color(grey, light);
@ -61,7 +70,7 @@
top: 0;
padding: 0;
z-index: 1;
width: item-spacing(7);
width: $right-region-width;
font-size: item-spacing(6);
cursor: pointer;
@ -69,4 +78,12 @@
background-color: set-color(grey, light);
}
}
&__content-detail {
&#{&} {
position: absolute;
bottom: item-spacing(1);
right: $right-region-width;
}
}
}

View File

@ -19,12 +19,16 @@
</section>
<footer class="notification-confirm-modal__footer">
<button class="nacho-button--large nacho-button--secondary" {{action "onClose"}}>
{{dismissButtonText}}
</button>
{{#unless (eq dismissButtonText false)}}
<button class="nacho-button--large nacho-button--secondary" {{action "onClose"}}>
{{dismissButtonText}}
</button>
{{/unless}}
<button class="nacho-button nacho-button--large-inverse" {{action "onConfirm"}}>
{{confirmButtonText}}
</button>
{{#unless (eq confirmButtonText false)}}
<button class="nacho-button nacho-button--large-inverse" {{action "onConfirm"}}>
{{confirmButtonText}}
</button>
{{/unless}}
</footer>
{{/modal-dialog}}

View File

@ -21,7 +21,17 @@
<i class="notification-toast__icon"></i>
<div class="notification-toast__content notification-toast__content--{{service.toast.props.type}}">
<p>{{service.toast.props.content}}</p>
<p class="notification-toast__content__msg">
{{split-text service.toast.props.content 140}}
</p>
{{#if (gt service.toast.props.content.length 140)}}
<button
class="nacho-button nacho-button--tertiary notification-toast__content-detail"
onclick={{action "showContentDetail" target=service}}>
See Detail
</button>
{{/if}}
</div>
<button class="notification-toast__dismiss" onclick={{action "dismissToast" target=service}}>

View File

@ -13,7 +13,7 @@ import { Classification, ComplianceFieldIdValue, IdLogicalType } from 'wherehows
*/
interface IDatasetComplianceActions {
didEditCompliancePolicy: () => Promise<void>;
didEditPurgePolicy: () => Promise<{} | void>;
didEditPurgePolicy: () => Promise<void>;
didEditDatasetLevelCompliancePolicy: () => Promise<void>;
[K: string]: (...args: Array<any>) => any;
}

View File

@ -0,0 +1,44 @@
import { IConfirmOptions } from 'wherehows-web/services/notifications';
/**
* Defines the interface for properties used in proxy-ing or handling dialog's
* events async
* @interface INotificationDialogProps
*/
interface INotificationDialogProps {
dismissedOrConfirmed: Promise<void>;
dialogActions: IConfirmOptions['dialogActions'];
}
/**
* Creates an instance of INotificationDialogProps when invoked. Includes a promise for dismissing or
* confirming the dialog
* @returns {INotificationDialogProps}
*/
const notificationDialogActionFactory = (): INotificationDialogProps => {
let dialogActions = <IConfirmOptions['dialogActions']>{};
/**
* Inner function to create a dialog object
* @template T
* @param {((value?: T | PromiseLike<T>) => void)} resolveFn
* @param {(reason?: any) => void} rejectFn
* @returns {IConfirmOptions.dialogActions}
*/
const createDialogActions = <T>(
resolveFn: (value?: T | PromiseLike<T>) => void,
rejectFn: (reason?: any) => void
): IConfirmOptions['dialogActions'] => ({
didConfirm: () => resolveFn(),
didDismiss: () => rejectFn()
});
const dismissedOrConfirmed = new Promise<void>((resolve, reject): void => {
dialogActions = { ...dialogActions, ...createDialogActions(resolve, reject) };
});
return {
dismissedOrConfirmed,
dialogActions
};
};
export { notificationDialogActionFactory };