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%);