From bb8e91457c1dc38685434f75c7111274d6dabf3c Mon Sep 17 00:00:00 2001 From: Ed Carroll Date: Fri, 11 Aug 2017 17:07:21 +0100 Subject: [PATCH 1/3] feat(message): Initial notification functionality --- .../page-content/page-content.component.css | 2 +- .../page-title/page-title.component.css | 2 +- .../pages/collections/message/message.page.ts | 9 +- .../app/pages/development/test/test.page.html | 5 +- .../app/pages/development/test/test.page.ts | 16 +- .../modules/accordion/accordion.page.html | 2 +- .../pages/modules/collapse/collapse.page.html | 2 +- .../modules/datepicker/datepicker.page.html | 6 +- .../pages/modules/dropdown/dropdown.page.html | 2 +- .../src/app/pages/modules/popup/popup.page.ts | 2 +- .../pages/modules/progress/progress.page.ts | 18 ++ .../app/pages/modules/search/search.page.html | 2 +- .../app/pages/modules/select/select.page.html | 4 +- .../message/classes/active-message.ts | 40 ++++ .../message/classes/message-config.ts | 49 +++++ .../message/classes/message-controller.ts | 44 ++++ .../message/components/message-container.ts | 91 ++++++++ .../components/message-global-container.ts | 79 +++++++ src/collections/message/components/message.ts | 196 +++++++++++++++--- src/collections/message/index.ts | 8 + src/collections/message/message.module.ts | 26 ++- src/collections/message/public.ts | 9 +- .../message/services/message-service.ts | 69 ++++++ src/misc/util/helpers/util.ts | 6 + src/modules/progress/components/progress.ts | 22 +- .../classes/transition-controller.ts | 47 +++-- 26 files changed, 692 insertions(+), 66 deletions(-) create mode 100644 src/collections/message/classes/active-message.ts create mode 100644 src/collections/message/classes/message-config.ts create mode 100644 src/collections/message/classes/message-controller.ts create mode 100644 src/collections/message/components/message-container.ts create mode 100644 src/collections/message/components/message-global-container.ts create mode 100644 src/collections/message/services/message-service.ts diff --git a/demo/src/app/components/page-content/page-content.component.css b/demo/src/app/components/page-content/page-content.component.css index 87ba68ea3..bd3b20502 100644 --- a/demo/src/app/components/page-content/page-content.component.css +++ b/demo/src/app/components/page-content/page-content.component.css @@ -23,7 +23,7 @@ } } -@media only screen and (max-width: 425px) { +@media only screen and (max-width: 480px) { :host.ui.main.container { margin-left: 1em !important; margin-right: 1em !important; diff --git a/demo/src/app/components/page-title/page-title.component.css b/demo/src/app/components/page-title/page-title.component.css index 27e0f3100..6a8814696 100644 --- a/demo/src/app/components/page-title/page-title.component.css +++ b/demo/src/app/components/page-title/page-title.component.css @@ -26,7 +26,7 @@ } } -@media only screen and (max-width: 425px) { +@media only screen and (max-width: 480px) { :host.ui.masthead.segment { margin-bottom: 1em; padding: 1em; diff --git a/demo/src/app/pages/collections/message/message.page.ts b/demo/src/app/pages/collections/message/message.page.ts index 86222cb2b..b1fa3faf6 100644 --- a/demo/src/app/pages/collections/message/message.page.ts +++ b/demo/src/app/pages/collections/message/message.page.ts @@ -11,7 +11,7 @@ const exampleStandardTemplate = ` `; const exampleNoDismissTemplate = ` - +
Attached message!
@@ -44,7 +44,7 @@ export class MessagePage { selector: "", properties: [ { - name: "isDismissable", + name: "hasDismissButton", type: "boolean", description: "Sets whether or not the message has a dismiss button.", defaultValue: "true" @@ -67,6 +67,11 @@ export class MessagePage { name: "dismiss", type: "void", description: "Fires when the message is dismissed by the user." + }, + { + name: "click", + type: "void", + description: "Fires when the message is clicked by the user." } ] } diff --git a/demo/src/app/pages/development/test/test.page.html b/demo/src/app/pages/development/test/test.page.html index 011d256d2..8465bd8c3 100644 --- a/demo/src/app/pages/development/test/test.page.html +++ b/demo/src/app/pages/development/test/test.page.html @@ -7,6 +7,9 @@

Examples

- + + +
+
\ No newline at end of file diff --git a/demo/src/app/pages/development/test/test.page.ts b/demo/src/app/pages/development/test/test.page.ts index 05b9b67d0..3a4a01c20 100644 --- a/demo/src/app/pages/development/test/test.page.ts +++ b/demo/src/app/pages/development/test/test.page.ts @@ -1,10 +1,24 @@ import { Component, AfterViewInit, ViewChild, TemplateRef } from "@angular/core"; import { FormControl, Validators } from "@angular/forms"; +import { MessageController, SuiMessageService, MessagePosition, MessageConfig, MessageState } from "ng2-semantic-ui"; @Component({ selector: "demo-page-test", templateUrl: "./test.page.html" }) export class TestPage { - constructor() {} + public controller:MessageController; + + constructor(private _messageService:SuiMessageService) { + this.controller = new MessageController(); + this._messageService.position = MessagePosition.BottomRight; + this._messageService.isNewestOnTop = true; + } + + public open():void { + const message = new MessageConfig(Date.now().toString(), MessageState.Default, "Header"); + message.hasProgress = true; + // this.controller.show(message); + this._messageService.show(message); + } } diff --git a/demo/src/app/pages/modules/accordion/accordion.page.html b/demo/src/app/pages/modules/accordion/accordion.page.html index d4636126f..c5ea6c448 100644 --- a/demo/src/app/pages/modules/accordion/accordion.page.html +++ b/demo/src/app/pages/modules/accordion/accordion.page.html @@ -5,7 +5,7 @@ - +
Important Note

The accordion depends on the Web Animations API, which requires a polyfill for full browser diff --git a/demo/src/app/pages/modules/collapse/collapse.page.html b/demo/src/app/pages/modules/collapse/collapse.page.html index 6fcff0905..55209d5b6 100644 --- a/demo/src/app/pages/modules/collapse/collapse.page.html +++ b/demo/src/app/pages/modules/collapse/collapse.page.html @@ -14,7 +14,7 @@

Collapse

- +
Important Note

The collapse component uses the Web Animations API.

diff --git a/demo/src/app/pages/modules/datepicker/datepicker.page.html b/demo/src/app/pages/modules/datepicker/datepicker.page.html index 49992434e..65271f336 100644 --- a/demo/src/app/pages/modules/datepicker/datepicker.page.html +++ b/demo/src/app/pages/modules/datepicker/datepicker.page.html @@ -5,7 +5,7 @@ - +

Important Note

The datepicker is a port of Semantic-UI-Calendar, @@ -48,7 +48,7 @@

Min & Max

- +
Timezones

The datepicker will always return a Date object in the local timezone. @@ -66,7 +66,7 @@

Mobile Fallback

- +
Localization

The datepicker has full localization support. Read the diff --git a/demo/src/app/pages/modules/dropdown/dropdown.page.html b/demo/src/app/pages/modules/dropdown/dropdown.page.html index 0887908cb..c94139e9e 100644 --- a/demo/src/app/pages/modules/dropdown/dropdown.page.html +++ b/demo/src/app/pages/modules/dropdown/dropdown.page.html @@ -36,7 +36,7 @@

Versatility

- +
Selection Dropdowns

Looking for selection dropdowns? That functionality is in diff --git a/demo/src/app/pages/modules/popup/popup.page.ts b/demo/src/app/pages/modules/popup/popup.page.ts index 509d0732e..de01b1a71 100644 --- a/demo/src/app/pages/modules/popup/popup.page.ts +++ b/demo/src/app/pages/modules/popup/popup.page.ts @@ -198,7 +198,7 @@ export class PopupExampleTemplate {} }) export class PopupExamplePlacement { @Input() - public position:string = "right bottom"; + public position:string = "top right"; } export const PopupPageComponents = [PopupPage, PopupExampleStandard, PopupExampleTemplate, PopupExamplePlacement]; diff --git a/demo/src/app/pages/modules/progress/progress.page.ts b/demo/src/app/pages/modules/progress/progress.page.ts index df1593cab..7545b8e05 100644 --- a/demo/src/app/pages/modules/progress/progress.page.ts +++ b/demo/src/app/pages/modules/progress/progress.page.ts @@ -91,6 +91,24 @@ export class ProgressPage { type: "boolean", description: "Sets whether or not the progress bar automatically turns green when value == maximum.", defaultValue: "true" + }, + { + name: "transition", + type: "number", + description: "Sets the transition function used when transitioning between values.", + defaultValue: "350" + }, + { + name: "transitionDuration", + type: "number", + description: "Sets the transition duration of the bar between values.", + defaultValue: "350" + }, + { + name: "canCompletelyEmpty", + type: "boolean", + description: "Sets whether the progress bar can empty completely.", + defaultValue: "false" } ] } diff --git a/demo/src/app/pages/modules/search/search.page.html b/demo/src/app/pages/modules/search/search.page.html index 3b736d54d..d71a3a19a 100644 --- a/demo/src/app/pages/modules/search/search.page.html +++ b/demo/src/app/pages/modules/search/search.page.html @@ -32,7 +32,7 @@

Template

- +
Localization

The search component has full localization support. Read the diff --git a/demo/src/app/pages/modules/select/select.page.html b/demo/src/app/pages/modules/select/select.page.html index dd86fb75e..9ef3c657b 100644 --- a/demo/src/app/pages/modules/select/select.page.html +++ b/demo/src/app/pages/modules/select/select.page.html @@ -18,7 +18,7 @@

Selection

- +
Important Note

The select component is unusual in the fact that it requires you to manually provide the options within. @@ -87,7 +87,7 @@

Remote Lookup

- +
Localization

The select component has full localization support. Read the diff --git a/src/collections/message/classes/active-message.ts b/src/collections/message/classes/active-message.ts new file mode 100644 index 000000000..908f3de25 --- /dev/null +++ b/src/collections/message/classes/active-message.ts @@ -0,0 +1,40 @@ +import { MessageConfig } from "./message-config"; +import { ComponentRef } from "@angular/core"; +import { SuiMessage } from "../components/message"; + +export abstract class SuiActiveMessage { + public abstract onClick(callback:() => void):SuiActiveMessage; + public abstract onDismiss(callback:() => void):SuiActiveMessage; + + public abstract dismiss():void; +} + +export class ActiveMessage implements SuiActiveMessage { + public config:MessageConfig; + public componentRef:ComponentRef; + + public get component():SuiMessage { + return this.componentRef.instance; + } + + constructor(config:MessageConfig, componentRef:ComponentRef) { + this.config = config; + this.componentRef = componentRef; + + this.component.onDismiss.subscribe(() => this.componentRef.destroy()); + } + + public onClick(callback:() => void):ActiveMessage { + this.config.onClick.subscribe(() => callback()); + return this; + } + + public onDismiss(callback:() => void):ActiveMessage { + this.config.onDismiss.subscribe(() => callback()); + return this; + } + + public dismiss():void { + this.component.dismiss(); + } +} diff --git a/src/collections/message/classes/message-config.ts b/src/collections/message/classes/message-config.ts new file mode 100644 index 000000000..6b70e580b --- /dev/null +++ b/src/collections/message/classes/message-config.ts @@ -0,0 +1,49 @@ +import { EventEmitter } from "@angular/core"; + +export type MessageState = "" | "info" | "success" | "warning" | "error"; + +export const MessageState = { + Default: "" as MessageState, + Info: "info" as MessageState, + Success: "success" as MessageState, + Warning: "warning" as MessageState, + Error: "error" as MessageState +}; + +export class MessageConfig { + public text:string; + public header?:string; + public state:MessageState; + + public timeout:number; + public extendedTimeout:number; + + public hasDismissButton:boolean; + public hasProgress:boolean; + + public transition:string; + public transitionInDuration:number; + public transitionOutDuration:number; + + public onClick:EventEmitter; + public onDismiss:EventEmitter; + + constructor(text:string, state:MessageState = MessageState.Default, header?:string) { + this.text = text; + this.state = state; + this.header = header; + + this.timeout = 5000; + this.extendedTimeout = 1000; + + this.hasDismissButton = true; + this.hasProgress = false; + + this.transition = "fade"; + this.transitionInDuration = 400; + this.transitionOutDuration = 1000; + + this.onClick = new EventEmitter(); + this.onDismiss = new EventEmitter(); + } +} diff --git a/src/collections/message/classes/message-controller.ts b/src/collections/message/classes/message-controller.ts new file mode 100644 index 000000000..2faee9b10 --- /dev/null +++ b/src/collections/message/classes/message-controller.ts @@ -0,0 +1,44 @@ +import { MessageConfig } from "./message-config"; +import { SuiActiveMessage } from "./active-message"; +import { SuiMessageContainer } from "../components/message-container"; + +export interface IMessageController { + maxShown:number; + isNewestOnTop:boolean; + show(config:MessageConfig):SuiActiveMessage; + dismissAll():void; +} + +export class MessageController implements IMessageController { + private _container:SuiMessageContainer; + + public maxShown:number; + public isNewestOnTop:boolean; + + constructor() { + this.maxShown = 7; + this.isNewestOnTop = true; + } + + public registerContainer(container:SuiMessageContainer):void { + this._container = container; + } + + public show(config:MessageConfig):SuiActiveMessage { + this.throwContainerError(); + + return this._container.show(config, this.maxShown, this.isNewestOnTop); + } + + public dismissAll():void { + this.throwContainerError(); + + return this._container.dismissAll(); + } + + private throwContainerError():void { + if (!this._container) { + throw new Error("You must pass this controller to a message container."); + } + } +} diff --git a/src/collections/message/components/message-container.ts b/src/collections/message/components/message-container.ts new file mode 100644 index 000000000..12c52dde8 --- /dev/null +++ b/src/collections/message/components/message-container.ts @@ -0,0 +1,91 @@ +import { Component, EventEmitter, Input, ComponentFactoryResolver, ViewContainerRef, ViewChild, ElementRef } from "@angular/core"; +import { MessageConfig } from "../classes/message-config"; +import { ActiveMessage, SuiActiveMessage } from "../classes/active-message"; +import { SuiMessage } from "./message"; +import { SuiComponentFactory } from "../../../misc/util/index"; +import { MessageController, IMessageController } from "../classes/message-controller"; + +@Component({ + selector: "sui-message-container", + template: ` +

+`, + styles: [` +:host { + display: block; +} +:host >>> sui-message { + display: block; + margin-bottom: 1rem; +} +:host >>> sui-message:last-of-type { + margin-bottom: 0; +} +:host >>> sui-message { + cursor: pointer; +} +`] +}) +export class SuiMessageContainer { + private _messages:ActiveMessage[]; + private _queue:ActiveMessage[]; + + @Input() + public set controller(controller:MessageController) { + controller.registerContainer(this); + } + + @ViewChild("containerSibling", { read: ViewContainerRef }) + public containerSibling:ViewContainerRef; + + constructor(private _componentFactory:SuiComponentFactory, private _element:ElementRef) { + this._messages = []; + this._queue = []; + } + + public show(config:MessageConfig, maxShown:number, showNewestFirst:boolean):SuiActiveMessage { + const componentRef = this._componentFactory.createComponent(SuiMessage); + componentRef.instance.loadConfig(config); + + const active = new ActiveMessage(config, componentRef) + .onDismiss(() => this.onMessageClose(active, showNewestFirst)); + + if (this._messages.length < maxShown) { + this.open(active, showNewestFirst); + } else { + this.queue(active); + } + + return active; + } + + private open(message:ActiveMessage, showNewestFirst:boolean):void { + this._messages.push(message); + + this._componentFactory.attachToView(message.componentRef, this.containerSibling); + if (!showNewestFirst) { + this._componentFactory.moveToElement(message.componentRef, this._element.nativeElement); + } + + message.component.show(); + } + + private queue(message:ActiveMessage):void { + this._queue.push(message); + } + + public dismissAll():void { + this._queue = []; + this._messages.forEach(m => m.dismiss()); + } + + private onMessageClose(message:ActiveMessage, showNewestFirst:boolean):void { + this._messages = this._messages.filter(m => m !== message); + + if (this._queue.length > 0) { + const queued = this._queue.shift(); + + this.open(queued, showNewestFirst); + } + } +} diff --git a/src/collections/message/components/message-global-container.ts b/src/collections/message/components/message-global-container.ts new file mode 100644 index 000000000..6ca7d924f --- /dev/null +++ b/src/collections/message/components/message-global-container.ts @@ -0,0 +1,79 @@ +import { Component, HostListener } from "@angular/core"; +import { MessageController } from "../classes/message-controller"; +import { SuiMessageService } from "../services/message-service"; +import { Util, IDynamicClasses } from "../../../misc/util/index"; + +export type MessagePosition = "top" | "top-left" | "top-right" | + "bottom" | "bottom-left" | "bottom-right"; + +export const MessagePosition = { + Top: "top" as MessagePosition, + TopLeft: "top-left" as MessagePosition, + TopRight: "top-right" as MessagePosition, + Bottom: "bottom" as MessagePosition, + BottomLeft: "bottom-left" as MessagePosition, + BottomRight: "bottom-right" as MessagePosition +}; + +@Component({ + selector: "sui-message-global-container", + template: ` +
+ +
+`, + styles: [` +.global.container { + display: block; + position: fixed; +} +.global.container.top { + top: 1rem; +} +.global.container.bottom { + bottom: 1rem; +} +.global.container.left { + left: 1rem; +} +.global.container.right { + right: 1rem; +} +.global.container:not(.left):not(.right) { + left: 1rem; +} +`] +}) +export class SuiMessageGlobalContainer { + public controller:MessageController; + + public position:MessagePosition; + public width:number; + + public get dynamicClasses():IDynamicClasses { + const classes:IDynamicClasses = {}; + + this.position + .split("-") + .forEach(p => classes[p] = true); + + return classes; + } + + public get dynamicWidth():number { + const margin = Util.DOM.getDocumentFontSize(); + let width = this.width; + + if (this.position === MessagePosition.Top || + this.position === MessagePosition.Bottom || + window.innerWidth < width + margin * 2) { + + width = window.innerWidth - margin * 2; + } + + return width; + } + + @HostListener("window:resize") + public onDocumentResize():void {} +} diff --git a/src/collections/message/components/message.ts b/src/collections/message/components/message.ts index eb352910a..fd97d7f0b 100644 --- a/src/collections/message/components/message.ts +++ b/src/collections/message/components/message.ts @@ -1,5 +1,7 @@ -import { Component, Input, Output, EventEmitter } from "@angular/core"; -import { TransitionController, Transition, TransitionDirection } from "../../../modules/transition"; +import { Component, EventEmitter, Input, Output, HostBinding } from "@angular/core"; +import { TransitionController, Transition, TransitionDirection } from "../../../modules/transition/index"; +import { HandledEvent, IDynamicClasses } from "../../../misc/util/index"; +import { MessageState, MessageConfig } from "../classes/message-config"; export interface IMessage { dismiss():void; @@ -8,55 +10,189 @@ export interface IMessage { @Component({ selector: "sui-message", template: ` -
- - +
+
+ + + +
{{ header }}
+

{{ text }}

+
+
+
-`, - styles: [` -/* Fix for CSS Bug */ -.ui.icon.visible.message { - display: flex !important; -} -`] +` }) export class SuiMessage implements IMessage { + public isDynamic:boolean; + public isClosing:boolean; + public isDismissing:boolean; + + public text:string; + public header?:string; + public state:MessageState; + + public timeout:number; + public extendedTimeout:number; + public currentTimeout:number; + @Input() - public isDismissable:boolean; + public hasDismissButton:boolean; - @Output("dismiss") - public onDismiss:EventEmitter; + public hasProgress:boolean; - public isDismissed:boolean; + public timeoutProgress:number; public transitionController:TransitionController; @Input() public transition:string; + public transitionInDuration:number; - @Input() - public transitionDuration:number; + @Input("transitionDuration") + public transitionOutDuration:number; + + private _displayTimeout:number; + + @Output("click") + public onClick:EventEmitter; + + @Output("dismiss") + public onDismiss:EventEmitter; @Input("class") - public class:string; + public classes:string; + + public get dynamicClasses():IDynamicClasses { + const classes:IDynamicClasses = {}; + classes[this.state] = true; + + if (this.isDynamic && this.hasProgress) { + classes["attached"] = true; + } + + (this.classes || "") + .split(" ") + .forEach(c => classes[c] = true); + + return classes; + } constructor() { - this.isDismissable = true; - this.onDismiss = new EventEmitter(); + const config = new MessageConfig(""); + this.loadConfig(config); + + this.isDynamic = false; + this.transitionOutDuration = 300; + this.timeoutProgress = 100; + + this.transitionController = new TransitionController(false); + + this.show(); + } + + public loadConfig(config:MessageConfig):void { + this.isDynamic = true; - this.isDismissed = false; + this.text = config.text; + this.header = config.header; + this.state = config.state; - this.transitionController = new TransitionController(); - this.transition = "fade"; - this.transitionDuration = 300; + this.timeout = config.timeout; + this.extendedTimeout = config.extendedTimeout; - this.class = ""; + this.hasDismissButton = config.hasDismissButton; + this.hasProgress = config.hasProgress; + + this.transition = config.transition; + this.transitionInDuration = config.transitionInDuration; + this.transitionOutDuration = config.transitionOutDuration; + + this.onClick = config.onClick; + this.onDismiss = config.onDismiss; + } + + public show():void { + this.transitionController.stopAll(); + this.transitionController.animate( + new Transition( + this.transition, + this.isDynamic ? this.transitionInDuration : 0, + TransitionDirection.In, + () => { + if (this.isDynamic) { + this.beginTimer(this.timeout); + } + })); } public dismiss():void { - this.transitionController.animate(new Transition(this.transition, this.transitionDuration, TransitionDirection.Out, () => { - this.isDismissed = true; - this.onDismiss.emit(this); - })); + this.isDismissing = true; + this.transitionOutDuration = this.transitionInDuration; + + this.hide(); + } + + public hide():void { + this.isClosing = true; + + this.transitionController.stopAll(); + this.transitionController.animate( + new Transition( + this.transition, + this.transitionOutDuration, + TransitionDirection.Out, + () => { + this.isClosing = false; + this.onDismiss.emit(); + })); + } + + public beginTimer(timeout:number):void { + if (this.isDynamic && !this.isDismissing) { + this.timeoutProgress = 0; + this.currentTimeout = timeout; + this._displayTimeout = window.setTimeout(() => this.onTimedOut(), timeout); + } + } + + public cancelTimer():void { + if (this.isDynamic && !this.isDismissing) { + this.timeoutProgress = 100; + this.currentTimeout = 0; + clearTimeout(this._displayTimeout); + + if (this.isClosing) { + this.isClosing = false; + + this.transitionController.cancel(); + } + } + } + + public onClicked(e:HandledEvent):void { + if (!e.eventHandled) { + this.cancelTimer(); + this.onClick.emit(); + } + } + + public onDismissClicked(e:HandledEvent):void { + e.eventHandled = true; + this.dismiss(); + } + + private onTimedOut():void { + this.hide(); } } diff --git a/src/collections/message/index.ts b/src/collections/message/index.ts index 96b22dfc8..176c963d5 100644 --- a/src/collections/message/index.ts +++ b/src/collections/message/index.ts @@ -1,3 +1,11 @@ +export * from "./classes/active-message"; +export * from "./classes/message-config"; +export * from "./classes/message-controller"; + +export * from "./components/message-container"; +export * from "./components/message-global-container"; export * from "./components/message"; +export * from "./services/message-service"; + export * from "./message.module"; diff --git a/src/collections/message/message.module.ts b/src/collections/message/message.module.ts index a5a1e5339..ebba2a8bd 100644 --- a/src/collections/message/message.module.ts +++ b/src/collections/message/message.module.ts @@ -1,18 +1,36 @@ import { NgModule } from "@angular/core"; import { CommonModule } from "@angular/common"; -import { SuiTransitionModule } from "../../modules/transition"; +import { SuiTransitionModule } from "../../modules/transition/index"; +import { SuiProgressModule } from "../../modules/progress/index"; +import { SuiUtilityModule } from "../../misc/util/index"; + +import { SuiMessageContainer } from "./components/message-container"; import { SuiMessage } from "./components/message"; +import { SuiMessageGlobalContainer } from "./components/message-global-container"; +import { SuiMessageService } from "./services/message-service"; @NgModule({ imports: [ CommonModule, - SuiTransitionModule + SuiTransitionModule, + SuiProgressModule, + SuiUtilityModule ], declarations: [ - SuiMessage + SuiMessage, + SuiMessageContainer, + SuiMessageGlobalContainer ], exports: [ - SuiMessage + SuiMessage, + SuiMessageContainer + ], + providers: [ + SuiMessageService + ], + entryComponents: [ + SuiMessage, + SuiMessageGlobalContainer ] }) export class SuiMessageModule {} diff --git a/src/collections/message/public.ts b/src/collections/message/public.ts index 25abd9ac0..379785a74 100644 --- a/src/collections/message/public.ts +++ b/src/collections/message/public.ts @@ -1,4 +1,11 @@ export { SuiMessageModule, - IMessage + IMessage, + SuiMessageContainer, + MessageController, + MessageConfig, + SuiActiveMessage, + MessageState, + MessagePosition, + SuiMessageService } from "./index"; diff --git a/src/collections/message/services/message-service.ts b/src/collections/message/services/message-service.ts new file mode 100644 index 000000000..37c3f71f2 --- /dev/null +++ b/src/collections/message/services/message-service.ts @@ -0,0 +1,69 @@ +import { Injectable, ComponentRef } from "@angular/core"; +import { SuiComponentFactory } from "../../../misc/util/index"; +import { SuiMessageGlobalContainer, MessagePosition } from "../components/message-global-container"; +import { MessageController, IMessageController } from "../classes/message-controller"; +import { MessageConfig } from "../classes/message-config"; +import { SuiActiveMessage } from "../classes/active-message"; + +@Injectable() +export class SuiMessageService implements IMessageController { + private _controller:MessageController; + private _containerRef:ComponentRef; + + private get _container():SuiMessageGlobalContainer { + return this._containerRef.instance; + } + + public get position():MessagePosition { + return this._container.position; + } + + public set position(position:MessagePosition) { + this._container.position = position; + } + + public get width():number { + return this._container.width; + } + + public set width(width:number) { + this._container.width = width; + } + + public get maxShown():number { + return this._controller.maxShown; + } + + public set maxShown(max:number) { + this._controller.maxShown = max; + } + + public get isNewestOnTop():boolean { + return this._controller.isNewestOnTop; + } + + public set isNewestOnTop(value:boolean) { + this._controller.isNewestOnTop = value; + } + + constructor(private _componentFactory:SuiComponentFactory) { + this._controller = new MessageController(); + + this._containerRef = this._componentFactory.createComponent(SuiMessageGlobalContainer); + this._container.controller = this._controller; + + this._componentFactory.attachToApplication(this._containerRef); + this._componentFactory.moveToDocumentBody(this._containerRef); + + this.position = MessagePosition.TopRight; + this.width = 480; + } + + public show(config:MessageConfig):SuiActiveMessage { + return this._controller.show(config); + } + + public dismissAll():void { + return this._controller.dismissAll(); + } +} diff --git a/src/misc/util/helpers/util.ts b/src/misc/util/helpers/util.ts index bf30e0ddf..725a4136d 100644 --- a/src/misc/util/helpers/util.ts +++ b/src/misc/util/helpers/util.ts @@ -75,6 +75,12 @@ export const Util = { } return value; + }, + + getDocumentFontSize():number { + return parseFloat(window + .getComputedStyle(document.documentElement, undefined) + .getPropertyValue("font-size")); } }, diff --git a/src/modules/progress/components/progress.ts b/src/modules/progress/components/progress.ts index 4ca7685ae..77901d630 100644 --- a/src/modules/progress/components/progress.ts +++ b/src/modules/progress/components/progress.ts @@ -3,7 +3,11 @@ import { Component, Input, HostBinding } from "@angular/core"; @Component({ selector: "sui-progress", template: ` -
+
{{ percentage }}%
@@ -12,7 +16,6 @@ import { Component, Input, HostBinding } from "@angular/core"; `, styles: [` .bar { - transition-duration: 300ms !important; z-index: 1; } `] @@ -96,6 +99,15 @@ export class SuiProgress { return percentage.toFixed(this.precision); } + @Input() + public transition:string; + + @Input() + public transitionDuration:number; + + @Input() + public canCompletelyEmpty:boolean; + @Input("class") public set classValue(classes:string) { if (classes.includes("attached") || classes.includes("tiny")) { @@ -115,6 +127,10 @@ export class SuiProgress { this.autoSuccess = true; this.showProgress = true; + this.transition = "ease"; + this.transitionDuration = 350; + this.canCompletelyEmpty = false; + this._popupClasses = true; } -} +} \ No newline at end of file diff --git a/src/modules/transition/classes/transition-controller.ts b/src/modules/transition/classes/transition-controller.ts index cb99652db..d1ca8aea0 100644 --- a/src/modules/transition/classes/transition-controller.ts +++ b/src/modules/transition/classes/transition-controller.ts @@ -131,12 +131,10 @@ export class TransitionController { } // Wait the length of the animation before calling the complete callback. - this._animationTimeout = window.setTimeout(() => this.finishTransition(transition), transition.duration); + this._animationTimeout = window.setTimeout(() => this.finalizeTransition(transition), transition.duration); } - // Called when a transition has completed. - private finishTransition(transition:Transition):void { - // Unset the Semantic UI classes & styles for transitioning. + private completeTransition(transition:Transition):void { transition.classes.forEach(c => this._renderer.removeClass(this._element, c)); this._renderer.removeClass(this._element, `animating`); this._renderer.removeClass(this._element, transition.directionClass); @@ -144,6 +142,19 @@ export class TransitionController { this._renderer.removeStyle(this._element, `animationDuration`); this._renderer.removeStyle(this._element, `display`); + // Delete the transition from the queue. + this._queue.shift(); + this._isAnimating = false; + + this._changeDetector.markForCheck(); + + clearTimeout(this._animationTimeout); + } + + // Called when a transition has completed. + private finalizeTransition(transition:Transition):void { + this.completeTransition(transition); + if (transition.direction === TransitionDirection.In) { // If we have just animated in, we are now visible. this._isVisible = true; @@ -158,12 +169,6 @@ export class TransitionController { transition.onComplete(); } - // Delete the transition from the queue. - this._queue.shift(); - this._isAnimating = false; - - this._changeDetector.markForCheck(); - // Immediately attempt to perform another transition. this.performTransition(); } @@ -174,8 +179,21 @@ export class TransitionController { return; } - clearTimeout(this._animationTimeout); - this.finishTransition(transition); + this.finalizeTransition(transition); + } + + // Cancels the current transition, leaves the rest of the queue intact. + public cancel(transition:Transition = this._queueFirst):void { + if (!transition || !this._isAnimating) { + return; + } + + this.completeTransition(transition); + + if (transition.direction === TransitionDirection.In) { + // Return hidden class if we were originally transitioning in. + this._isHidden = true; + } } // Stops the current transition, and empties the queue. @@ -184,6 +202,11 @@ export class TransitionController { this.stop(); } + public cancelAll():void { + this.clearQueue(); + this.cancel(); + } + // Empties the transition queue but carries on with the current transition. public clearQueue():void { if (this.isAnimating) { From c5339807136d21a40c9ba115b00bb72b8d541b6d Mon Sep 17 00:00:00 2001 From: Ed Carroll Date: Sat, 19 Aug 2017 09:42:09 +0100 Subject: [PATCH 2/3] fix(message): Fixed nullable error --- src/collections/message/components/message-container.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collections/message/components/message-container.ts b/src/collections/message/components/message-container.ts index 12c52dde8..c76d4ad2a 100644 --- a/src/collections/message/components/message-container.ts +++ b/src/collections/message/components/message-container.ts @@ -83,7 +83,7 @@ export class SuiMessageContainer { this._messages = this._messages.filter(m => m !== message); if (this._queue.length > 0) { - const queued = this._queue.shift(); + const [queued] = this._queue.slice(0, 1); this.open(queued, showNewestFirst); } From 0e96a18d82271e6f5c5708939e0fd8c6ce5471ea Mon Sep 17 00:00:00 2001 From: Ed Carroll Date: Sat, 19 Aug 2017 09:49:37 +0100 Subject: [PATCH 3/3] feat(message): Expanded test demo --- demo/src/app/pages/development/test/test.page.html | 4 +++- demo/src/app/pages/development/test/test.page.ts | 7 ++++++- src/collections/message/components/message-container.ts | 2 ++ src/collections/message/components/message.ts | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/demo/src/app/pages/development/test/test.page.html b/demo/src/app/pages/development/test/test.page.html index 8465bd8c3..388ab3df2 100644 --- a/demo/src/app/pages/development/test/test.page.html +++ b/demo/src/app/pages/development/test/test.page.html @@ -8,7 +8,9 @@

Examples

- + +
+

diff --git a/demo/src/app/pages/development/test/test.page.ts b/demo/src/app/pages/development/test/test.page.ts index 3a4a01c20..55fe7af0c 100644 --- a/demo/src/app/pages/development/test/test.page.ts +++ b/demo/src/app/pages/development/test/test.page.ts @@ -18,7 +18,12 @@ export class TestPage { public open():void { const message = new MessageConfig(Date.now().toString(), MessageState.Default, "Header"); message.hasProgress = true; - // this.controller.show(message); + this.controller.show(message); this._messageService.show(message); } + + public dismissAll():void { + this.controller.dismissAll(); + this._messageService.dismissAll(); + } } diff --git a/src/collections/message/components/message-container.ts b/src/collections/message/components/message-container.ts index c76d4ad2a..d489f1ecb 100644 --- a/src/collections/message/components/message-container.ts +++ b/src/collections/message/components/message-container.ts @@ -61,6 +61,7 @@ export class SuiMessageContainer { private open(message:ActiveMessage, showNewestFirst:boolean):void { this._messages.push(message); + console.log(this._messages); this._componentFactory.attachToView(message.componentRef, this.containerSibling); if (!showNewestFirst) { @@ -76,6 +77,7 @@ export class SuiMessageContainer { public dismissAll():void { this._queue = []; + console.log(this._messages); this._messages.forEach(m => m.dismiss()); } diff --git a/src/collections/message/components/message.ts b/src/collections/message/components/message.ts index fd97d7f0b..fb60ef3ba 100644 --- a/src/collections/message/components/message.ts +++ b/src/collections/message/components/message.ts @@ -137,6 +137,7 @@ export class SuiMessage implements IMessage { } public dismiss():void { + console.log("GELLO"); this.isDismissing = true; this.transitionOutDuration = this.transitionInDuration;