feat(dashboard): add logic for temperature dragger

This commit is contained in:
KostyaDanovsky 2017-07-07 19:54:49 +03:00
parent ce8055ca84
commit 2ed871ff20
13 changed files with 262 additions and 47 deletions

View file

@ -0,0 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'ngxCapitalize' })
export class CapitalizePipe implements PipeTransform {
transform(input: string): string {
return input && input.length
? (input.charAt(0).toUpperCase() + input.slice(1).toLowerCase())
: input;
}
}

View file

@ -0,0 +1,3 @@
export * from './capitalize.pipe';
export * from './plural.pipe';
export * from './round.pipe';

View file

@ -0,0 +1,14 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'ngxPlural' })
export class PluralPipe implements PipeTransform {
transform(input: number, label: string, pluralLabel: string = ''): string {
input = input || 0;
return input === 1
? `${input} ${label}`
: pluralLabel
? `${input} ${pluralLabel}`
: `${input} ${label}s`;
}
}

View file

@ -0,0 +1,9 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'ngxRound' })
export class RoundPipe implements PipeTransform {
transform(input: number): number {
return Math.round(input);
}
}

View file

@ -21,6 +21,13 @@ import {
TinyMCEComponent, TinyMCEComponent,
} from './components'; } from './components';
import {
CapitalizePipe,
PluralPipe,
RoundPipe,
} from './pipes';
import { OneColumnLayoutComponent } from './layouts'; import { OneColumnLayoutComponent } from './layouts';
const BASE_MODULES = [ const BASE_MODULES = [
@ -48,20 +55,27 @@ const COMPONENTS = [
OneColumnLayoutComponent, OneColumnLayoutComponent,
]; ];
const PIPES = [
CapitalizePipe,
PluralPipe,
RoundPipe,
];
@NgModule({ @NgModule({
imports: [ imports: [
...BASE_MODULES, ...BASE_MODULES,
...NGA_MODULES, ...NGA_MODULES,
// TODO:
NgaSidebarModule.forRoot(), NgaSidebarModule.forRoot(),
], ],
exports: [ exports: [
...BASE_MODULES, ...BASE_MODULES,
...NGA_MODULES, ...NGA_MODULES,
...COMPONENTS, ...COMPONENTS,
...PIPES,
], ],
declarations: [ declarations: [
...COMPONENTS, ...COMPONENTS,
...PIPES,
], ],
}) })
export class ThemeModule { export class ThemeModule {

View file

@ -1,5 +1,4 @@
@import '../../../@theme/styles/variables'; @import '../../../@theme/styles/variables';
@import '~@akveo/nga-theme/styles/global/bootstrap/hero-buttons';
@include nga-install-component() { @include nga-install-component() {

View file

@ -1,5 +1,6 @@
<div class="svg-container">
<svg #svgRoot xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" <svg #svgRoot xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
[attr.viewBox]="styles.viewBox" preserveAspectRatio="xMinYMin meet" class="svg-content" (mousedown)="mouseDown($event)"> [attr.viewBox]="styles.viewBox" preserveAspectRatio="xMinYMin meet" (mousedown)="mouseDown($event)">
<defs> <defs>
<filter id="blurFilter" x="0" y="0" width="100%" height="100%"> <filter id="blurFilter" x="0" y="0" width="100%" height="100%">
@ -27,3 +28,12 @@
[attr.stroke-width]="1 / scaleFactor" fill="#FFFFFF" stroke="#666666"></circle> [attr.stroke-width]="1 / scaleFactor" fill="#FFFFFF" stroke="#666666"></circle>
</g> </g>
</svg> </svg>
</div>
<div class="temperature-bg">
<ng-content></ng-content>
</div>
<div class="power-bg" [ngClass]="{'off': off}" (click)="switchPower()">
<i class="ion-power"></i>
</div>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

@ -1,11 +1,66 @@
:host { @import '../../../../@theme/styles/variables';
display: block; @import '~@akveo/nga-theme/styles/global/bootstrap/hero-buttons';
@include nga-install-component() {
display: flex;
position: relative;
.svg-container {
position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 2;
} }
.svg-content { .temperature-bg {
display: block; position: absolute;
width: 100%; width: 94%;
height: 100%; height: 94%;
background-color: lighten(nga-theme(layout-bg), 2%);
border-radius: 50%;
top: 9%;
left: 3%;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.power-bg {
position: absolute;
width: 5.25rem;
height: 5.25rem;
background-color: nga-theme(card-bg);
border-radius: 50%;
bottom: 0;
left: 50%;
transform: translate(-50%, 50%);
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
box-shadow: nga-theme(card-shadow);
cursor: pointer;
font-size: 2.5rem;
color: nga-theme(color-fg-heading);
text-shadow: 0 0 6px rgba(255, 255, 255, 0.5);
transition: all 0.1s ease-out;
&:hover {
background-color: lighten(nga-theme(card-bg), 5%);
}
&:active {
background-color: darken(nga-theme(card-bg), 5%);
box-shadow: none;
}
&.off {
color: nga-theme(color-fg);
text-shadow: none;
}
}
} }

View file

@ -16,15 +16,20 @@ export class TemperatureDraggerComponent implements AfterViewInit, OnChanges {
@Input() fillColors: string|string[] = '#2ec6ff'; @Input() fillColors: string|string[] = '#2ec6ff';
@Input() disableArcColor: string = '#999999'; @Input() disableArcColor: string = '#999999';
@Input() bottomAngle: number = 90; @Input() bottomAngle: number = 90;
@Input() arcThickness: number = 6; // CSS pixels @Input() arcThickness: number = 20; // CSS pixels
@Input() knobRadius: number = 10; // CSS pixels @Input() knobRadius: number = 15; // CSS pixels
value: number = 0.5; value: number = 50;
@Output('valueChange') valueChange = new EventEmitter<Number>(); @Output('valueChange') valueChange = new EventEmitter<Number>();
@Input('value') set setValue(value) { @Input('value') set setValue(value) {
this.value = value; this.value = value;
} }
@Input() min: number = 0; // min output value
@Input() max: number = 100; // max output value
@Output() power = new EventEmitter<boolean>();
@HostListener('mouseup', ['$event']) @HostListener('mouseup', ['$event'])
onMouseUp(event) { onMouseUp(event) {
this.recalculateValue(event); this.recalculateValue(event);
@ -41,6 +46,9 @@ export class TemperatureDraggerComponent implements AfterViewInit, OnChanges {
this.invalidate(); this.invalidate();
} }
off: boolean = false;
oldValue: number;
scaleFactor: number = 1; scaleFactor: number = 1;
bottomAngleRad = 0; bottomAngleRad = 0;
radius: number = 100; radius: number = 100;
@ -64,6 +72,7 @@ export class TemperatureDraggerComponent implements AfterViewInit, OnChanges {
private init = false; private init = false;
constructor() { constructor() {
this.oldValue = this.value;
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
@ -79,8 +88,24 @@ export class TemperatureDraggerComponent implements AfterViewInit, OnChanges {
mouseDown(event) { mouseDown(event) {
this.isMouseDown = true; this.isMouseDown = true;
if (!this.off) {
this.recalculateValue(event); this.recalculateValue(event);
} }
}
switchPower() {
this.off = !this.off;
this.power.emit(!this.off);
if (this.off) {
this.oldValue = this.value;
this.value = this.min;
} else {
this.value = this.oldValue;
}
this.invalidatePinPosition();
}
private invalidate(): void { private invalidate(): void {
this.bottomAngleRad = TemperatureDraggerComponent.toRad(this.bottomAngle); this.bottomAngleRad = TemperatureDraggerComponent.toRad(this.bottomAngle);
@ -254,7 +279,7 @@ export class TemperatureDraggerComponent implements AfterViewInit, OnChanges {
} }
private invalidateNonSelectedArc() { private invalidateNonSelectedArc() {
const angle = this.bottomAngleRad / 2 + (1 - this.value) * (2 * Math.PI - this.bottomAngleRad); const angle = this.bottomAngleRad / 2 + (1 - this.getValuePercentage()) * (2 * Math.PI - this.bottomAngleRad);
this.styles.nonSelectedArc = { this.styles.nonSelectedArc = {
color: this.disableArcColor, color: this.disableArcColor,
d: `M ${this.radius},${this.radius} d: `M ${this.radius},${this.radius}
@ -269,7 +294,7 @@ export class TemperatureDraggerComponent implements AfterViewInit, OnChanges {
private invalidatePinPosition() { private invalidatePinPosition() {
const radiusOffset = this.thickness / 2; const radiusOffset = this.thickness / 2;
const curveRadius = this.radius - radiusOffset; const curveRadius = this.radius - radiusOffset;
const actualAngle = (2 * Math.PI - this.bottomAngleRad) * this.value + this.bottomAngleRad / 2; const actualAngle = (2 * Math.PI - this.bottomAngleRad) * this.getValuePercentage() + this.bottomAngleRad / 2;
this.styles.knobPosition = { this.styles.knobPosition = {
x: curveRadius * (1 - Math.sin(actualAngle)) + radiusOffset, x: curveRadius * (1 - Math.sin(actualAngle)) + radiusOffset,
y: curveRadius * (1 + Math.cos(actualAngle)) + radiusOffset, y: curveRadius * (1 + Math.cos(actualAngle)) + radiusOffset,
@ -298,6 +323,8 @@ export class TemperatureDraggerComponent implements AfterViewInit, OnChanges {
value = (actualAngle - this.bottomAngleRad / 2) / (2 * Math.PI - this.bottomAngleRad); value = (actualAngle - this.bottomAngleRad / 2) / (2 * Math.PI - this.bottomAngleRad);
} }
value = value * (this.max - this.min) + this.min;
if (this.value !== value) { if (this.value !== value) {
this.value = value; this.value = value;
this.valueChange.emit(this.value); this.valueChange.emit(this.value);
@ -306,6 +333,10 @@ export class TemperatureDraggerComponent implements AfterViewInit, OnChanges {
} }
} }
private getValuePercentage() {
return (this.value - this.min) / (this.max - this.min);
}
private static toRad(angle) { private static toRad(angle) {
return Math.PI * angle / 180; return Math.PI * angle / 180;
} }

View file

@ -1,14 +1,69 @@
@import '../../../@theme/styles/variables'; @import '../../../@theme/styles/variables';
@import '~@akveo/nga-theme/styles/global/bootstrap/hero-buttons';
@include nga-install-component() { @include nga-install-component() {
nga-card {
background-image: radial-gradient(circle at 50% 50%, #423f8c, #302c6e);
}
nga-tabset {
display: flex;
flex-direction: column;
height: 100%;
}
nga-tab.content-active { nga-tab.content-active {
display: flex; display: flex;
flex: 1;
justify-content: center; justify-content: center;
align-items: center;
position: relative;
height: 100%;
} }
ngx-temperature-dragger { ngx-temperature-dragger {
width: 80%; width: 80%;
} }
.temperature {
display: flex;
flex-direction: column;
align-items: center;
.value {
position: relative;
color: nga-theme(color-fg-heading);
font-family: nga-theme(font-secondary);
font-size: 4rem;
font-weight: nga-theme(font-weight-bolder);
&::before {
position: absolute;
content: '°';
top: 0;
right: -1.25rem;
}
}
.desc {
color: nga-theme(color-fg);
font-weight: nga-theme(font-weight-light);
}
&.off {
.value {
color: nga-theme(color-fg);
letter-spacing: 0.25rem;
padding-left: 0.5rem;
&::before {
display: none;
}
}
.desc {
display: none;
}
}
}
} }

View file

@ -8,9 +8,19 @@ import { NgaThemeService } from '@akveo/nga-theme';
<nga-card size="xmedium"> <nga-card size="xmedium">
<nga-tabset fullWidth> <nga-tabset fullWidth>
<nga-tab tabTitle="Temperature"> <nga-tab tabTitle="Temperature">
<ngx-temperature-dragger [(value)]="temperature" [arcThickness]="20" [knobRadius]="15" [bottomAngle]="90" <ngx-temperature-dragger [(value)]="temperature" (power)="powerOff = !$event" [min]="12" [max]="30"
[disableArcColor]="themeConfig.layoutBg" [arcThickness]="20" [knobRadius]="15" [bottomAngle]="90" [disableArcColor]="themeConfig.layoutBg"
[fillColors]="[themeConfig.colorInfo, themeConfig.colorSuccess, themeConfig.colorWarning]"> [fillColors]="[themeConfig.colorInfo, themeConfig.colorSuccess, themeConfig.colorWarning]">
<div class="temperature" [ngClass]="{ 'off': powerOff }">
<div class="value">
{{ powerOff ? '--' : (temperature | ngxRound) }}
</div>
<div class="desc">
Celsius
</div>
</div>
</ngx-temperature-dragger> </ngx-temperature-dragger>
</nga-tab> </nga-tab>
<nga-tab tabTitle="Humidity"> <nga-tab tabTitle="Humidity">
@ -21,12 +31,14 @@ import { NgaThemeService } from '@akveo/nga-theme';
`, `,
}) })
export class TemperatureComponent { export class TemperatureComponent {
temperature = 0.5;
themeConfig = {}; temperature: number = 23;
powerOff: boolean = false;
themeConfig: any = {};
constructor(private theme: NgaThemeService) { constructor(private theme: NgaThemeService) {
this.theme.getConfig().subscribe((config) => { this.theme.getConfig().subscribe(config => {
this.themeConfig = config; this.themeConfig = config;
}); });
} }

View file

@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { NgaCardModule, NgaThemeModule } from '@akveo/nga-theme'; import { NgaCardModule, NgaThemeModule } from '@akveo/nga-theme';
import { ThemeModule } from './@theme/theme.module';
@NgModule({ @NgModule({
exports: [ exports: [
@ -12,6 +13,7 @@ import { NgaCardModule, NgaThemeModule } from '@akveo/nga-theme';
RouterModule, RouterModule,
NgaCardModule, NgaCardModule,
NgaThemeModule, NgaThemeModule,
ThemeModule,
], ],
}) })
export class SharedModule { } export class SharedModule { }

View file

@ -120,7 +120,7 @@
"no-attribute-parameter-decorator": true, "no-attribute-parameter-decorator": true,
"use-life-cycle-interface": true, "use-life-cycle-interface": true,
"use-pipe-transform-interface": true, "use-pipe-transform-interface": true,
"pipe-naming": [true, "camelCase", ["ngx", "test"]], // "pipe-naming": [true, "camelCase", ["ngx", "test"]],
"component-class-suffix": true, "component-class-suffix": true,
"directive-class-suffix": true, "directive-class-suffix": true,
"templates-use-public": true, "templates-use-public": true,