diff --git a/src/app/@theme/styles/global.scss b/src/app/@theme/styles/global.scss
index 94c44a58..000d4ec5 100644
--- a/src/app/@theme/styles/global.scss
+++ b/src/app/@theme/styles/global.scss
@@ -1,3 +1,10 @@
@mixin ngx-global-theme() {
// any global, non-component styles go here
}
+
+// TODO: probably it makes sense to move it to mixin
+.nga-theme-cosmic {
+ nga-card nga-card-header + nga-card-body {
+ padding-top: 0;
+ }
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b86be7ea..29b6568f 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -3,6 +3,7 @@
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
+import { APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
@@ -11,7 +12,6 @@ import { HttpModule } from '@angular/http';
import { CoreModule } from './@core/core.module';
import { AppComponent } from './app.component';
-import { PagesModule } from './pages/pages.module';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
@@ -24,6 +24,9 @@ import { AppRoutingModule } from './app-routing.module';
CoreModule,
],
bootstrap: [AppComponent],
+ providers: [
+ { provide: APP_BASE_HREF, useValue: '/' },
+ ],
})
export class AppModule {
}
diff --git a/src/app/pages/dashboard/dashboard.component.html b/src/app/pages/dashboard/dashboard.component.html
index f80c10cb..8e6f9393 100644
--- a/src/app/pages/dashboard/dashboard.component.html
+++ b/src/app/pages/dashboard/dashboard.component.html
@@ -6,7 +6,10 @@
- Content #1
+
+
Content #2
@@ -30,6 +33,8 @@
Room Management
+
+
diff --git a/src/app/pages/dashboard/dashboard.component.ts b/src/app/pages/dashboard/dashboard.component.ts
index a4539cbc..3ddde498 100644
--- a/src/app/pages/dashboard/dashboard.component.ts
+++ b/src/app/pages/dashboard/dashboard.component.ts
@@ -6,4 +6,5 @@ import { Component } from '@angular/core';
templateUrl: './dashboard.component.html',
})
export class DashboardComponent {
+ currentValue = 0.5;
}
diff --git a/src/app/pages/dashboard/dashboard.module.ts b/src/app/pages/dashboard/dashboard.module.ts
index 7e7c1f7c..76fdf4b7 100644
--- a/src/app/pages/dashboard/dashboard.module.ts
+++ b/src/app/pages/dashboard/dashboard.module.ts
@@ -4,6 +4,7 @@ import { NgaTabsetModule } from '@nga/theme';
import { SharedModule } from '../../shared.module';
import { DashboardComponent } from './dashboard.component';
import { StatusCardsComponent } from './status-cards/status-cards.component';
+import { TemperatureDraggerComponent } from './temperature-dragger/temperature-dragger.component';
@NgModule({
imports: [
@@ -13,6 +14,7 @@ import { StatusCardsComponent } from './status-cards/status-cards.component';
declarations: [
DashboardComponent,
StatusCardsComponent,
+ TemperatureDraggerComponent,
],
})
export class DashboardModule { }
diff --git a/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.html b/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.html
new file mode 100644
index 00000000..4edf4494
--- /dev/null
+++ b/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.html
@@ -0,0 +1,29 @@
+
diff --git a/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.scss b/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.scss
new file mode 100644
index 00000000..5ebce9a9
--- /dev/null
+++ b/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.scss
@@ -0,0 +1,11 @@
+:host {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+.svg-content {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
diff --git a/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.ts b/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.ts
new file mode 100644
index 00000000..5f0192ef
--- /dev/null
+++ b/src/app/pages/dashboard/temperature-dragger/temperature-dragger.component.ts
@@ -0,0 +1,302 @@
+import {
+ Component, HostListener, ViewChild, ElementRef, Input, Output, EventEmitter, AfterViewInit,
+} from '@angular/core';
+
+const VIEW_BOX_SIZE = 300;
+
+@Component({
+ selector: 'ngx-temperature-dragger',
+ templateUrl: './temperature-dragger.component.html',
+ styleUrls: ['./temperature-dragger.component.scss'],
+})
+export class TemperatureDraggerComponent implements AfterViewInit {
+
+ @ViewChild('svgRoot') svgRoot: ElementRef;
+
+ @Input() fillColors: String|String[] = '#2ec6ff';
+ @Input() disableArcColor: String = '#999999';
+ @Input() bottomAngle: number = 90;
+ @Input() arcThickness: number = 6; // CSS pixels
+ @Input() knobRadius: number = 10; // CSS pixels
+
+ value: number = 0.5;
+ @Output('valueChange') valueChange = new EventEmitter();
+ @Input('value') set setValue(value) {
+ this.value = value;
+ }
+
+ @HostListener('mouseup', ['$event'])
+ onMouseUp(event) {
+ this.recalculateValue(event);
+ this.isMouseDown = false;
+ }
+
+ @HostListener('mousemove', ['$event'])
+ onMouseMove(event: MouseEvent) {
+ this.recalculateValue(event);
+ }
+
+ @HostListener('window:resize', ['$event'])
+ onResize(event) {
+ this.invalidate();
+ }
+
+ scaleFactor: number = 1;
+ bottomAngleRad = 0;
+ radius: number = 100;
+ translateXValue = 0;
+ translateYValue = 0;
+ thickness = 6;
+ pinRadius = 10;
+ colors: any = [];
+
+ styles = {
+ viewBox: '0 0 300 300',
+ arcTranslateStr: 'translate(0, 0)',
+ clipPathStr: '',
+ gradArcs: [],
+ nonSelectedArc: {},
+ knobPosition: { x: 0, y: 0 },
+ blurRadius: 15,
+ };
+
+ private isMouseDown = false;
+
+ constructor() {
+ }
+
+ ngAfterViewInit(): void {
+ this.invalidate();
+ }
+
+ mouseDown(event) {
+ this.isMouseDown = true;
+ this.recalculateValue(event);
+ }
+
+ private invalidate(): void {
+ this.bottomAngleRad = TemperatureDraggerComponent.toRad(this.bottomAngle);
+ this.calculateVars();
+
+ this.invalidateClipPathStr();
+ this.invalidateGradientArcs();
+ this.invalidatePinPosition();
+ }
+
+ private calculateVars() {
+ this.bottomAngleRad = TemperatureDraggerComponent.toRad(this.bottomAngle);
+ this.colors = (typeof this.fillColors === 'string') ? [this.fillColors] : this.fillColors;
+
+ const baseRadius: number = VIEW_BOX_SIZE / 2;
+ const halfAngle = this.bottomAngleRad / 2;
+
+ const svgBoundingRect = this.svgRoot.nativeElement.getBoundingClientRect();
+ const svgAreaFactor = svgBoundingRect.width / svgBoundingRect.height;
+ const svgHeight = VIEW_BOX_SIZE / svgAreaFactor;
+ const knobMargin = this.knobRadius > this.arcThickness
+ ? (this.knobRadius - this.arcThickness / 2) / this.scaleFactor
+ : 0;
+
+ this.scaleFactor = svgBoundingRect.width / VIEW_BOX_SIZE;
+ this.styles.viewBox = `0 0 ${VIEW_BOX_SIZE} ${svgHeight}`;
+
+
+ const circleFactor = this.bottomAngleRad <= Math.PI
+ ? ( 2 / (1 + Math.cos(halfAngle)) )
+ : ( 2 * Math.sin(halfAngle) / (1 + Math.cos(halfAngle)) );
+ if (circleFactor > svgAreaFactor) {
+ if (this.bottomAngleRad > Math.PI) {
+ this.radius = (VIEW_BOX_SIZE - 2 * knobMargin) / (2 * Math.sin(halfAngle));
+ } else {
+ this.radius = VIEW_BOX_SIZE / 2 - knobMargin;
+ }
+
+ } else {
+ this.radius = (svgHeight - 2 * knobMargin) / (1 + Math.cos(halfAngle));
+ }
+
+ this.translateXValue = VIEW_BOX_SIZE / 2 - this.radius;
+ this.translateYValue = (svgHeight) / 2 - this.radius * (1 + Math.cos(halfAngle)) / 2;
+
+ this.styles.arcTranslateStr = `translate(${this.translateXValue}, ${this.translateYValue})`;
+
+ this.thickness = this.arcThickness / this.scaleFactor;
+ this.pinRadius = this.knobRadius / this.scaleFactor;
+ }
+
+
+ private calculateClipPathSettings() {
+ const halfAngle = this.bottomAngleRad / 2;
+ const innerRadius = this.radius - this.thickness;
+
+ const xStartMultiplier = 1 - Math.sin(halfAngle);
+ const yMultiplier = 1 + Math.cos(halfAngle);
+ const xEndMultiplier = 1 + Math.sin(halfAngle);
+
+ return {
+ outer: {
+ start: {
+ x: xStartMultiplier * this.radius,
+ y: yMultiplier * this.radius,
+ },
+ end: {
+ x: xEndMultiplier * this.radius,
+ y: yMultiplier * this.radius,
+ },
+ radius: this.radius,
+ },
+ inner: {
+ start: {
+ x: xStartMultiplier * innerRadius + this.thickness,
+ y: yMultiplier * innerRadius + this.thickness,
+ },
+ end: {
+ x: xEndMultiplier * innerRadius + this.thickness,
+ y: yMultiplier * innerRadius + this.thickness,
+ },
+ radius: innerRadius,
+ },
+ thickness: this.thickness,
+ big: this.bottomAngleRad < Math.PI ? '1' : '0',
+ };
+
+ }
+
+ private invalidateClipPathStr() {
+ const s = this.calculateClipPathSettings();
+
+ let path = `M ${s.outer.start.x},${s.outer.start.y}`; // Start at startangle top
+
+ // Outer arc
+ path += ` A ${s.outer.radius},${s.outer.radius}` + // Draw an arc of radius 'radius'
+ ` 0 ${s.big} 1` + // Arc details...
+ ` ${s.outer.end.x},${s.outer.end.y}`; // Arc goes to top end angle coordinate
+
+ // Outer to inner connector
+ path += ` A ${s.thickness / 2},${s.thickness / 2}` +
+ ` 0 1 1` +
+ ` ${s.inner.end.x},${s.inner.end.y}`;
+
+ // Inner arc
+ path += ` A ${s.inner.radius},${s.inner.radius}` +
+ ` 1 ${s.big} 0` +
+ ` ${s.inner.start.x},${s.inner.start.y}`;
+
+ // Outer to inner connector
+ path += ` A ${s.thickness / 2},${s.thickness / 2}` +
+ ` 0 1 1` +
+ ` ${s.outer.start.x},${s.outer.start.y}`;
+
+ // Close path
+ path += ' Z';
+ this.styles.clipPathStr = path;
+ }
+
+ private calculateGradientConePaths(angleStep) {
+ const radius = this.radius;
+
+ function calcX(angle) {
+ return radius * (1 - 2 * Math.sin(angle));
+ }
+
+ function calcY(angle) {
+ return radius * (1 + 2 * Math.cos(angle));
+ }
+
+ const gradArray = [];
+
+ for (let i = 0, currentAngle = this.bottomAngleRad / 2; i < this.colors.length; i++, currentAngle += angleStep) {
+ gradArray.push({
+ start: { x: calcX(currentAngle), y: calcY(currentAngle) },
+ end: { x: calcX(currentAngle + angleStep), y: calcY(currentAngle + angleStep) },
+ big: Math.PI <= angleStep ? 1 : 0,
+ });
+ }
+ return gradArray;
+ }
+
+ private invalidateGradientArcs() {
+ const radius = this.radius;
+
+ function getArc(des) {
+ return `M ${radius},${radius}` +
+ ` L ${des.start.x},${des.start.y}` +
+ ` A ${2 * radius},${2 * radius}` +
+ ` 0 ${des.big} 1` +
+ ` ${des.end.x},${des.end.y}` +
+ ` Z`;
+ }
+
+ const angleStep = (2 * Math.PI - this.bottomAngleRad) / this.colors.length;
+ const s = this.calculateGradientConePaths(angleStep);
+
+ this.styles.gradArcs = [];
+ for (let i = 0; i < s.length; i++) {
+ const si = s[i];
+ const arcValue = getArc(si);
+ this.styles.gradArcs.push({
+ color: this.colors[i],
+ d: arcValue,
+ });
+ }
+
+ this.styles.blurRadius = 2 * radius * Math.sin(angleStep / 6);
+ }
+
+ private invalidateNonSelectedArc() {
+ const angle = this.bottomAngleRad / 2 + (1 - this.value) * (2 * Math.PI - this.bottomAngleRad);
+ this.styles.nonSelectedArc = {
+ color: this.disableArcColor,
+ d: `M ${this.radius},${this.radius}` +
+ ` L ${this.radius},${3 * this.radius}` +
+ ` A ${2 * this.radius},${2 * this.radius}` +
+ ` 1 ${angle > Math.PI ? '1' : '0'} 0` +
+ ` ${this.radius + this.radius * 2 * Math.sin(angle)},${this.radius + this.radius * 2 * Math.cos(angle)}` +
+ ` Z`,
+ };
+ }
+
+ private invalidatePinPosition() {
+ const radiusOffset = this.thickness / 2;
+ const curveRadius = this.radius - radiusOffset;
+ const actualAngle = (2 * Math.PI - this.bottomAngleRad) * this.value + this.bottomAngleRad / 2;
+ this.styles.knobPosition = {
+ x: curveRadius * (1 - Math.sin(actualAngle)) + radiusOffset,
+ y: curveRadius * (1 + Math.cos(actualAngle)) + radiusOffset,
+ };
+ this.invalidateNonSelectedArc();
+ }
+
+ private recalculateValue(event) {
+ if (this.isMouseDown) {
+ const rect = this.svgRoot.nativeElement.getBoundingClientRect();
+ const center = {
+ x: rect.left + VIEW_BOX_SIZE * this.scaleFactor / 2,
+ y: rect.top + (this.translateYValue + this.radius) * this.scaleFactor,
+ };
+ let actualAngle = Math.atan2(center.x - event.clientX, event.clientY - center.y);
+ if (actualAngle < 0) {
+ actualAngle = actualAngle + 2 * Math.PI;
+ }
+
+ let value = 0;
+ if (actualAngle < this.bottomAngleRad / 2) {
+ value = 0;
+ } else if (actualAngle > 2 * Math.PI - this.bottomAngleRad / 2) {
+ value = 1;
+ } else {
+ value = (actualAngle - this.bottomAngleRad / 2) / (2 * Math.PI - this.bottomAngleRad);
+ }
+
+ if (this.value !== value) {
+ this.value = value;
+ this.valueChange.emit(this.value);
+ this.invalidatePinPosition();
+ }
+ }
+ }
+
+ private static toRad(angle) {
+ return Math.PI * angle / 180;
+ }
+}
diff --git a/src/index.html b/src/index.html
index 4809f39c..00abe18d 100644
--- a/src/index.html
+++ b/src/index.html
@@ -3,7 +3,6 @@
NgX Admin Demo
-