Skip to content
Commits on Source (2)
......@@ -115,7 +115,14 @@ import { ToggleComponent } from './components/toggle/toggle.component';
import { MarketingAsFeaturedInComponent } from './components/marketing/as-featured-in.component';
import { SidebarMenuComponent } from './components/sidebar-menu/sidebar-menu.component';
import { ChartV2Component } from './components/chart-v2/chart-v2.component';
import { MiniChartComponent } from './components/mini-chart/mini-chart.component';
import * as PlotlyJS from 'plotly.js/dist/plotly.js';
import { PlotlyModule } from 'angular-plotly.js';
import { PageLayoutComponent } from './components/page-layout/page-layout.component';
import { DashboardLayoutComponent } from './components/dashboard-layout/dashboard-layout.component';
import { ShadowboxLayoutComponent } from './components/shadowbox-layout/shadowbox-layout.component';
import { ShadowboxHeaderComponent } from './components/shadowbox-header/shadowbox-header.component';
PlotlyModule.plotlyjs = PlotlyJS;
@NgModule({
imports: [
......@@ -124,6 +131,7 @@ import { MiniChartComponent } from './components/mini-chart/mini-chart.component
RouterModule,
FormsModule,
ReactiveFormsModule,
PlotlyModule,
],
declarations: [
MINDS_PIPES,
......@@ -220,7 +228,10 @@ import { MiniChartComponent } from './components/mini-chart/mini-chart.component
MarketingAsFeaturedInComponent,
SidebarMenuComponent,
ChartV2Component,
MiniChartComponent,
PageLayoutComponent,
DashboardLayoutComponent,
ShadowboxLayoutComponent,
ShadowboxHeaderComponent,
],
exports: [
MINDS_PIPES,
......@@ -312,6 +323,10 @@ import { MiniChartComponent } from './components/mini-chart/mini-chart.component
MarketingComponent,
MarketingAsFeaturedInComponent,
SidebarMenuComponent,
ChartV2Component,
PageLayoutComponent,
DashboardLayoutComponent,
ShadowboxLayoutComponent,
],
providers: [
SiteService,
......
<!-- <div
<div
#chartContainer
class="m-chartV2__chartContainer"
[ngClass]="{ isTouchDevice: isTouchDevice }"
[ngClass]="{ isTouchDevice: isTouchDevice, isMini: isMini }"
>
<plotly-plot
*ngIf="init"
#graphDiv
id="graphDiv"
[data]="data"
[layout]="layout"
[config]="config"
......@@ -14,48 +12,70 @@
[style]="{ position: 'relative' }"
(hover)="onHover($event)"
(unhover)="onUnhover($event)"
(plotly_click)="onClick($event)"
id="graphDiv"
>
</plotly-plot>
</div>
<div #hoverInfoDiv id="hoverInfoDiv" class="m-chartV2__hoverInfoDiv">
<i *ngIf="isTouchDevice" class="material-icons" (click)="onUnhover($event)"
<div #hoverInfoDiv class="m-chartV2__hoverInfoDiv">
<i
*ngIf="isTouchDevice"
class="material-icons m-chartV2__hoverInfo__closeBtn"
(click)="onUnhover($event)"
>close</i
>
<div class="m-chartV2__hoverInfo__row">
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div
[ngSwitch]="selectedMetric?.unit"
class="m-chartV2__hoverInfo__row--primary"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.value | number }} {{ selectedMetric.label | lowercase }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.value | currency }} USD
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.value | number: '1.1-3' }} {{ selectedMetric?.unit }}
</ng-template>
</div>
<div class="m-chartV2__hoverInfo__row" *ngIf="isComparison">
vs
<ng-container
[ngSwitch]="selectedMetric?.unit"
class="m-chartV2__hoverInfo__row"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.comparisonValue | number }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.comparisonValue | currency }}
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.comparisonValue | number: '1.1-3' }}
</ng-template>
</ng-container>
on {{ hoverInfo.comparisonDate | utcDate | date: datePipe }}
<div class="m-chartV2__hoverInfo__wrapper">
<div class="m-chartV2__hoverInfo__arrowContainer" *ngIf="isMini">
<i class="material-icons">arrow_upward</i>
</div>
<div class="m-chartV2__hoverInfo__rowsContainer">
<div class="m-chartV2__hoverInfo__row">
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div
[ngSwitch]="rawData?.unit"
class="m-chartV2__hoverInfo__row--primary"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.value | number: '1.0-0' | abbr }}
{{ rawData.label | lowercase }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.value | currency }} USD
</ng-template>
<ng-template ngSwitchCase="eth">
{{ hoverInfo.value | number: '1.3-3' }} ETH
</ng-template>
<ng-template ngSwitchCase="tokens">
{{ hoverInfo.value | number: '1.1-3' }} Tokens
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.value | number: '1.0-3' }} {{ rawData?.unit }}
</ng-template>
</div>
<div class="m-chartV2__hoverInfo__row" *ngIf="isComparison">
vs
<ng-container
[ngSwitch]="rawData?.unit"
class="m-chartV2__hoverInfo__row"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.comparisonValue | number: '1.0-0' | abbr }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.comparisonValue | currency }}
</ng-template>
<ng-template ngSwitchCase="eth">
{{ hoverInfo.value | number: '1.3-3' }} ETH
</ng-template>
<ng-template ngSwitchCase="tokens">
{{ hoverInfo.value | number: '1.1-3' }} Tokens
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.comparisonValue | number: '1.1-3' }}
</ng-template>
</ng-container>
on {{ hoverInfo.comparisonDate | utcDate | date: datePipe }}
</div>
</div>
</div>
</div> -->
</div>
m-chartV2 {
display: block;
position: relative;
margin-left: 40px;
}
.js-plotly-plot,
.plot-container {
height: 44vh;
min-height: 44vh;
display: block;
}
#graphDiv {
display: block;
position: relative;
g,
g > * {
cursor: default;
}
> * {
transition: background-color 0.3s cubic-bezier(0.23, 1, 0.32, 1),
color 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}
.main-svg {
max-width: 100%;
}
}
.m-chartV2__hoverInfoDiv {
width: 160px;
padding: 12px;
position: absolute;
pointer-events: none;
border-radius: 3px;
font-size: 12px;
z-index: 9999999999;
opacity: 0;
transition: opacity 0.2s ease-in;
@include m-theme() {
background-color: themed($m-white);
box-shadow: 0 0 4px rgba(themed($m-black), 0.3);
color: themed($m-grey-300);
}
[class*='m-chartV2__hoverInfo__row'] {
padding-bottom: 4px;
font-weight: 300;
&:last-of-type {
padding-top: 2px;
}
}
.m-chartV2__hoverInfo__row--primary {
font-weight: 400;
font-size: 15px;
@include m-theme() {
color: themed($m-grey-600);
}
}
.m-chartV2__hoverInfo__closeBtn {
display: none;
font-size: 15px;
position: absolute;
cursor: pointer;
top: 10px;
right: 10px;
transition: color 0.3s cubic-bezier(0.23, 1, 0.32, 1);
@include m-theme() {
color: themed($m-grey-300);
}
&:active {
@include m-theme() {
color: themed($m-grey-500);
}
}
}
}
// ----------------------------------------------------
.isTouchDevice .m-chartV2__hoverInfoDiv .m-chartV2__hoverInfo__closeBtn {
display: block;
}
@media screen and (max-width: $min-tablet) {
m-chartV2 {
margin-left: 16px;
}
}
// ----------------------------------------------------
m-chartV2.isMini {
margin-left: 0;
margin-top: 24px;
.js-plotly-plot,
.plot-container {
height: 40px;
min-height: 40px;
}
.m-chartV2__chartContainer {
// margin-right: 24px;
}
.m-chartV2__hoverInfoDiv {
width: 150px;
padding: 0px;
.m-chartV2__hoverInfo__wrapper {
display: flex;
}
.m-chartV2__hoverInfo__rowsContainer {
display: flex;
flex-direction: column;
padding: 14px 14px 14px 0;
}
.m-chartV2__hoverInfo__arrowContainer {
width: 20px;
i {
margin-left: -4px;
transform: rotate(-45deg) scaleX(0.5);
@include m-theme() {
color: themed($m-grey-600);
}
}
}
[class*='m-chartV2__hoverInfo__row'] {
line-height: 1.1;
}
.m-chartV2__hoverInfo__row--primary {
font-size: 12px;
}
}
@media screen and (max-width: $min-tablet) {
margin-left: 0;
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { UtcDatePipe } from '../../pipes/utcdate';
import { AbbrPipe } from '../../pipes/abbr';
import { MockService } from '../../../utils/mock';
import { ThemeService } from '../../services/theme.service';
import { ChartV2Component } from './chart-v2.component';
describe('ChartV2Component', () => {
......@@ -8,14 +12,46 @@ describe('ChartV2Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ChartV2Component],
declarations: [ChartV2Component, UtcDatePipe, AbbrPipe],
providers: [
{
provide: ThemeService,
useValue: MockService(ThemeService),
},
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ChartV2Component);
component = fixture.componentInstance;
fixture.detectChanges();
component.rawData = {
id: 'views',
label: 'Pageviews',
permissions: ['admin', 'user'],
unit: 'usd',
description: '',
visualisation: {
type: 'chart',
segments: [
{
buckets: [
{
key: 1567296000000,
date: '2019-09-01T00:00:00+00:00',
value: 11,
},
{
key: 1567382400000,
date: '2019-09-02T00:00:00+00:00',
value: 12,
},
],
},
],
},
};
});
it('should create', () => {
......
<div class="m-dashboardLayout__header">
<ng-content select="[m-dashboardLayout__header]"></ng-content>
</div>
<div class="m-dashboardLayout__body">
<ng-content select="[m-dashboardLayout__body]"></ng-content>
</div>
m-dashboardLayout {
display: block;
width: 100%;
max-width: 100%;
}
.m-dashboardLayout__header {
h3 {
font-size: 26px;
font-weight: 500;
margin-top: 0;
margin-right: 24px;
}
}
.m-dashboardLayout__body {
position: relative;
display: block;
width: 100%;
}
@media screen and (max-width: $min-tablet) {
m-dashboardLayout {
display: block;
padding: 0;
max-width: none;
width: 100%;
}
.m-dashboardLayout__header {
padding-left: 24px;
}
}
@media screen and (max-width: $max-mobile) {
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MiniChartComponent } from './mini-chart.component';
import { DashboardLayoutComponent } from './dashboard-layout.component';
describe('MiniChartComponent', () => {
let component: MiniChartComponent;
let fixture: ComponentFixture<MiniChartComponent>;
describe('DashboardLayoutComponent', () => {
let component: DashboardLayoutComponent;
let fixture: ComponentFixture<DashboardLayoutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MiniChartComponent],
declarations: [DashboardLayoutComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MiniChartComponent);
fixture = TestBed.createComponent(DashboardLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
......
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'm-miniChart',
templateUrl: './mini-chart.component.html',
selector: 'm-dashboardLayout',
templateUrl: './dashboard-layout.component.html',
})
export class MiniChartComponent implements OnInit {
export class DashboardLayoutComponent implements OnInit {
constructor() {}
ngOnInit() {}
......
<m-sidebarMenu [catId]="navId"></m-sidebarMenu>
<section class="m-pageLayout__main">
<ng-content select="[m-pageLayout__main]"></ng-content>
</section>
m-pageLayout {
display: block;
position: relative;
width: 100%;
padding-top: 56px;
margin-bottom: 48px;
@include m-theme() {
background-color: themed($m-white);
color: themed($m-grey-800);
}
.m-tooltip {
margin-left: 4px;
i {
font-size: 12px;
@include m-theme() {
color: rgba(themed($m-grey-300), 0.7);
}
}
.m-tooltip--bubble {
z-index: 9999;
font-size: 11px;
@include m-theme() {
color: themed($m-white);
background-color: themed($m-blue);
}
}
}
}
m-sidebarMenu {
display: block;
box-sizing: border-box;
padding-left: 105px;
width: 245px;
@include m-theme() {
background-color: themed($m-white);
}
}
.m-pageLayout__main {
margin-left: 350px;
margin-right: 24px;
@include m-theme() {
background-color: themed($m-white);
color: themed($m-grey-800);
}
}
@media screen and (max-width: $min-tablet) {
.m-pageLayout__main {
display: block;
margin: 0;
}
m-sidebarMenu {
margin-left: 0;
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, Input } from '@angular/core';
import { PageLayoutComponent } from './page-layout.component';
@Component({
selector: 'm-sidebarMenu',
template: '',
})
class SidebarMenuComponentMock {
@Input() catId;
}
describe('PageLayoutComponent', () => {
let component: PageLayoutComponent;
let fixture: ComponentFixture<PageLayoutComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PageLayoutComponent, SidebarMenuComponentMock],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PageLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'm-pageLayout',
templateUrl: './page-layout.component.html',
})
export class PageLayoutComponent implements OnInit {
@Input() navId: string;
constructor() {}
ngOnInit() {}
}
<section class="m-shadowboxHeader__section">
<div class="m-shadowboxHeader__wrapper">
<ng-container *ngIf="isScrollable">
<div
*ngIf="isOverflown && !isAtScrollStart"
class="m-shadowboxHeader__overflowFade--left"
></div>
<div
[ngClass]="{ showButton: showButton.left }"
class="m-shadowboxHeader__overflowScrollButton--left"
(click)="slide('left')"
>
<i class="material-icons">chevron_left</i>
</div>
</ng-container>
<div
#shadowboxHeaderContainer
class="m-shadowboxHeader__container disable-scrollbars"
(scroll)="onScroll($event)"
>
<ng-content select=".m-shadowboxLayout__header"></ng-content>
</div>
<ng-container *ngIf="isScrollable">
<div
*ngIf="isOverflown && !isAtScrollEnd"
class="m-shadowboxHeader__overflowFade--right"
></div>
<div
[ngClass]="{ showButton: showButton.right }"
class="m-shadowboxHeader__overflowScrollButton--right"
(click)="slide('right')"
>
<i class="material-icons">chevron_right</i>
</div>
</ng-container>
</div>
</section>
m-shadowboxHeader {
min-height: 116px;
display: block;
}
.m-shadowboxHeader__section {
position: relative;
}
.m-shadowboxHeader__wrapper {
position: relative;
z-index: 1;
height: 124px;
@include m-theme() {
box-shadow: 0 7px 15px -7px rgba(themed($m-black-always), 0.1);
}
}
.m-shadowboxHeader__container {
overflow-x: hidden;
overflow-y: hidden;
// display: flex;
// flex-wrap: nowrap;
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
&.disable-scrollbars {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
&::-webkit-scrollbar {
width: 0px;
background: transparent; /* Chrome/Safari/Webkit */
}
}
.m-tooltip--bubble {
width: 160px;
}
}
[class*='m-shadowboxHeader__overflowFade--'] {
position: absolute;
top: 0;
bottom: 0;
width: 24px;
z-index: 2;
&.m-shadowboxHeader__overflowFade--right {
@include m-theme() {
right: 0;
background: linear-gradient(
to right,
rgba(themed($m-white), 0) 0,
themed($m-white) 50%
);
}
}
&.m-shadowboxHeader__overflowFade--left {
@include m-theme() {
left: 0;
background: linear-gradient(
to left,
rgba(themed($m-white), 0) 0,
themed($m-white) 50%
);
}
}
}
[class*='m-shadowboxHeader__overflowScrollButton--'] {
position: absolute;
top: 50%;
border-radius: 50%;
box-sizing: border-box;
z-index: 2;
transform: translateY(-50%);
opacity: 0;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
cursor: pointer;
&.showButton {
opacity: 1;
}
@include m-theme() {
background-color: themed($m-white);
box-shadow: 0px 0px 10px -3px rgba(themed($m-black-always), 0.3);
border: 1px solid themed($m-white);
}
&:hover {
@include m-theme() {
border: 1px solid themed($m-blue);
}
}
&.m-shadowboxHeader__overflowScrollButton--right {
right: -12;
}
&.m-shadowboxHeader__overflowScrollButton--left {
left: -12;
}
i {
@include m-theme() {
color: themed($m-grey-200);
}
}
}
@media screen and (max-width: $min-tablet) {
.m-shadowboxHeader__section {
[class*='m-shadowboxHeader__overflowScrollButton--'] {
display: none;
}
.m-shadowboxHeader__container {
overflow-x: scroll;
scroll-snap-type: x mandatory;
.m-analytics__metric {
scroll-snap-align: start;
&:first-child {
margin-left: 16px;
}
&:last-child {
margin-right: 16px;
}
}
}
}
.m-shadowboxHeader__wrapper {
@include m-theme() {
box-shadow: none;
}
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {
Component,
Input,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
ViewChild,
ElementRef,
HostListener,
} from '@angular/core';
import { ShadowboxHeaderComponent } from './shadowbox-header.component';
describe('ShadowboxHeaderComponent', () => {
let component: ShadowboxHeaderComponent;
let fixture: ComponentFixture<ShadowboxHeaderComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ShadowboxHeaderComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ShadowboxHeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {
Component,
Input,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
ViewChild,
ElementRef,
HostListener,
} from '@angular/core';
@Component({
selector: 'm-shadowboxHeader',
templateUrl: './shadowbox-header.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShadowboxHeaderComponent implements AfterViewInit {
@Input() isScrollable: boolean = true;
@Input() metricActivated;
@ViewChild('shadowboxHeaderContainer', { static: false })
shadowboxHeaderContainerEl: ElementRef;
shadowboxHeaderContainer;
childClientWidth: number;
faderWidth = 24;
isOverflown: boolean = false;
isAtScrollEnd = false;
isAtScrollStart = true;
showButton = { left: false, right: false };
constructor(private cd: ChangeDetectorRef) {}
ngAfterViewInit() {
this.checkOverflow();
// const activeMetric = ;//get the index of the metric with .active
// this.slideToActiveMetric();
}
// updateMetric(metric) {
// // TODO: if clicked metric is not fully visible, slide() until it is
// this.analyticsService.updateMetric(metric.id);
// }
// ----------------------------------------------------
@HostListener('click', ['$event.target'])
onClick(target) {
console.log('***Clicked on: ', target);
// this.slideToActiveMetric(metricIndex);
}
slideToActiveMetric(metricIndex) {
// TODOOJM
}
// ----------------------------------------------------
@HostListener('window:resize')
onResize() {
this.checkOverflow();
}
onScroll($event) {
this.checkOverflow();
}
checkOverflow() {
if (!this.isScrollable) {
return;
}
const firstMetric = <HTMLElement>(
document.querySelector('.m-shadowboxLayout__headerItem')
);
// TODO: figure out how to avoid test failure "Cannot read property 'clientWidth' of null"
this.childClientWidth = firstMetric ? firstMetric.clientWidth : 160;
this.shadowboxHeaderContainer = this.shadowboxHeaderContainerEl.nativeElement;
this.isOverflown =
this.shadowboxHeaderContainer.scrollWidth -
this.shadowboxHeaderContainer.clientWidth >
0;
this.isAtScrollStart =
this.shadowboxHeaderContainer.scrollLeft < this.faderWidth;
this.showButton.left = this.isOverflown && !this.isAtScrollStart;
this.isAtScrollEnd =
!this.isOverflown ||
this.shadowboxHeaderContainer.scrollWidth -
(this.shadowboxHeaderContainer.scrollLeft +
this.shadowboxHeaderContainer.clientWidth) <
this.faderWidth;
this.showButton.right =
this.isOverflown &&
this.shadowboxHeaderContainer.scrollLeft >= 0 &&
!this.isAtScrollEnd;
this.detectChanges();
}
slide(direction) {
let currentScrollLeft = this.shadowboxHeaderContainer.scrollLeft;
let targetScrollLeft;
let scrollEndOffset = 0;
const partiallyVisibleMetricWidth =
this.shadowboxHeaderContainer.clientWidth % this.childClientWidth;
const completelyVisibleMetricsWidth =
this.shadowboxHeaderContainer.clientWidth - partiallyVisibleMetricWidth;
if (direction === 'right') {
if (currentScrollLeft < this.faderWidth) {
currentScrollLeft = this.faderWidth;
}
targetScrollLeft = Math.min(
currentScrollLeft + completelyVisibleMetricsWidth,
this.shadowboxHeaderContainer.scrollWidth -
completelyVisibleMetricsWidth
);
} else {
if (this.isAtScrollEnd) {
scrollEndOffset = partiallyVisibleMetricWidth - this.faderWidth;
}
targetScrollLeft = Math.max(
currentScrollLeft - completelyVisibleMetricsWidth + scrollEndOffset,
0
);
}
this.shadowboxHeaderContainer.scrollTo({
top: 0,
left: targetScrollLeft,
behavior: 'smooth',
});
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
}