diff options
Diffstat (limited to 'webapp/src/app/@theme/components')
13 files changed, 608 insertions, 0 deletions
diff --git a/webapp/src/app/@theme/components/footer/footer.component.scss b/webapp/src/app/@theme/components/footer/footer.component.scss new file mode 100644 index 0000000..78d8114 --- /dev/null +++ b/webapp/src/app/@theme/components/footer/footer.component.scss @@ -0,0 +1,30 @@ +@import '../../styles/themes'; +@import '~@nebular/theme/styles/global/bootstrap/breakpoints'; +@import '~bootstrap/scss/mixins/breakpoints'; + +@include nb-install-component() { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + + .socials { + font-size: 2rem; + + a { + padding: 0.4rem; + color: nb-theme(color-fg); + transition: color ease-out 0.1s; + + &:hover { + color: nb-theme(color-fg-heading); + } + } + } + + @include media-breakpoint-down(is) { + .socials { + font-size: 1.5rem; + } + } +} diff --git a/webapp/src/app/@theme/components/footer/footer.component.ts b/webapp/src/app/@theme/components/footer/footer.component.ts new file mode 100644 index 0000000..8e1e825 --- /dev/null +++ b/webapp/src/app/@theme/components/footer/footer.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ngx-footer', + styleUrls: ['./footer.component.scss'], + template: ` + <span class="created-by">Created by + <b><a href="http://iot.bzh" target="_blank">IoT.bzh</a></b> 2017 + + <span style="font-size: small;">(powered by <a href="https://github.com/akveo/ngx-admin" target="_blank">akveo/ngx-admin</a>)</span> + </span> + <!-- MODS_XDS + <div class="socials"> + <a href="#" target="_blank" class="ion ion-social-github"></a> + <a href="#" target="_blank" class="ion ion-social-facebook"></a> + <a href="#" target="_blank" class="ion ion-social-twitter"></a> + <a href="#" target="_blank" class="ion ion-social-linkedin"></a> + </div> + --> + `, +}) +export class FooterComponent { +} diff --git a/webapp/src/app/@theme/components/header/header.component.html b/webapp/src/app/@theme/components/header/header.component.html new file mode 100644 index 0000000..5d5eff6 --- /dev/null +++ b/webapp/src/app/@theme/components/header/header.component.html @@ -0,0 +1,32 @@ +<div class="header-container" + [class.left]="position === 'normal'" + [class.right]="position === 'inverse'"> + <div class="logo-containter"> + <a (click)="toggleSidebar()" href="#" class="navigation"><i class="nb-menu"></i></a> +<!-- MODS_XDS + <div class="logo" (click)="goToHome()">ngx-<span>admin</span></div> +--> + <div class="logo" (click)="goToHome()">XDS <span>dashboard</span></div> + </div> +<!-- MODS_XDS + <ngx-theme-switcher></ngx-theme-switcher> +--> +</div> + +<nb-actions + size="medium" + class="header-container" + [class.right]="position === 'normal'" + [class.left]="position === 'inverse'"> + <nb-action icon="nb-grid-b" class="toggle-layout" (click)="toggleSettings()"></nb-action> + <nb-action> + <nb-user [menu]="userMenu" [name]="user?.name" [picture]="user?.picture"></nb-user> + </nb-action> + <nb-action class="control-item" disabled icon="nb-notifications"></nb-action> +<!-- MODS_XDS + <nb-action class="control-item" icon="nb-email"></nb-action> +--> + <nb-action class="control-item"> + <nb-search type="rotate-layout" (click)="startSearch()"></nb-search> + </nb-action> +</nb-actions> diff --git a/webapp/src/app/@theme/components/header/header.component.scss b/webapp/src/app/@theme/components/header/header.component.scss new file mode 100644 index 0000000..647311b --- /dev/null +++ b/webapp/src/app/@theme/components/header/header.component.scss @@ -0,0 +1,115 @@ +@import '../../styles/themes'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/bootstrap/breakpoints'; + +@include nb-install-component() { + display: flex; + justify-content: space-between; + width: 100%; + + .left { + display: flex; + width: 100%; + order: 0; + flex-direction: row; + } + .right { + order: 1; + flex-direction: row-reverse; + } + + .logo-containter { + display: flex; + align-items: center; + } + + .control-item { + display: block; + } + + .header-container { + display: flex; + align-items: center; + width: 100%; + + .navigation { + padding-right: nb-theme(padding); + font-size: 2.5rem; + text-decoration: none; + + i { + display: block; + } + + } + + .logo { + padding: 0 nb-theme(padding); + font-size: 1.75rem; + font-weight: nb-theme(font-weight-bolder); + border-left: 1px solid nb-theme(separator); + white-space: nowrap; + + span { + font-weight: nb-theme(font-weight-normal); + } + } + } + + .toggle-layout /deep/ a { + display: block; + text-decoration: none; + line-height: 1; + + i { + color: nb-theme(color-fg-highlight); + font-size: 2.25rem; + } + } + + @include media-breakpoint-down(md) { + + nb-action:not(.toggle-layout) { + border: none; + } + + .control-item { + display: none; + } + + .toggle-layout { + padding: 0; + } + } + + @include media-breakpoint-down(sm) { + + nb-user /deep/ .user-name { + display: none; + } + } + + @include media-breakpoint-down(is) { + + .header-container { + .logo { + font-size: 1.25rem; + } + } + + .toggle-layout { + display: none; + } + + nb-action:not(.toggle-layout) { + padding: 0; + } + } + + @include media-breakpoint-down(xs) { + .right /deep/ { + display: none; + } + } +} + diff --git a/webapp/src/app/@theme/components/header/header.component.ts b/webapp/src/app/@theme/components/header/header.component.ts new file mode 100644 index 0000000..e2a84cb --- /dev/null +++ b/webapp/src/app/@theme/components/header/header.component.ts @@ -0,0 +1,51 @@ +import { Component, Input, OnInit } from '@angular/core'; + +import { NbMenuService, NbSidebarService } from '@nebular/theme'; +// XDS_MODS +import { UserService } from '../../../@core-xds/services/users.service'; +import { AnalyticsService } from '../../../@core/utils/analytics.service'; + +@Component({ + selector: 'ngx-header', + styleUrls: ['./header.component.scss'], + templateUrl: './header.component.html', +}) +export class HeaderComponent implements OnInit { + + + @Input() position = 'normal'; + + user: any; + + userMenu = [{ title: 'Profile' }, { title: 'Log out' }]; + + constructor(private sidebarService: NbSidebarService, + private menuService: NbMenuService, + private userService: UserService, + private analyticsService: AnalyticsService) { + } + + ngOnInit() { + // XDS_MODS + this.userService.getUsers() + .subscribe((users: any) => this.user = users.anonymous); + } + + toggleSidebar(): boolean { + this.sidebarService.toggle(true, 'menu-sidebar'); + return false; + } + + toggleSettings(): boolean { + this.sidebarService.toggle(false, 'settings-sidebar'); + return false; + } + + goToHome() { + this.menuService.navigateHome(); + } + + startSearch() { + this.analyticsService.trackEvent('startSearch'); + } +} diff --git a/webapp/src/app/@theme/components/index.ts b/webapp/src/app/@theme/components/index.ts new file mode 100644 index 0000000..4a25efe --- /dev/null +++ b/webapp/src/app/@theme/components/index.ts @@ -0,0 +1,6 @@ +export * from './header/header.component'; +export * from './footer/footer.component'; +export * from './search-input/search-input.component'; +// XDS_MODS export * from './tiny-mce/tiny-mce.component'; +export * from './theme-settings/theme-settings.component'; +export * from './theme-switcher/theme-switcher.component'; diff --git a/webapp/src/app/@theme/components/search-input/search-input.component.scss b/webapp/src/app/@theme/components/search-input/search-input.component.scss new file mode 100644 index 0000000..5ef07ef --- /dev/null +++ b/webapp/src/app/@theme/components/search-input/search-input.component.scss @@ -0,0 +1,33 @@ +:host { + display: flex; + align-items: center; + + i.control-icon { + &::before { + font-size: 2.3rem; + } + + &:hover { + cursor: pointer; + } + } + + input { + border: none; + outline: none; + margin-left: 1rem; + width: 15rem; + transition: width 0.2s ease; + + &.hidden { + width: 0; + margin: 0; + } + } + + /deep/ search-input { + input { + background: transparent; + } + } +} diff --git a/webapp/src/app/@theme/components/search-input/search-input.component.ts b/webapp/src/app/@theme/components/search-input/search-input.component.ts new file mode 100644 index 0000000..d9f0f10 --- /dev/null +++ b/webapp/src/app/@theme/components/search-input/search-input.component.ts @@ -0,0 +1,35 @@ +import { Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core'; + +@Component({ + selector: 'ngx-search-input', + styleUrls: ['./search-input.component.scss'], + template: ` + <i class="control-icon ion ion-ios-search" + (click)="showInput()"></i> + <input placeholder="Type your search request here..." + #input + [class.hidden]="!isInputShown" + (blur)="hideInput()" + (input)="onInput($event)"> + `, +}) +export class SearchInputComponent { + @ViewChild('input') input: ElementRef; + + @Output() search: EventEmitter<string> = new EventEmitter<string>(); + + isInputShown = false; + + showInput() { + this.isInputShown = true; + this.input.nativeElement.focus(); + } + + hideInput() { + this.isInputShown = false; + } + + onInput(val: string) { + this.search.emit(val); + } +} diff --git a/webapp/src/app/@theme/components/theme-settings/theme-settings.component.scss b/webapp/src/app/@theme/components/theme-settings/theme-settings.component.scss new file mode 100644 index 0000000..4a0a93e --- /dev/null +++ b/webapp/src/app/@theme/components/theme-settings/theme-settings.component.scss @@ -0,0 +1,36 @@ +@import '../../styles/themes'; + +@include nb-install-component() { + h6 { + margin-bottom: 0.5rem; + } + + .settings-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + + width: 90%; + margin: 0 0 1rem; + + a { + text-decoration: none; + font-size: 2.25rem; + + color: nb-theme(color-fg); + + &.selected { + color: nb-theme(color-success); + } + + @include nb-for-theme(cosmic) { + &.selected { + color: nb-theme(link-color); + } + } + } + } +} + diff --git a/webapp/src/app/@theme/components/theme-settings/theme-settings.component.ts b/webapp/src/app/@theme/components/theme-settings/theme-settings.component.ts new file mode 100644 index 0000000..9cd60fe --- /dev/null +++ b/webapp/src/app/@theme/components/theme-settings/theme-settings.component.ts @@ -0,0 +1,65 @@ +import { Component, Input, OnInit } from '@angular/core'; + +import { StateService } from '../../../@core/data/state.service'; + +@Component({ + selector: 'ngx-theme-settings', + styleUrls: ['./theme-settings.component.scss'], + template: ` + <h6>LAYOUTS</h6> + <div class="settings-row"> + <a *ngFor="let layout of layouts" + href="#" + [class.selected]="layout.selected" + [attr.title]="layout.name" + (click)="layoutSelect(layout)"> + <i [attr.class]="layout.icon"></i> + </a> + </div> + <h6>SIDEBAR</h6> + <div class="settings-row"> + <a *ngFor="let sidebar of sidebars" + href="#" + [class.selected]="sidebar.selected" + [attr.title]="sidebar.name" + (click)="sidebarSelect(sidebar)"> + <i [attr.class]="sidebar.icon"></i> + </a> + </div> + `, +}) +export class ThemeSettingsComponent { + + layouts = []; + sidebars = []; + + constructor(protected stateService: StateService) { + this.stateService.getLayoutStates() + .subscribe((layouts: any[]) => this.layouts = layouts); + + this.stateService.getSidebarStates() + .subscribe((sidebars: any[]) => this.sidebars = sidebars); + } + + layoutSelect(layout: any): boolean { + this.layouts = this.layouts.map((l: any) => { + l.selected = false; + return l; + }); + + layout.selected = true; + this.stateService.setLayoutState(layout); + return false; + } + + sidebarSelect(sidebars: any): boolean { + this.sidebars = this.sidebars.map((s: any) => { + s.selected = false; + return s; + }); + + sidebars.selected = true; + this.stateService.setSidebarState(sidebars); + return false; + } +} diff --git a/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.scss b/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.scss new file mode 100644 index 0000000..210add8 --- /dev/null +++ b/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.scss @@ -0,0 +1,101 @@ +@import '../../styles/themes'; +@import '~@nebular/theme/styles/global/bootstrap/hero-buttons'; +@import '~bootstrap/scss/mixins/breakpoints'; +@import '~@nebular/theme/styles/global/bootstrap/breakpoints'; + +@include nb-install-component() { + display: flex; + flex-direction: column; + align-items: center; + width: 50%; + + .theme-switch { + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + margin: 0; + + & > span { + font-size: 1.125rem; + font-weight: nb-theme(font-weight-bold); + transition: opacity 0.3s ease; + + &.light { + color: nb-theme(color-fg-text); + padding-right: 10px; + } + + &.cosmic { + color: nb-theme(color-fg); + padding-left: 10px; + } + + @include nb-for-theme(cosmic) { + &.light { + color: nb-theme(color-fg); + } + + &.cosmic { + color: nb-theme(color-white); + } + } + + &:active { + opacity: 0.78; + } + } + } + + .switch { + position: relative; + display: inline-block; + width: 4rem; + height: 1.75rem; + margin: 0; + + input { + display: none; + + &:checked + .slider::before { + transform: translateX(2.25rem); + } + } + + .slider { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 1.75rem; + background-color: nb-theme(layout-bg); + } + + .slider::before { + position: absolute; + content: ''; + height: 1.75rem; + width: 1.75rem; + border-radius: 50%; + background-color: nb-theme(color-success); + transition: 0.2s; + + box-shadow: 0 0 0.25rem 0 rgba(nb-theme(color-fg), 0.4); + + @include nb-for-theme(cosmic) { + @include btn-hero-primary-gradient(); + } + } + } + + @include media-breakpoint-down(is) { + .light, .cosmic { + display: none; + } + } + + @include media-breakpoint-down(xs) { + align-items: flex-end; + } +} diff --git a/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.ts b/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.ts new file mode 100644 index 0000000..e84b942 --- /dev/null +++ b/webapp/src/app/@theme/components/theme-switcher/theme-switcher.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit } from '@angular/core'; +import { NbThemeService } from '@nebular/theme'; +import { NbJSThemeOptions } from '@nebular/theme/services/js-themes/theme.options'; +import { AnalyticsService } from '../../../@core/utils/analytics.service'; + +@Component({ + selector: 'ngx-theme-switcher', + styleUrls: ['./theme-switcher.component.scss'], + template: ` + <label class="theme-switch"> + <span class="light">Light</span> + <div class="switch"> + <input type="checkbox" [checked]="currentBoolTheme()" (change)="toggleTheme(theme.checked)" #theme> + <span class="slider"></span> + </div> + <span class="cosmic">Cosmic</span> + </label> + `, +}) +export class ThemeSwitcherComponent implements OnInit { + theme: NbJSThemeOptions; + + constructor(private themeService: NbThemeService, private analyticsService: AnalyticsService) { + } + + ngOnInit() { + this.themeService.getJsTheme() + .subscribe((theme: NbJSThemeOptions) => this.theme = theme); + } + + toggleTheme(theme: boolean) { + const boolTheme = this.boolToTheme(theme); + this.themeService.changeTheme(boolTheme); + this.analyticsService.trackEvent('switchTheme'); + } + + currentBoolTheme() { + return this.themeToBool(this.theme); + } + + private themeToBool(theme: NbJSThemeOptions) { + return theme.name === 'cosmic'; + } + + private boolToTheme(theme: boolean) { + return theme ? 'cosmic' : 'default'; + } +} diff --git a/webapp/src/app/@theme/components/tiny-mce/tiny-mce.component.ts b/webapp/src/app/@theme/components/tiny-mce/tiny-mce.component.ts new file mode 100644 index 0000000..c54685b --- /dev/null +++ b/webapp/src/app/@theme/components/tiny-mce/tiny-mce.component.ts @@ -0,0 +1,33 @@ +import { Component, OnDestroy, AfterViewInit, Output, EventEmitter, ElementRef } from '@angular/core'; + +@Component({ + selector: 'ngx-tiny-mce', + template: '', +}) +export class TinyMCEComponent implements OnDestroy, AfterViewInit { + + @Output() editorKeyup = new EventEmitter<any>(); + + editor: any; + + constructor(private host: ElementRef) { } + + ngAfterViewInit() { + tinymce.init({ + target: this.host.nativeElement, + plugins: ['link', 'paste', 'table'], + skin_url: 'assets/skins/lightgray', + setup: editor => { + this.editor = editor; + editor.on('keyup', () => { + this.editorKeyup.emit(editor.getContent()); + }); + }, + height: '320', + }); + } + + ngOnDestroy() { + tinymce.remove(this.editor); + } +} |