diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 74687f8512..58f7e77ae9 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,13 +1,23 @@ import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; +import { ToasterContainerComponent, ToasterConfig } from 'angular2-toaster'; import { Component } from '@angular/core'; @Component({ selector: 'app-root', styles: [], - template: '', + template: ` + + `, }) export class AppComponent { + toasterConfig: ToasterConfig = new ToasterConfig({ + showCloseButton: true, + mouseoverTimerStop: true, + animation: 'flyRight', + limit: 5, + }); + constructor(angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics) { } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c1048bb916..7d8bee83c7 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,10 +4,11 @@ import 'zone.js/dist/zone'; import { Angulartics2Module } from 'angulartics2'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; import { AppRoutingModule } from './app-routing.module'; -import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { FormsModule } from '@angular/forms'; import { NgModule } from '@angular/core'; import { ServicesModule } from './services/services.module'; +import { ToasterModule } from 'angular2-toaster'; import { AddComponent } from './vault/add.component'; import { AppComponent } from './app.component'; @@ -25,7 +26,7 @@ import { ViewComponent } from './vault/view.component'; @NgModule({ imports: [ - BrowserModule, + BrowserAnimationsModule, FormsModule, AppRoutingModule, ServicesModule, @@ -34,6 +35,7 @@ import { ViewComponent } from './vault/view.component'; clearQueryParams: true, }, }), + ToasterModule, ], declarations: [ AddComponent, diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index f9547273ae..c11c9229a4 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -77,7 +77,7 @@ const settingsService = new SettingsService(userService, storageService); const cipherService = new CipherService(cryptoService, userService, settingsService, apiService, storageService); const folderService = new FolderService(cryptoService, userService, - () => i18nService.t('noFolder'), apiService, storageService); + () => i18nService.t('noneFolder'), apiService, storageService); const collectionService = new CollectionService(cryptoService, userService, storageService); const lockService = new LockService(cipherService, folderService, collectionService, cryptoService, platformUtilsService, storageService, diff --git a/src/app/vault/add.component.ts b/src/app/vault/add.component.ts index cc56b52ad5..8ca54e1fae 100644 --- a/src/app/vault/add.component.ts +++ b/src/app/vault/add.component.ts @@ -6,6 +6,9 @@ import { OnChanges, } from '@angular/core'; +import { Angulartics2 } from 'angulartics2'; +import { ToasterService } from 'angular2-toaster'; + import { CipherType } from 'jslib/enums/cipherType'; import { FieldType } from 'jslib/enums/fieldType'; import { SecureNoteType } from 'jslib/enums/secureNoteType'; @@ -41,7 +44,8 @@ export class AddComponent implements OnChanges { addFieldTypeOptions: any[]; constructor(private cipherService: CipherService, private folderService: FolderService, - private i18nService: I18nService, private platformUtilsService: PlatformUtilsService) { + private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, + private analytics: Angulartics2, private toasterService: ToasterService) { this.typeOptions = [ { name: i18nService.t('typeLogin'), value: CipherType.Login }, { name: i18nService.t('typeCard'), value: CipherType.Card }, @@ -104,15 +108,15 @@ export class AddComponent implements OnChanges { async save() { if (this.cipher.name == null || this.cipher.name === '') { - this.platformUtilsService.alertError(this.i18nService.t('errorOccurred'), + this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), this.i18nService.t('nameRequired')); return; } const cipher = await this.cipherService.encrypt(this.cipher); await this.cipherService.saveWithServer(cipher); - //$analytics.eventTrack('Added Cipher'); - // TODO: success message + this.analytics.eventTrack.next({ action: 'Added Cipher' }); + this.toasterService.popAsync('success', null, this.i18nService.t('addedItem')); }; addField() { diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index e781d47e3c..c14f86a9c3 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -29,9 +29,6 @@ "collections": { "message": "Collections" }, - "noFolder": { - "message": "No Folder" - }, "searchVault": { "message": "Search vault" }, @@ -286,5 +283,24 @@ }, "nameRequired": { "message": "Name is required." + }, + "addedItem": { + "message": "Added item" + }, + "editedItem": { + "message": "Edited item" + }, + "deleteItemConfirmation": { + "message": "Are you sure you want to delete this item?" + }, + "deletedItem": { + "message": "Deleted item" + }, + "overwritePasswordConfirmation": { + "message": "Are you sure you want to overwrite the current password?" + }, + "noneFolder": { + "message": "No Folder", + "description": "This is the folder for uncategorized items" } } diff --git a/src/scss/box.scss b/src/scss/box.scss new file mode 100644 index 0000000000..ef6bc6b768 --- /dev/null +++ b/src/scss/box.scss @@ -0,0 +1,194 @@ +@import "variables.scss"; + +.box { + min-width: 400px; + max-width: 550px; + width: 100%; + margin: 30px auto 0 auto; + + &:first-child { + margin-top: 10px; + } + + &:last-child { + margin-bottom: 30px; + } + + .box-header { + margin: 0 10px 5px 10px; + color: $gray-light; + text-transform: uppercase; + } + + .box-content { + background: $box-background-color; + border-radius: $border-radius; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2); + + .box-content-row { + padding: 10px 15px; + position: relative; + z-index: 1; + display: block; + color: $text-color; + overflow-wrap: break-word; + + &:before { + content: ""; + position: absolute; + right: 0; + bottom: 0; + height: 1px; + width: calc(100% - 10px); + border-bottom: 1px solid $box-border-color; + } + + &:first-child, &:last-child { + border-radius: $border-radius; + } + + &:last-child { + &:before { + border: none; + height: 0; + } + } + + &:after { + content: ""; + display: table; + clear: both; + } + + &:hover, &:focus, &.active { + background-color: $box-background-hover-color; + } + + &.pre { + white-space: pre; + overflow-x: auto; + } + + .no-wrap { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &.box-content-row-cf { + display: flex; + align-items: center; + width: 100%; + + > a { + padding: 8px 10px 8px 5px; + color: $brand-danger; + } + + > div { + width: 100%; + } + } + + .row-label, label { + font-size: $font-size-small; + color: $text-muted; + display: block; + width: 100%; + margin-bottom: 5px; + } + + &.box-content-row-checkbox, &.box-content-row-input, &.box-content-row-slider { + label, .row-label { + font-size: $font-size-base; + color: $text-color; + display: inline; + width: initial; + margin-bottom: 0; + float: left; + } + + input.row-label { + width: calc(100% - 40px); + } + } + + input:not([type="checkbox"]), select, textarea { + border: none; + width: 100%; + background-color: transparent; + + &::-webkit-input-placeholder { + color: lighten($gray-light, 35%); + } + + &:focus { + outline: none; + } + } + + input[type="checkbox"] { + float: right; + display: inline-block; + } + + .action-buttons { + float: right; + + .row-btn { + float: left; + cursor: pointer; + padding: 10px 8px; + background: none; + border: none; + color: $brand-primary; + + &:hover, &:focus { + color: darken($brand-primary, 10%); + } + + &.disabled { + color: $list-icon-color; + + &:hover { + color: $list-icon-color; + } + } + + &:last-child { + padding-right: 2px !important; + } + } + } + + select.field-type { + margin: 5px 0 0 25px; + width: calc(100% - 25px); + } + + .right-icon, .fa-chevron-right { + float: right; + margin-top: 4px; + color: $list-icon-color; + } + + .row-sub-label { + float: right; + display: block; + margin-right: 15px; + color: $gray-light; + } + + small.row-sub-label { + margin-top: 2px; + } + } + } + + .box-footer { + margin: 5px 10px; + font-size: $font-size-small; + color: $text-muted; + } +} + diff --git a/src/scss/plugins.scss b/src/scss/plugins.scss new file mode 100644 index 0000000000..ddc3821a94 --- /dev/null +++ b/src/scss/plugins.scss @@ -0,0 +1,72 @@ +@import "variables.scss"; + +#toast-container { + .toast-close-button { + right: -0.15em; + } + + .toast { + opacity: 1 !important; + background-image: none !important; + border-radius: $border-radius; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); + display: flex; + align-items: center; + + &:hover { + box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); + } + + &:before { + position: fixed; + font-family: FontAwesome; + font-size: 25px; + line-height: 20px; + float: left; + color: #ffffff; + padding-right: 0.5em; + margin: auto 0 auto -36px; + } + + .toaster-icon { + display: none; + } + + &.toast-danger, &.toast-error { + background-image: none !important; + background-color: $brand-danger; + + &:before { + content: "\f0e7"; + margin-left: -30px; + } + } + + &.toast-warning { + background-image: none !important; + background-color: $brand-warning; + + &:before { + content: "\f071"; + } + } + + &.toast-info { + background-image: none !important; + background-color: $brand-info; + + &:before { + content: "\f05a"; + } + } + + &.toast-success { + background-image: none !important; + background-color: $brand-success; + + &:before { + content: "\f00C"; + } + } + } +} diff --git a/src/scss/styles.scss b/src/scss/styles.scss index bad9b70e8f..143c57d82f 100644 --- a/src/scss/styles.scss +++ b/src/scss/styles.scss @@ -1,43 +1,10 @@ $fa-font-path: "~font-awesome/fonts"; @import "~font-awesome/scss/font-awesome.scss"; +@import "~angular2-toaster/toaster"; -$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif; -$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; -$font-size-base: 14px; -$font-size-large: 18px; -$font-size-small: 12px; -$text-color: #000000; -$background-color: #efeff4; -$border-color: #f0f0f0; -$border-color-dark: #ddd; -$list-item-hover: #fbfbfb; -$list-icon-color: #c7c7cd; -$border-radius: 3px; - -$gray: #555; -$gray-light: #777; -$text-muted: $gray-light; - -$brand-primary: #3c8dbc; -$brand-danger: #dd4b39; -$brand-success: #00a65a; -$brand-info: #555555; -$brand-warning: #f39c12; -$brand-primary-accent: #286090; - -$background-color: white; -$background-color-alt: #f9fafc; -$background-color-alt2: #ecf0f5; - -$box-background-color: $background-color; -$box-background-hover-color: $background-color-alt; -$box-border-color: $border-color; - -$button-border-color: darken($border-color-dark, 12%); -$button-backgound-color: white; -$button-color: lighten($text-color, 40%); -$button-color-primary: darken($brand-primary, 8%); -$button-color-danger: darken($brand-danger, 10%); +@import "variables.scss"; +@import "box.scss"; +@import "plugins.scss"; * { box-sizing: border-box; @@ -629,198 +596,6 @@ textarea { } } -.box { - min-width: 400px; - max-width: 550px; - width: 100%; - margin: 30px auto 0 auto; - - &:first-child { - margin-top: 10px; - } - - &:last-child { - margin-bottom: 30px; - } - - .box-header { - margin: 0 10px 5px 10px; - color: $gray-light; - text-transform: uppercase; - } - - .box-content { - background: $box-background-color; - border-radius: $border-radius; - box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2); - - .box-content-row { - padding: 10px 15px; - position: relative; - z-index: 1; - display: block; - color: $text-color; - overflow-wrap: break-word; - - &:before { - content: ""; - position: absolute; - right: 0; - bottom: 0; - height: 1px; - width: calc(100% - 10px); - border-bottom: 1px solid $box-border-color; - } - - &:first-child, &:last-child { - border-radius: $border-radius; - } - - &:last-child { - &:before { - border: none; - height: 0; - } - } - - &:after { - content: ""; - display: table; - clear: both; - } - - &:hover, &:focus, &.active { - background-color: $box-background-hover-color; - } - - &.pre { - white-space: pre; - overflow-x: auto; - } - - .no-wrap { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - &.box-content-row-cf { - display: flex; - align-items: center; - width: 100%; - - > a { - padding: 8px 10px 8px 5px; - color: $brand-danger; - } - - > div { - width: 100%; - } - } - - .row-label, label { - font-size: $font-size-small; - color: $text-muted; - display: block; - width: 100%; - margin-bottom: 5px; - } - - &.box-content-row-checkbox, &.box-content-row-input, &.box-content-row-slider { - label, .row-label { - font-size: $font-size-base; - color: $text-color; - display: inline; - width: initial; - margin-bottom: 0; - float: left; - } - - input.row-label { - width: calc(100% - 40px); - } - } - - input:not([type="checkbox"]), select, textarea { - border: none; - width: 100%; - background-color: transparent; - - &::-webkit-input-placeholder { - color: lighten($gray-light, 35%); - } - - &:focus { - outline: none; - } - } - - input[type="checkbox"] { - float: right; - display: inline-block; - } - - .action-buttons { - float: right; - - .row-btn { - float: left; - cursor: pointer; - padding: 10px 8px; - background: none; - border: none; - color: $brand-primary; - - &:hover, &:focus { - color: darken($brand-primary, 10%); - } - - &.disabled { - color: $list-icon-color; - - &:hover { - color: $list-icon-color; - } - } - - &:last-child { - padding-right: 2px !important; - } - } - } - - select.field-type { - margin: 5px 0 0 25px; - width: calc(100% - 25px); - } - - .right-icon, .fa-chevron-right { - float: right; - margin-top: 4px; - color: $list-icon-color; - } - - .row-sub-label { - float: right; - display: block; - margin-right: 15px; - color: $gray-light; - } - - small.row-sub-label { - margin-top: 2px; - } - } - } - - .box-footer { - margin: 5px 10px; - font-size: $font-size-small; - color: $text-muted; - } -} - .totp { .totp-code { font-family: $font-family-monospace; diff --git a/src/scss/variables.scss b/src/scss/variables.scss new file mode 100644 index 0000000000..54e35f22a1 --- /dev/null +++ b/src/scss/variables.scss @@ -0,0 +1,37 @@ +$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif; +$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; +$font-size-base: 14px; +$font-size-large: 18px; +$font-size-small: 12px; +$text-color: #000000; +$background-color: #efeff4; +$border-color: #f0f0f0; +$border-color-dark: #ddd; +$list-item-hover: #fbfbfb; +$list-icon-color: #c7c7cd; +$border-radius: 3px; + +$gray: #555; +$gray-light: #777; +$text-muted: $gray-light; + +$brand-primary: #3c8dbc; +$brand-danger: #dd4b39; +$brand-success: #00a65a; +$brand-info: #555555; +$brand-warning: #f39c12; +$brand-primary-accent: #286090; + +$background-color: white; +$background-color-alt: #f9fafc; +$background-color-alt2: #ecf0f5; + +$box-background-color: $background-color; +$box-background-hover-color: $background-color-alt; +$box-border-color: $border-color; + +$button-border-color: darken($border-color-dark, 12%); +$button-backgound-color: white; +$button-color: lighten($text-color, 40%); +$button-color-primary: darken($brand-primary, 8%); +$button-color-danger: darken($brand-danger, 10%);