...
 
Commits (2)
......@@ -113,6 +113,9 @@ import { MarketingComponent } from './components/marketing/marketing.component';
import { MarketingFooterComponent } from './components/marketing/footer.component';
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';
@NgModule({
imports: [
......@@ -215,6 +218,9 @@ import { MarketingAsFeaturedInComponent } from './components/marketing/as-featur
MarketingComponent,
MarketingFooterComponent,
MarketingAsFeaturedInComponent,
SidebarMenuComponent,
ChartV2Component,
MiniChartComponent,
],
exports: [
MINDS_PIPES,
......@@ -305,6 +311,7 @@ import { MarketingAsFeaturedInComponent } from './components/marketing/as-featur
ToggleComponent,
MarketingComponent,
MarketingAsFeaturedInComponent,
SidebarMenuComponent,
],
providers: [
SiteService,
......
<!-- <div
#chartContainer
class="m-chartV2__chartContainer"
[ngClass]="{ isTouchDevice: isTouchDevice }"
>
<plotly-plot
*ngIf="init"
#graphDiv
id="graphDiv"
[data]="data"
[layout]="layout"
[config]="config"
[useResizeHandler]="true"
[style]="{ position: 'relative' }"
(hover)="onHover($event)"
(unhover)="onUnhover($event)"
(plotly_click)="onClick($event)"
>
</plotly-plot>
</div>
<div #hoverInfoDiv id="hoverInfoDiv" class="m-chartV2__hoverInfoDiv">
<i *ngIf="isTouchDevice" class="material-icons" (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>
</div> -->
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AnalyticsMenuComponent } from './menu.component';
import { ChartV2Component } from './chart-v2.component';
describe('AnalyticsMenuComponent', () => {
let component: AnalyticsMenuComponent;
let fixture: ComponentFixture<AnalyticsMenuComponent>;
describe('ChartV2Component', () => {
let component: ChartV2Component;
let fixture: ComponentFixture<ChartV2Component>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AnalyticsMenuComponent],
declarations: [ChartV2Component],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AnalyticsMenuComponent);
fixture = TestBed.createComponent(ChartV2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
xit('should create', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
});
This diff is collapsed.
<p>
mini-chart works!
</p>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AnalyticsLayoutTableComponent } from './layout-table.component';
import { MiniChartComponent } from './mini-chart.component';
describe('AnalyticsLayoutTableComponent', () => {
let component: AnalyticsLayoutTableComponent;
let fixture: ComponentFixture<AnalyticsLayoutTableComponent>;
describe('MiniChartComponent', () => {
let component: MiniChartComponent;
let fixture: ComponentFixture<MiniChartComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AnalyticsLayoutTableComponent],
declarations: [MiniChartComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AnalyticsLayoutTableComponent);
fixture = TestBed.createComponent(MiniChartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
xit('should create', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'm-analytics__layout--table',
templateUrl: './layout-table.component.html',
selector: 'm-miniChart',
templateUrl: './mini-chart.component.html',
})
export class AnalyticsLayoutTableComponent implements OnInit {
export class MiniChartComponent implements OnInit {
constructor() {}
ngOnInit() {}
......
const sidebarMenuCategories = [
{
category: {
id: 'analytics',
label: 'Analytics',
path: '/analytics/dashboard/',
permissions: ['admin', 'user'],
},
subcategories: [
// {
// id: 'summary',
// label: 'Summary',
// permissions: ['admin', 'user'],
// },
{
id: 'traffic',
label: 'Traffic',
permissions: ['admin', 'user'],
},
{
id: 'earnings',
label: 'Earnings',
permissions: ['admin', 'user'],
},
// {
// id: 'engagement',
// label: 'Engagement',
// permissions: ['admin', 'user'],
// },
{
id: 'trending',
label: 'Trending',
permissions: ['admin', 'user'],
},
// {
// id: 'referrers',
// label: 'Referrers',
// permissions: ['admin', 'user'],
// },
// {
// id: 'plus',
// label: 'Plus',
// permissions: ['admin'],
// },
// {
// id: 'pro',
// label: 'Pro',
// permissions: ['admin'],
// {
// id: 'boost',
// label: 'Boost',
// permissions: ['admin'],
// },
// {
// id: 'nodes',
// label: 'Nodes',
// permissions: ['admin'],
// },
],
},
// {
// category: {
// id: 'test1',
// label: 'Test1',
// permissions: ['admin', 'user'],
// path: '/somepath/bork',
// },
// subcategories: [
// {
// id: 'nodes',
// label: 'Nodes',
// permissions: ['admin'],
// },
// {
// id: 'nodes2',
// label: 'Nodes2',
// permissions: ['admin'],
// },
// ],
// },
// {
// category: {
// id: 'test2',
// label: 'Test2 no subcats',
// path: '/anotherpath/test2',
// },
// },
];
export default sidebarMenuCategories;
<section class="m-sidebarMenu">
<div class="m-sidebarMenu__topbar">
<i class="material-icons" (click)="mobileMenuExpanded = true">menu</i>
<div class="m-sidebarMenu__topbarCatLabel" *ngIf="activeCat">
{{ activeCat.category.label }}
</div>
</div>
<div
class="m-sidebarMenu__overlay"
[ngClass]="{ mobileMenuExpanded: mobileMenuExpanded }"
(click)="mobileMenuExpanded = false"
></div>
<div
class="m-sidebarMenu__sidebar"
[ngClass]="{ mobileMenuExpanded: mobileMenuExpanded }"
>
<a class="m-sidebarMenu__userWrapper" [routerLink]="['/', user.username]">
<img
class="m-sidebarMenu__userAvatar"
[src]="minds.cdn_url + 'icon/' + user.guid + '/small/' + user.icontime"
/>
<div class="m-sidebarMenu__userDetails">
<div class="m-sidebarMenu__userDetails__name">{{ user.name }}</div>
<div class="m-sidebarMenu__userDetails__username">
@{{ user.username }}
</div>
<!-- TODO: get subscriberCount and remove username -->
<!-- <div class="m-sidebarMenu__userDetails__subscribers">
{{ user.subscribers_count | abbr }} subscribers
</div> -->
</div>
</a>
<ng-container *ngFor="let cat of cats">
<div
class="m-sidebarMenu__catContainer"
*ngIf="cat.category.permissionGranted"
[ngClass]="{ expanded: cat.category.expanded }"
>
<div class="m-sidebarMenu__catLabel">
<h3>{{ cat.category.label }}</h3>
<i
class="material-icons"
*ngIf="cat.category.expanded && cat.subcategories"
(click)="cat.category.expanded = false"
>keyboard_arrow_up</i
>
<i
class="material-icons"
*ngIf="!cat.category.expanded && cat.subcategories"
(click)="cat.category.expanded = true"
>keyboard_arrow_down</i
>
</div>
<div class="m-sidebarMenu__subcatContainer" *ngIf="cat.subcategories">
<div
class="m-sidebarMenu__subcat"
*ngFor="let subcat of cat.subcategories"
>
<a
*ngIf="subcat.permissionGranted"
class="m-sidebarMenu__subcatLabel"
(click)="mobileMenuExpanded = false"
[routerLink]="'../' + subcat.id"
routerLinkActive="selected"
>{{ subcat.label }}</a
>
</div>
</div>
</div>
</ng-container>
</div>
</section>
// .m-sidebarMarkers__container,
// m-v2-topbar {
// display: none;
// }
m-sidebarMenu {
display: block;
// min-width: 180px;
// padding: 16px 16px 16px 80px;
// flex: 1 1 0px;
.m-sidebarMenu {
padding: 16px 16px 16px 80px;
}
i {
display: none;
cursor: pointer;
}
.m-sidebarMenu__topbar,
.m-sidebarMenu__userWrapper {
display: none;
}
.m-sidebarMenu__catContainer {
.m-sidebarMenu__subcatContainer {
display: block;
cursor: pointer;
}
}
// .m-sidebarMenu__sidebar {
// position: relative;
// position: -webkit-sticky;
// position: sticky;
// top: 0;
// }
.page.isMobile m-analytics__menu {
margin-right: -32px;
flex: 0 1 0px;
}
.m-sidebarMenu__catLabel {
display: flex;
justify-content: space-between;
align-items: center;
i {
display: none;
}
}
m-analytics__menu {
display: block;
max-width: 160px;
// ----------------------------------------
// MOBILE
.m-sidebarMenu__subcatContainer {
cursor: pointer;
display: none;
.m-sidebarMenu__subcat {
a {
display: block;
padding: 8px 0;
text-decoration: none;
font-weight: 400;
@include m-theme() {
color: themed($m-grey-300);
}
}
a.selected,
&:hover a {
@include m-theme() {
color: themed($m-blue);
}
}
}
}
}
// --------------------------------------------------
// TABLET & MOBILE
// --------------------------------------------------
@media screen and (max-width: $min-tablet) {
m-sidebarMenu {
margin-right: -32px;
flex: 0 1 0px;
padding: 0;
.m-sidebarMenu {
padding: 0 8px;
}
.isMobile {
.topbar {
.m-sidebarMenu__topbar {
display: block;
z-index: 99999;
position: fixed;
top: 0;
......@@ -24,7 +80,7 @@ m-analytics__menu {
padding: 16px;
text-align: center;
@include m-theme() {
background-color: themed($m-grey-100);
background-color: themed($m-grey-50);
color: themed($m-grey-800);
}
......@@ -35,34 +91,32 @@ m-analytics__menu {
left: 16px;
transform: translateY(-50%);
@include m-theme() {
background-color: themed($m-grey-100);
color: themed($m-grey-700);
color: themed($m-grey-300);
}
}
.pageTitle {
.m-sidebarMenu__topbarCatLabel {
font-size: 20px;
margin: 0;
margin: 0 0 0 -24px;
min-height: 20px;
}
}
.overlay {
.m-sidebarMenu__overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: -1;
// display: none;
background-color: transparent;
transition: background-color 0.5s cubic-bezier(0.075, 0.82, 0.165, 1);
&.expanded {
// display: block;
&.mobileMenuExpanded {
z-index: 999998;
@include m-theme() {
background-color: rgba(themed($m-grey-700), 0.2);
}
}
}
.sidebar {
.m-sidebarMenu__sidebar {
z-index: 999999;
position: fixed;
top: 0;
......@@ -76,50 +130,66 @@ m-analytics__menu {
@include m-theme() {
background-color: themed($m-white);
}
&.expanded {
&.mobileMenuExpanded {
left: 0;
}
.sidebarTitle {
.m-sidebarMenu__catContainer {
.m-sidebarMenu__subcatContainer {
display: none;
}
&.expanded {
.m-sidebarMenu__subcatContainer {
display: block;
.m-sidebarMenu__subcat {
a {
padding: 6px 0;
}
}
}
}
}
.m-sidebarMenu__catLabel {
display: flex;
justify-content: space-between;
align-items: center;
h3 {
font-size: 20px;
margin: 0;
margin: 16px 0;
}
i {
font-size: 20px;
display: inline-block;
font-size: 26px;
@include m-theme() {
color: themed($m-grey-200);
}
}
}
.profile {
.m-sidebarMenu__userWrapper {
display: flex;
text-decoration: none;
margin: 24px 0;
@include m-theme() {
color: themed($m-grey-800);
}
.avatar {
.m-sidebarMenu__userAvatar {
border-radius: 50%;
margin-right: 16px;
}
.details {
.m-sidebarMenu__userDetails {
& > {
padding: 8px 0;
}
.name {
.m-sidebarMenu__userDetails__name {
font-weight: bold;
}
.username {
.m-sidebarMenu__userDetails__username {
@include m-theme() {
color: themed($m-grey-200);
}
}
.subscribers {
.m-sidebarMenu__userDetails__subscribers {
font-size: 11px;
@include m-theme() {
color: themed($m-grey-200);
......@@ -129,32 +199,4 @@ m-analytics__menu {
}
}
}
// ----------------------------------------
padding: 16px 16px 16px 16px;
flex: 1 1 0px;
i {
display: none;
}
.catContainer {
cursor: pointer;
.cat {
a {
display: block;
padding: 6px 0;
text-decoration: none;
font-weight: 400;
@include m-theme() {
color: themed($m-grey-200);
}
}
a.selected,
&:hover a {
@include m-theme() {
color: themed($m-blue);
}
}
}
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Session } from '../../../services/session';
import { sessionMock } from '../../../../tests/session-mock.spec';
import { SidebarMenuComponent } from './sidebar-menu.component';
describe('SidebarMenuComponent', () => {
let component: SidebarMenuComponent;
let fixture: ComponentFixture<SidebarMenuComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SidebarMenuComponent],
imports: [RouterTestingModule],
providers: [{ provide: Session, useValue: sessionMock }],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SidebarMenuComponent);
component = fixture.componentInstance;
// component.user = sessionMock.user;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Session } from '../../../services/session';
import menuCategories from './categories.default';
interface MenuCategory {
category: MenuLink;
subcategories?: MenuLink[];
expanded?: boolean;
}
export { MenuCategory };
interface MenuLink {
id: string;
label: string;
permissions?: string[];
permissionGranted?: boolean;
path?: string;
}
export { MenuLink };
@Component({
selector: 'm-sidebarMenu',
templateUrl: './sidebar-menu.component.html',
})
export class SidebarMenuComponent implements OnInit {
cats: MenuCategory[] = menuCategories;
mobileMenuExpanded = false;
activeCat;
minds: Minds;
user;
userRoles: string[] = ['user'];
constructor(public route: ActivatedRoute, public session: Session) {}
ngOnInit() {
this.minds = window.Minds;
this.user = this.session.getLoggedInUser();
this.getUserRoles();
this.grantPermissionsAndFindActiveCat();
}
getUserRoles() {
if (this.session.isAdmin()) {
this.userRoles.push('admin');
}
// TODO: define & handle other userRole options, e.g. pro, loggedIn
}
grantPermissionsAndFindActiveCat() {
this.cats.forEach(catObj => {
catObj.category['permissionGranted'] = catObj.category.permissions
? this.checkForRoleMatch(catObj.category.permissions)
: true;
if (catObj.subcategories) {
catObj.subcategories.forEach(subCat => {
subCat['permissionGranted'] = subCat.permissions
? this.checkForRoleMatch(subCat.permissions)
: true;
});
}
if (location.pathname.indexOf(catObj.category.path) !== -1) {
catObj.category['expanded'] = true;
this.activeCat = catObj;
} else {
catObj.category['expanded'] = false;
}
});
}
checkForRoleMatch(permissionsArray) {
return permissionsArray.some(role => this.userRoles.includes(role));
}
}
......@@ -51,7 +51,6 @@ import { PageviewsCardComponent } from './components/cards/pageviews/pageviews.c
import { PageviewsChartComponent } from './components/charts/pageviews/pageviews.component';
import { AnalyticsDashboardComponent } from './v2/dashboard.component';
import { AnalyticsLayoutChartComponent } from './v2/layouts/layout-chart/layout-chart.component';
import { AnalyticsLayoutTableComponent } from './v2/layouts/layout-table/layout-table.component';
import { AnalyticsLayoutSummaryComponent } from './v2/layouts/layout-summary/layout-summary.component';
import { AnalyticsMetricsComponent } from './v2/components/metrics/metrics.component';
import { AnalyticsFiltersComponent } from './v2/components/filters/filters.component';
......@@ -63,7 +62,6 @@ import { SearchModule } from '../search/search.module';
import { AnalyticsSearchComponent } from './v2/components/search/search.component';
import { FormsModule } from '@angular/forms';
import { AnalyticsSearchSuggestionsComponent } from './v2/components/search-suggestions/search-suggestions.component';
import { AnalyticsMenuComponent } from './v2/components/menu/menu.component';
PlotlyModule.plotlyjs = PlotlyJS;
......@@ -163,7 +161,6 @@ const routes: Routes = [
Graph,
AnalyticsDashboardComponent,
AnalyticsLayoutChartComponent,
AnalyticsLayoutTableComponent,
AnalyticsLayoutSummaryComponent,
AnalyticsMetricsComponent,
AnalyticsFiltersComponent,
......@@ -172,7 +169,6 @@ const routes: Routes = [
AnalyticsTableComponent,
AnalyticsSearchComponent,
AnalyticsSearchSuggestionsComponent,
AnalyticsMenuComponent,
],
providers: [AnalyticsDashboardService],
})
......
const categories: Array<any> = [
// {
// id: 'summary',
// label: 'Summary',
// permissions: ['admin', 'user'],
// metrics: [],
// },
{
id: 'traffic',
label: 'Traffic',
permissions: ['admin', 'user'],
metrics: [
'active_users',
'signups',
'unique_visitors',
'pageviews',
'impressions',
'retention',
],
},
{
id: 'earnings',
label: 'Earnings',
permissions: ['admin', 'user'],
metrics: ['total', 'pageviews', 'active_referrals', 'customers'],
},
// {
// id: 'engagement',
// label: 'Engagement',
// permissions: ['admin', 'user'],
// metrics: ['posts', 'votes', 'comments', 'reminds', 'subscribers', 'tags'],
// },
{
id: 'trending',
label: 'Trending',
permissions: ['admin', 'user'],
metrics: ['top_content', 'top_channels'],
},
// {
// id: 'referrers',
// label: 'Referrers',
// permissions: ['admin', 'user'],
// metrics: ['top_referrers'],
// },
// {
// id: 'plus',
// label: 'Plus',
// permissions: ['admin'],
// metrics: ['transactions', 'users', 'revenue_usd', 'revenue_tokens'],
// },
// {
// id: 'pro',
// label: 'Pro',
// permissions: ['admin'],
// metrics: ['transactions', 'users', 'revenue_usd', 'revenue_tokens'],
// },
// {
// id: 'boost',
// label: 'Boost',
// permissions: ['admin'],
// metrics: ['transactions', 'users', 'revenue_tokens'],
// },
// {
// id: 'nodes',
// label: 'Nodes',
// permissions: ['admin'],
// metrics: ['transactions', 'users', 'revenue_usd', 'revenue_tokens'],
// },
];
export default categories;
const chartPalette: Array<any> = [
{
id: 'm-white',
themeMap: ['#fff', '#161616'],
},
{
id: 'm-transparent',
themeMap: ['rgba(0,0,0,0)', 'rgba(0,0,0,0)'],
},
{
id: 'm-grey-50',
themeMap: ['rgba(232,232,232,1)', 'rgba(53,53,53,1)'],
},
{
id: 'm-grey-70',
themeMap: ['#eee', '#333'],
},
{
id: 'm-grey-130',
themeMap: ['#ccc', '#555'],
},
{
id: 'm-grey-160',
themeMap: ['#bbb', '#555'],
},
{
id: 'm-grey-300',
themeMap: ['#999', '#666'],
},
{
id: 'm-blue',
themeMap: ['#4690df', '#44aaff'],
},
{
id: 'm-red-dark',
themeMap: ['#c62828', '#e57373'],
},
{
id: 'm-amber-dark',
themeMap: ['#ffa000', '#ffecb3'],
},
{
id: 'm-green-dark',
themeMap: ['#388e3c', '#8bc34a'],
},
{
id: 'm-blue-grey-500',
themeMap: ['#607d8b', '#607d8b'],
},
];
const chartPalette = {
segmentColorIds: [
// Colors for up to 6 segments
'm-blue',
'm-grey-160',
'm-amber-dark',
'm-green-dark',
'm-red-dark',
'm-blue-grey-500',
],
themeMaps: [
{
id: 'm-white',
themeMap: ['#fff', '#232323'],
},
{
id: 'm-grey-50',
themeMap: ['rgba(232,232,232,1)', 'rgba(47,47,47,1)'], // 222
},
{
id: 'm-grey-70',
themeMap: ['#eee', '#404040'], // 333 before 5% lighten
},
{
id: 'm-grey-130',
themeMap: ['#ccc', '#515151'], // 444
},
{
id: 'm-grey-160',
themeMap: ['#bbb', '#626262'], // 555
},
{
id: 'm-grey-300',
themeMap: ['#999', '#737373'], // 666
},
{
id: 'm-blue',
themeMap: ['#4690df', '#5db6ff'], // 44aaff
},
{
id: 'm-red-dark',
themeMap: ['#c62828', '#e98989'], // e57373
},
{
id: 'm-amber-dark',
themeMap: ['#ffa000', '#fff2cc'], // ffecb3
},
{
id: 'm-green-dark',
themeMap: ['#388e3c', '#97c95d'], // 8bc34a
},
{
id: 'm-blue-grey-500',
themeMap: ['#607d8b', '#6b8a99'], // 607d8b
},
],
};
export default chartPalette;
<!-- TODO: Make this into a different component -->
<!-- <m-chart [buckets]="(vm$.thecurrentvisualisation" | async)></m-chart> -->
<!-- TODO: then all this becomes m-plotlyChart -->
<!-- <div *ngIf="vm$ | async as vm"> -->
<div>
<div #graphDiv id="graphDiv"></div>
<!-- <plotly-plot
<div
#chartContainer
class="m-analyticsChart__chartContainer"
[ngClass]="{ isTouchDevice: isTouchDevice }"
>
<plotly-plot
*ngIf="init"
#graphDiv
id="graphDiv"
[divId]="graphDiv"
[data]="data"
[layout]="layout"
[config]="config"
[useResizeHandler]="true"
[style]="{ position: 'relative' }"
[useResizeHandler]="true"
(hover)="onHover($event)"
(unhover)="onUnhover($event)"
(afterPlot)="afterPlot()"
(plotly_click)="onClick($event)"
>
</plotly-plot> -->
<!-- <div class="hoverInfo__row">
{{ hoverInfo.date | date: selectedTimespan.datePipe }}
</div> -->
</plotly-plot>
</div>
<div #hoverInfoDiv id="hoverInfoDiv" class="hoverInfoDiv">
<div class="hoverInfo__row">
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div [ngSwitch]="selectedMetric.unit" class="hoverInfo__row--primary">
<div #hoverInfoDiv id="hoverInfoDiv" class="m-analyticsChart__hoverInfoDiv">
<i *ngIf="isTouchDevice" class="material-icons" (click)="onUnhover($event)"
>close</i
>
<div class="m-analyticsChart__hoverInfo__row">
{{ hoverInfo.date | utcDate | date: datePipe }}
</div>
<div
[ngSwitch]="selectedMetric?.unit"
class="m-analyticsChart__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-analyticsChart__hoverInfo__row" *ngIf="isComparison">
vs
<ng-container
[ngSwitch]="selectedMetric?.unit"
class="m-analyticsChart__hoverInfo__row"
>
<ng-template ngSwitchCase="number">
{{ hoverInfo.value | number }} {{ selectedMetric.label | lowercase }}
{{ hoverInfo.comparisonValue | number }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.value | currency }} USD
{{ hoverInfo.comparisonValue | currency }}
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.value | number: '1.1-3' }} {{ selectedMetric.unit }}
{{ hoverInfo.comparisonValue | number: '1.1-3' }}
</ng-template>
</div>
<div class="hoverInfo__row" *ngIf="isComparison">
vs
<ng-container [ngSwitch]="selectedMetric.unit" class="hoverInfo__row">
<ng-template ngSwitchCase="number">
{{ hoverInfo.comparisonValue | number }}
</ng-template>
<ng-template ngSwitchCase="usd">
{{ hoverInfo.comparisonValue | currency }} USD
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.comparisonValue | number: '1.1-3' }}
{{ selectedMetric.unit }}
</ng-template>
</ng-container>
on {{ hoverInfo.comparisonDate | utcDate | date: datePipe }}
</div>
</ng-container>
on {{ hoverInfo.comparisonDate | utcDate | date: datePipe }}
</div>
</div>
m-analytics__chart {
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 > * {
......@@ -24,7 +28,7 @@ m-analytics__chart {
}
}
.hoverInfoDiv {
.m-analyticsChart__hoverInfoDiv {
width: 160px;
padding: 12px;
position: absolute;
......@@ -37,20 +41,49 @@ m-analytics__chart {
@include m-theme() {
background-color: themed($m-white);
box-shadow: 0 0 4px rgba(themed($m-black), 0.3);
color: themed($m-grey-200);
color: themed($m-grey-300);
}
[class*='hoverInfo__row'] {
[class*='m-analyticsChart__hoverInfo__row'] {
padding-bottom: 4px;
font-weight: 300;
&:last-of-type {
padding-top: 2px;
}
}
.hoverInfo__row--primary {
.m-analyticsChart__hoverInfo__row--primary {
font-weight: 400;
font-size: 15px;
// font-weight: bold;
@include m-theme() {
color: themed($m-grey-600);
}
}
i {
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);
}
&:hover {
@include m-theme() {
color: themed($m-grey-500);
}
}
}
}
.isTouchDevice .m-analyticsChart__hoverInfoDiv i {
display: block;
}
@media screen and (max-width: $min-tablet) {
m-analytics__chart {
margin-left: 16px;
}
}
<div class="filterLabelWrapper" *ngIf="filter.id !== 'timespan'">
<div class="m-analyticsFilter__labelWrapper" *ngIf="showLabel">
<span>{{ filter.label }}</span>
<m-tooltip icon="help">
<div class="filterDesc">{{ filter?.description }}</div>
<ul class="filterOptions__descContainer">
<div>{{ filter?.description }}</div>
<ul>
<ng-container *ngFor="let option of filter.options">
<li class="filterOption__desc">
<span class="filterOption__descLabel">{{ option.label }}</span
><span class="filterOption__desc" *ngIf="option.description"
>: {{ option.description }}</span
>
<li>
<span>{{ option.label }}</span
><span *ngIf="option.description">: {{ option.description }}</span>
</li>
</ng-container>
</ul>
</m-tooltip>
</div>
<div
class="filterWrapper"
class="m-analyticsFilter__wrapper"
[ngClass]="{
expanded: expanded,
isMobile: isMobile,
dropUp: dropUp
}"
(focus)="expanded = true"
(blur)="expanded = false"
tabindex="0"
>
<div class="filterHeader" (click)="expanded = !expanded">
<div class="row">
<span class="option option--selected">
{{ selectedOption.label }}
</span>
<i class="material-icons" *ngIf="!expanded">keyboard_arrow_down</i>
<i class="material-icons" *ngIf="expanded">keyboard_arrow_up</i>
</div>
<div
class="m-analyticsFilter__header m-analyticsFilter__row"
(click)="expanded = !expanded"
>
<span class="m-analyticsFilter__option m-analyticsFilter__option--selected">
{{ selectedOption.label }}
</span>
<i class="material-icons" *ngIf="!expanded">keyboard_arrow_down</i>
<i class="material-icons" *ngIf="expanded">keyboard_arrow_up</i>
</div>
<div class="unselectedOptionsContainer">
<div class="m-analyticsFilter__optionsContainer">
<ng-container *ngFor="let option of filter.options">
<div
class="option row"
class="m-analyticsFilter__option m-analyticsFilter__row"
(click)="updateFilter(option)"
[ngClass]="{
unavailable: option.available === false
}"
>
<span>{{ option.label }}</span>
{{ option.label }}
<!-- <span>{{ option.label }}</span> -->
</div>
</ng-container>
</div>
......
......@@ -3,18 +3,19 @@ $rounded-bottom: 0 0 3px 3px;
m-analytics__filter {
position: relative;
margin: 0 36px 0 0;
margin: 0 24px 36px 0;
z-index: 2;
display: block;
}
.filterLabelWrapper {
.m-analyticsFilter__labelWrapper {
position: absolute;
bottom: 110%;
bottom: 115%;
white-space: nowrap;
@include m-theme() {
color: rgba(themed($m-grey-200), 0.9);
color: themed($m-grey-300);
}
m-tooltip {
margin-right: 4px;
margin-left: 4px;
}
> * {
display: inline-block;
......@@ -23,75 +24,84 @@ m-analytics__filter {
i {
font-size: 12px;
@include m-theme() {
color: rgba(themed($m-grey-200), 0.7);
color: rgba(themed($m-grey-300), 0.7);
}
}
.m-tooltip--bubble {
letter-spacing: 1.2px;
line-height: 16px;
z-index: 9999;
}
.m-tooltip--bubble {
letter-spacing: 1.2px;
line-height: 16px;
z-index: 9999;
font-size: 11px;
bottom: 110%;
left: 0;
width: 160px;
@include m-theme() {
color: themed($m-white);
background-color: themed($m-blue);
}
> * {
font-size: 11px;
bottom: 85%;
left: 100%;
@include m-theme() {
color: themed($m-white);
background-color: themed($m-blue);
}
> * {
font-size: 11px;
font-weight: 300;
line-height: inherit;
letter-spacing: inherit;
}
ul {
padding-inline-start: 16px;
margin-block-end: 4px;
li {
padding-bottom: 8px;
.filterOption__descLabel {
// font-weight: bold;
}
}
font-weight: 300;
line-height: inherit;
letter-spacing: inherit;
}
ul {
padding-inline-start: 16px;
margin-block-end: 4px;
li {
padding-bottom: 8px;
}
}
}
}
.filterWrapper {
.m-analyticsFilter__wrapper {
cursor: pointer;
&:focus {
outline: 0;
}
> * {
width: 180px;
box-sizing: border-box;
}
.m-analyticsFilter__optionsContainer {
padding: 8px 0;
.m-analyticsFilter__option {
transform: translateY(25%);
}
}
&.expanded {
@include m-theme() {
box-shadow: 0px 1px 15px 0 rgba(themed($m-black), 0.15);
}
.filterHeader {
.m-analyticsFilter__header {
@include m-theme() {
border-color: themed($m-blue);
}
}
.unselectedOptionsContainer {
visibility: visible;
// @include m-theme() {
// box-shadow: 0px 1px 15px 0 rgba(themed($m-black), 0.15);
// }
.m-analyticsFilter__optionsContainer {
display: block;
}
&:not(.dropUp) {
.filterHeader {
.m-analyticsFilter__header {
@include m-theme() {
border-radius: $rounded-top;
}
}
.unselectedOptionsContainer {
.m-analyticsFilter__optionsContainer {
border-top: none;
border-radius: $rounded-bottom;
}
}
&.dropUp {
.filterHeader {
.m-analyticsFilter__header {
border-radius: $rounded-bottom;
}
.unselectedOptionsContainer {
.m-analyticsFilter__optionsContainer {
bottom: 100%;
border-radius: $rounded-top;
border-bottom: none;
......@@ -99,40 +109,36 @@ m-analytics__filter {
}
}
.filterHeader {
.m-analyticsFilter__header {
position: relative;
width: 100%;
padding: 8px 6px 6px 10px;
border-radius: 3px;
transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
@include m-theme() {
background-color: themed($m-white);
color: rgba(themed($m-grey-200), 0.9);
color: themed($m-grey-300);
}
@include m-theme() {
border: 1px solid themed($m-grey-100);
}
.filterLabel {
.m-analyticsFilter__label {
margin-right: 10px;
}
i {
flex-grow: 0;
width: 24px;
height: 24px;
}
.option--selected {
margin-right: 8px;
.m-analyticsFilter__option--selected {
@include m-theme() {
color: themed($m-grey-500);
}
}
}
.unselectedOptionsContainer {
.m-analyticsFilter__optionsContainer {
position: absolute;
// display: none;
visibility: hidden;
width: 100%;
padding: 8px 6px 6px 10px;
display: none;
border-radius: 3px;
left: 0px;
transition: box-shadow 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
......@@ -141,21 +147,29 @@ m-analytics__filter {
border-top: 1px solid themed($m-blue);
background-color: themed($m-white);
}
.option {
padding: 5px 0;
}
}
.row {
.m-analyticsFilter__row {
display: flex;
justify-content: space-between;
align-items: center;
height: 46px;
padding: 0 20px;
&.m-analyticsFilter__header {
padding-right: 10px;
}
}
.option {
.m-analyticsFilter__option {
display: inline-block;
box-sizing: border-box;
width: inherit;
border-radius: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@include m-theme() {
background-color: themed($m-white);
color: rgba(themed($m-grey-200), 0.9);
color: themed($m-grey-300);
}
&.unavailable {
......@@ -166,19 +180,45 @@ m-analytics__filter {
}
&:hover:not(.unavailable) {
@include m-theme() {
color: themed($m-grey-500);
color: themed($m-grey-600);
}
}
}
}
.filterWrapper.isMobile {
.filterHeader {
i {
display: none;
@media screen and (max-width: $min-tablet) {
m-analytics__filter {
.m-analyticsFilter__labelWrapper {
.m-tooltip--bubble {
width: 120px;
}
}
.m-analyticsFilter__wrapper {
> * {
width: 140px;
}
}
}
.option--selected {
margin-right: 0;
}
@media screen and (max-width: $max-mobile) {
m-analytics__filter {
.m-analyticsFilter__wrapper {
.m-analyticsFilter__header {
i {
display: none;
}
}
.m-analyticsFilter__row {
padding: 0 18px;
height: 40px;
&.m-analyticsFilter__header {
padding-right: 10px;
}
}
.m-analyticsFilter__option--selected {
margin-right: 0;
}
}
}
}
......@@ -9,19 +9,9 @@ import {
import { Observable } from 'rxjs';
import {
AnalyticsDashboardService,
Category,
Response,
Dashboard,
Filter,
Option,
Metric,
Summary,
Visualisation,
Bucket,
Timespan,
UserState,
} from '../../dashboard.service';
import isMobileOrTablet from '../../../../../helpers/is-mobile-or-tablet';
import { Session } from '../../../../../services/session';
@Component({
......@@ -32,8 +22,8 @@ import { Session } from '../../../../../services/session';
export class AnalyticsFilterComponent implements OnInit {
@Input() filter: Filter;
@Input() dropUp: boolean = false;
@Input() showLabel: boolean = true;
isMobile: boolean;
expanded = false;
options: Array<any> = [];
selectedOption: Option;
......@@ -43,39 +33,37 @@ export class AnalyticsFilterComponent implements OnInit {
) {}
ngOnInit() {
// this.subscription = this.analyticsService.timespan$.subscribe(timespan => {
// if (this.filter.id === 'timespan') {
// this.selectedOption =
// this.filter.options.find(option => option.id === timespan) ||
// this.filter.options[0];
// // TODO: make selected option at top of array?
// } else {
// this.selectedOption =
// this.filter.options.find(option => option.selected === true) ||
// this.filter.options[0];
// }
// });
this.selectedOption =
this.filter.options.find(option => option.selected === true) ||
this.filter.options[0];
this.isMobile = isMobileOrTablet();
}
updateFilter(option: Option) {
this.expanded = false;
if ('available' in option && !option.available) {
return;
}
this.selectedOption = option;
if (this.filter.id === 'timespan') {
this.analyticsService.updateTimespan(option.id);
console.log('upDateFilter ', option.id);
return;
}
if (!this.selectedOption.available) {
return;
}
const selectedFilterStr = `${this.filter.id}::${option.id}`;
this.analyticsService.updateFilter(selectedFilterStr);
}
// clickHeader() {
// if (this.expanded) {
// console.log('its expanded');
// setTimeout(() => {
// this.expanded = false;
// });
// } else {
// console.log('itsnot expanded');
// }
// document.getElementById("myAnchor").blur();
// }
}
<div class="filtersContainer">
<div class="m-analytics__filtersContainer">
<!-- <ng-container *ngFor="let filter of filters$ | async"> -->
<ng-container *ngFor="let filter of filters">
<m-analytics__filter
......
.filtersContainer {
m-analytics__filters {
display: block;
}
.m-analytics__filtersContainer {
display: flex;
flex-wrap: wrap;
padding: 16px;
padding: 16px 16px 16px 0;
position: relative;
margin-top: 36px;
margin: 36px 0 0 40px;
}
@media screen and (max-width: $min-tablet) {
.m-analytics__filtersContainer {
margin-left: 16px;
}
}
......@@ -29,6 +29,7 @@ export class AnalyticsFiltersComponent implements OnInit, OnDestroy {
// Temporarily remove channel search from channel filter options
this.analyticsService.filters$.subscribe(filters => {
this.filters = filters;
const channelFilter = filters.find(filter => filter.id === 'channel');
channelFilter.options = channelFilter.options.filter(option => {
......
<section class="menu" [ngClass]="{ isMobile: isMobile }">
<div class="topbar" *ngIf="isMobile">
<i class="material-icons" (click)="expanded = true">menu</i>
<div class="pageTitle">
Analytics
</div>
</div>
<div
class="overlay"
[ngClass]="{ expanded: expanded }"
(click)="expanded = false"
></div>
<div class="sidebar" [ngClass]="{ expanded: expanded }">
<a class="profile" *ngIf="isMobile" [routerLink]="['/', user.username]">
<img
class="avatar"
[src]="minds.cdn_url + 'icon/' + user.guid + '/small/' + user.icontime"
/>
<div class="details">
<div class="name">{{ user.name }}</div>
<!-- TODO: remove username once subscriberCount is working -->
<div class="username">@{{ user.username }}</div>
<!-- TODO: get subscriberCount -->
<!-- <div class="subscribers">
{{ user.subscribers_count | abbr }} subscribers
</div> -->
</div>
</a>
<div class="sidebarTitle">
<h3>Analytics</h3>
<i class="material-icons" *ngIf="isMobile" (click)="expanded = false"
>keyboard_arrow_up</i
>
</div>
<div class="catContainer">
<!-- TODO: apply permissions from categories.default to cats sidebar -->
<div class="cat" *ngFor="let cat of cats">
<a
(click)="expanded = false"
[routerLink]="'../' + cat.id"
routerLinkActive="selected"
>{{ cat?.label }}</a
>
</div>
</div>
</div>
</section>
import {
Component,
OnInit,
OnDestroy,
Input,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import {
ActivatedRoute,
Router,
ParamMap,
RoutesRecognized,
} from '@angular/router';
import { Subscription, Observable } from 'rxjs';
import { Client } from '../../../../../services/api';
import { Session } from '../../../../../services/session';
import {
AnalyticsDashboardService,
Category,
UserState,
} from '../../dashboard.service';
import categories from '../../categories.default';
import isMobileOrTablet from '../../../../../helpers/is-mobile-or-tablet';
@Component({
selector: 'm-analytics__menu',
templateUrl: './menu.component.html',
})
export class AnalyticsMenuComponent implements OnInit {
isMobile: boolean;
expanded: boolean = false;
minds;
user;
cats = categories;
// subscription: Subscription;
// paramsSubscription: Subscription;
// category$ = this.analyticsService.category$;
selectedCat: string;
constructor(
// public client: Client,
public route: ActivatedRoute,
// private router: Router,
// public analyticsService: AnalyticsDashboardService,
// private cd: ChangeDetectorRef
public session: Session
) {}
ngOnInit() {
this.minds = window.Minds;
this.isMobile = isMobileOrTablet();
this.user = this.session.getLoggedInUser();
}
// updateCategory(categoryId) {
// this.analyticsService.updateCategory(categoryId);
// }
// detectChanges() {
// this.cd.markForCheck();
// this.cd.detectChanges();
// }
}
<section class="metricsSection" [ngClass]="{ isMobile: isMobile }">
<!-- <div class="overflowFade--left"></div>
<div class="overflowScrollButton--left">
<section class="m-analytics__metricsSection">
<div class="m-analytics__metricsWrapper" #metricsWrapper>
<div
*ngIf="isOverflown && !isAtScrollStart"
class="m-analytics__metricsOverflowFade--left"
></div>
<div
[ngClass]="{ showButton: showButton.left }"
class="m-analytics__metricsOverflowScrollButton--left"
(click)="slide('left')"
>
<i class="material-icons">chevron_left</i>
</div>
<div
#metricsContainer
*ngIf="metrics$ | async as metrics"
class="m-analytics__metricsContainer disable-scrollbars"
(scroll)="onScroll($event)"
>
<ng-container *ngFor="let metric of metrics">
<div
class="m-analytics__metric"
(click)="updateMetric(metric)"
[ngClass]="{ active: metric.visualisation }"
*ngIf="metric.permissionGranted"
>
<div class="m-analytics__metricLabel">
<span>{{ metric.label }}</span>
<m-tooltip [anchor]="top" icon="help">
{{ metric.description }}
</m-tooltip>
</div>
<div class="m-analytics__metricSummary" *ngIf="metric.summary">
<ng-container *ngIf="metric.unit === 'number'">
{{ metric.summary.current_value | number }}
</ng-container>
<ng-container *ngIf="metric.unit === 'usd'">
<span>$</span
>{{ metric.summary.current_value / 100 | number: '1.2-2' }}
</ng-container>
</div>
<div
*ngIf="metric.summary"
class="m-analytics__metricDelta"
[ngClass]="{
goodChange: metric.hasChanged && metric.positiveTrend,
badChange: metric.hasChanged && !metric.positiveTrend
}"
>
<i class="material-icons" *ngIf="metric.delta > 0">arrow_upward</i>
<i class="material-icons" *ngIf="metric.delta < 0"
>arrow_downward</i
>
<span>{{ metric.delta | percent: '1.0-1' }}</span>
</div>
</div>
</ng-container>
</div>
<div
*ngIf="isOverflown && !isAtScrollEnd"
class="m-analytics__metricsOverflowFade--right"
></div>
<div
[ngClass]="{ showButton: showButton.right }"
class="m-analytics__metricsOverflowScrollButton--right"
(click)="slide('right')"
>
<i class="material-icons">chevron_right</i>
</div>
</div>
</section>
<!--
<section class="m-analytics__metricsSection">
<div
*ngIf="isOverflown && !isAtScrollStart"
class="m-analytics__metricsOverflowFade--left"
></div>
<div
[ngClass]="{ showButton: showButton.left }"
class="m-analytics__metricsOverflowScrollButton--left"
(click)="slide('left')"
>
<i class="material-icons">chevron_left</i>
</div> -->
<div class="metricsWrapper">
<div class="metricsContainer" *ngIf="metrics$ | async as metrics">
</div>
<div class="m-analytics__metricsWrapper" #metricsWrapper>
<div
#metricsContainer
*ngIf="metrics$ | async as metrics"
class="m-analytics__metricsContainer disable-scrollbars"
(scroll)="onScroll($event)"
>
<ng-container *ngFor="let metric of metrics">
<div
class="metric"
class="m-analytics__metric"
(click)="updateMetric(metric)"
[ngClass]="{ active: metric.visualisation }"
*ngIf="metric.permissionGranted"
>
<div class="metricLabel">
<div class="m-analytics__metricLabel">
<span>{{ metric.label }}</span>
<m-tooltip [anchor]="top" icon="help">
{{ metric.description }}
</m-tooltip>
</div>
<div class="metricSummary" *ngIf="metric.summary">
<div class="m-analytics__metricSummary" *ngIf="metric.summary">
<ng-container *ngIf="metric.unit === 'number'">
{{ metric.summary.current_value | number }}
</ng-container>
......@@ -30,7 +116,7 @@
<div
*ngIf="metric.summary"
class="metricDelta"
class="m-analytics__metricDelta"
[ngClass]="{
goodChange: metric.hasChanged && metric.positiveTrend,
badChange: metric.hasChanged && !metric.positiveTrend
......@@ -46,8 +132,15 @@
</ng-container>
</div>
</div>
<!-- <div class="overflowFade--right"></div>
<div class="overflowScrollButton--right">
<div
*ngIf="isOverflown && !isAtScrollEnd"
class="m-analytics__metricsOverflowFade--right"
></div>
<div
[ngClass]="{ showButton: showButton.right }"
class="m-analytics__metricsOverflowScrollButton--right"
(click)="slide('right')"
>
<i class="material-icons">chevron_right</i>
</div> -->
</section>
</div>
</section> -->
m-analytics__metrics {
.metricsSection {
display: block;
.m-analytics__metricsSection {
position: relative;
[class*='overflowFade--'] {
[class*='m-analytics__metricsOverflowFade--'] {
position: absolute;
top: 0;
bottom: 0;
width: 24px;
z-index: 1;
&.overflowFade--right {
z-index: 2;
&.m-analytics__metricsOverflowFade--right {
@include m-theme() {
right: 0;
background: linear-gradient(
......@@ -18,7 +19,7 @@ m-analytics__metrics {
);
}
}
&.overflowFade--left {
&.m-analytics__metricsOverflowFade--left {
@include m-theme() {
left: 0;
background: linear-gradient(
......@@ -30,14 +31,18 @@ m-analytics__metrics {
}
}
[class*='overflowScrollButton--'] {
[class*='m-analytics__metricsOverflowScrollButton--'] {
position: absolute;
top: 50%;
border-radius: 50%;
box-sizing: border-box;
z-index: 2;
transform: translateY(-50%);
transition: all 0.2s ease-in;
opacity: 0;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
&.showButton {
opacity: 1;
}
cursor: pointer;
@include m-theme() {
background-color: themed($m-white);
......@@ -49,10 +54,10 @@ m-analytics__metrics {
border: 1px solid themed($m-blue);
}
}
&.overflowScrollButton--right {
&.m-analytics__metricsOverflowScrollButton--right {
right: -12;
}
&.overflowScrollButton--left {
&.m-analytics__metricsOverflowScrollButton--left {
left: -12;
}
i {
......@@ -63,40 +68,43 @@ m-analytics__metrics {
}
}
}
.metricsWrapper {
.m-analytics__metricsWrapper {
position: relative;
// overflow: hidden;
width: 100%;
z-index: 1;
@include m-theme() {
box-shadow: 0 7px 15px -7px rgba(themed($m-black-always), 0.1);
}
}
.metricsContainer {
scroll-snap-type: x mandatory;
position: relative;
.m-analytics__metricsContainer {
overflow-x: hidden;
overflow-y: hidden;
display: flex;
flex-wrap: nowrap;
// overflow-x: auto;
// padding: 0 16px;
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
// &.metricsContainer::-webkit-scrollbar {
// display: none;
// }
&.disable-scrollbars {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
&::-webkit-scrollbar {
width: 0px;
background: transparent; /* Chrome/Safari/Webkit */
}
}
.metric {
.m-analytics__metric {
cursor: pointer;
scroll-snap-align: start;
flex: 0 0 auto;
width: 20%;
width: 160px;
padding: 24px 20px 20px 20px;
font-size: 14px;
box-sizing: border-box;
overflow: visible;
@include m-theme() {
border-bottom: 8px solid themed($m-white);
}
@include m-theme() {
color: themed($m-grey-200);
color: themed($m-grey-300);
}
&.active {
@include m-theme() {
......@@ -105,34 +113,34 @@ m-analytics__metrics {
}
}
&:first-child {
margin-left: 16px;
margin-left: 40px;
}
&:last-child {
margin-right: 16px;
margin-right: 40px;
}
&:hover:not(.active) {
transition: background-color 0.3s cubic-bezier(0.23, 1, 0.32, 1);
@include m-theme() {
background-color: rgba(themed($m-grey-100), 0.2);
border-bottom: 8px solid rgba(0, 0, 0, 0);
transition: background-color 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}
}
.metricLabel {
.m-analytics__metricLabel {
white-space: nowrap;
}
m-tooltip {
vertical-align: middle;
margin-left: 4px;
}
.metricSummary {
.m-analytics__metricSummary {
font-size: 17px;
margin-top: 8px;
@include m-theme() {
color: themed($m-grey-800);
}
}
.metricDelta {
.m-analytics__metricDelta {
display: flex;
align-items: baseline;
padding-top: 4px;
......@@ -156,12 +164,13 @@ m-analytics__metrics {
i {
font-size: 12px;
@include m-theme() {
color: rgba(themed($m-grey-200), 0.7);
color: rgba(themed($m-grey-300), 0.7);
}
}
.m-tooltip--bubble {
z-index: 9999;
font-size: 11px;
width: 160px;
@include m-theme() {
color: themed($m-white);
background-color: themed($m-blue);
......@@ -169,10 +178,25 @@ m-analytics__metrics {
}
}
}
.metricsSection.isMobile {
.metricsContainer {
.metric {
flex: 1 0 auto;
@media screen and (max-width: $min-tablet) {
.m-analytics__metricsSection {
[class*='m-analytics__metricsOverflowScrollButton--'] {
display: none;
}
.m-analytics__metricsContainer {
overflow-x: scroll;
position: relative;
scroll-snap-type: x mandatory;
.m-analytics__metric {
scroll-snap-align: start;
&:first-child {
margin-left: 16px;
}
&:last-child {
margin-right: 16px;
}
}
}
}
}
import {
Component,
OnInit,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
OnDestroy,
ViewChild,
ElementRef,
HostListener,
} from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import {
AnalyticsDashboardService,
Category,
Response,
Dashboard,
Filter,
Option,
Metric as MetricBase,
Summary,
Visualisation,
Bucket,
Timespan,
UserState,
} from '../../dashboard.service';
import { Session } from '../../../../../services/session';
import isMobileOrTablet from '../../../../../helpers/is-mobile-or-tablet';
interface MetricExtended extends MetricBase {
delta: number;
......@@ -38,15 +30,24 @@ export { MetricExtended as Metric };
templateUrl: './metrics.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AnalyticsMetricsComponent implements OnInit, OnDestroy {
export class AnalyticsMetricsComponent implements OnInit, AfterViewInit {
@ViewChild('metricsContainer', { static: false })
metricsContainerEl: ElementRef;
metricsContainer;
data;
subscription: Subscription;
isMobile: boolean;
user;
userRoles: string[] = ['user'];
init = false;
metrics$;
isOverflown = { left: false, right: false };
metricClientWidth: number;
faderWidth = 24;
isOverflown: boolean = false;
isAtScrollEnd = false;
isAtScrollStart = true;
showButton = { left: false, right: false };
constructor(
private analyticsService: AnalyticsDashboardService,
......@@ -66,7 +67,6 @@ export class AnalyticsMetricsComponent implements OnInit, OnDestroy {
this.metrics$ = this.analyticsService.metrics$.pipe(
map(_metrics => {
const metrics = _metrics.map(metric => ({ ...metric })); // Clone to avoid updating
for (const metric of metrics) {
metric['permissionGranted'] = metric.permissions.some(role =>
this.userRoles.includes(role)
......@@ -95,27 +95,89 @@ export class AnalyticsMetricsComponent implements OnInit, OnDestroy {
}
}
}
return metrics;
})
);
this.isMobile = isMobileOrTablet();
// TODO: if selected metric is not fully visible, slide() until it is
}
ngAfterViewInit() {
this.checkOverflow();
}
updateMetric(metric) {
// TODO: if clicked metric is not fully visible, slide() until it is
this.analyticsService.updateMetric(metric.id);
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
@HostListener('window:resize')
onResize() {
this.checkOverflow();
}
ngOnDestroy() {
// this.subscription.unsubscribe();
onScroll($event) {
this.checkOverflow();
}
checkOverflow() {
// element.scrollWidth - element.clientWidth
const firstMetric = document.querySelector('.m-analytics__metric');
this.metricClientWidth = firstMetric.clientWidth;
this.metricsContainer = this.metricsContainerEl.nativeElement;
this.isOverflown =
this.metricsContainer.scrollWidth - this.metricsContainer.clientWidth > 0;
this.isAtScrollStart = this.metricsContainer.scrollLeft < this.faderWidth;
this.showButton.left = this.isOverflown && !this.isAtScrollStart;
this.isAtScrollEnd =
!this.isOverflown ||
this.metricsContainer.scrollWidth -
(this.metricsContainer.scrollLeft + this.metricsContainer.clientWidth) <
this.faderWidth;
this.showButton.right =
this.isOverflown &&
this.metricsContainer.scrollLeft >= 0 &&
!this.isAtScrollEnd;
this.detectChanges();
}
slide(direction) {
let currentScrollLeft = this.metricsContainer.scrollLeft;
let targetScrollLeft;
let scrollEndOffset = 0;
const partiallyVisibleMetricWidth =
this.metricsContainer.clientWidth % this.metricClientWidth;
const completelyVisibleMetricsWidth =
this.metricsContainer.clientWidth - partiallyVisibleMetricWidth;
if (direction === 'right') {
if (currentScrollLeft < this.faderWidth) {
currentScrollLeft = this.faderWidth;
}
targetScrollLeft = Math.min(
currentScrollLeft + completelyVisibleMetricsWidth,
this.metricsContainer.scrollWidth - completelyVisibleMetricsWidth
);
} else {
if (this.isAtScrollEnd) {
scrollEndOffset = partiallyVisibleMetricWidth - this.faderWidth;
}
targetScrollLeft = Math.max(
currentScrollLeft - completelyVisibleMetricsWidth + scrollEndOffset,
0
);
}
this.metricsContainer.scrollTo({
top: 0,
left: targetScrollLeft,
behavior: 'smooth',
});
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
}
<div class="tableWrapper" [hidden]="loading" [ngClass]="{ isMobile: isMobile }">
<div class="row--header">
<div *ngFor="let col of columns | slice: 0:1" class="col--entity cell">
<div class="m-analyticsTable__wrapper" [hidden]="loading">
<div class="m-analyticsTable__row--header">
<div
*ngFor="let col of columns | slice: 0:1"
class="m-analyticsTable__col--entity m-analyticsTable__cell"
>
{{ col.label }}
</div>
<div class="col--values">
<div class="m-analyticsTable__col--values">
<ng-container *ngFor="let col of columns | slice: 1">
<div class="subCol--value cell">{{ col.label }}</div>
<div class="m-analyticsTable__subCol--value m-analyticsTable__cell">
{{ col.label }}
</div>
</ng-container>
</div>
</div>
<ng-container *ngFor="let row of reformattedBuckets">
<a class="row--data" [routerLink]="row.entity.route">
<div class="col--entity cell" *ngIf="row.entity">
<div class="entityTitle">
<span>{{ row.entity.title }}</span>
<i class="material-icons">open_in_new</i>
</div>
<div class="entityDetails">
<span class="usernameWrapper">
<a [routerLink]="'/' + row.entity.name"
>@{{ row.entity.username }}</a
<a class="m-analyticsTable__row--data" [routerLink]="row.entity.route">
<div
class="m-analyticsTable__col--entity m-analyticsTable__cell"
*ngIf="row.entity"
>
<div class="m-analyticsTable__entityWrapper">
<div class="m-analyticsTable__entityTitle">
<span>{{ row.entity.title }}</span>
<i class="material-icons">open_in_new</i>
</div>
<div class="m-analyticsTable__entityDetails">
<span class="m-analyticsTable__usernameWrapper">
<a [routerLink]="'/' + row.entity.name"
>@{{ row.entity.username }}</a
>
</span>
<span class="m-analyticsTable__entityType">{{
row.entity.type | titlecase
}}</span>
<span class="m-analyticsTable__entityPublishDate"
>Published {{ row.entity.time_created * 1000 | date }}</span
>
</span>
<span *ngIf="!isMobile">{{ row.entity.type | titlecase }}</span>
<span *ngIf="!isMobile"
>Published {{ row.entity.time_created * 1000 | date }}</span
>
</div>
</div>
</div>
<div class="col--values">
<div class="m-analyticsTable__col--values">
<ng-container *ngFor="let value of row.values">
<div class="subCol--value cell">{{ value | abbr }}</div>
<div class="m-analyticsTable__subCol--value m-analyticsTable__cell">
{{ value | abbr }}
</div>
</ng-container>
</div>
</a>
......
.tableWrapper {
// * {
// border: 1px solid salmon;
// }
.m-analyticsTable__wrapper {
font-size: 13px;
font-weight: 400;
font-weight: 300;
overflow-x: scroll;
@include m-theme() {
color: themed($m-grey-800);
}
a {
text-decoration: none;
font-weight: normal;
font-weight: 300;
@include m-theme() {
color: themed($m-grey-800);
}
}
[class*='row'] {
display: flex;
}
.m-analyticsTable__cell {
display: flex;
overflow: hidden;
white-space: nowrap;
height: 76px;
}
.m-analyticsTable__subCol--value {
white-space: normal;
align-items: center;
display: flex;
justify-content: center;
min-width: 50px;
}
[class*='m-analyticsTable__row'] {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
transition: background-color 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}
// ***********************************************
.m-analyticsTable__row--header {
@include m-theme() {
border-bottom: 1px solid themed($m-grey-50);
color: themed($m-grey-200);
}
.m-analyticsTable__col--entity {
flex-direction: row;
align-items: center;
justify-content: space-between;
transition: background-color 0.3s cubic-bezier(0.23, 1, 0.32, 1);
&.row--header {
@include m-theme() {
border-bottom: 1px solid themed($m-grey-50);
color: themed($m-grey-200);
}
.col--entity {
flex-direction: row;
align-items: flex-end;
}
.col--values {
.subCol--value.cell {
align-items: flex-end;
}
}
}
.m-analyticsTable__col--values {
.m-analyticsTable__subCol--value {
white-space: normal;
text-align: center;
}
&.row--data {
&:hover {
@include m-theme() {
background-color: rgba(themed($m-blue-grey-50), 0.5);
}
.col--entity .entityTitle i {
display: inline-block;
}
}
.col--entity {
@include m-theme() {
border-right: 1px solid themed($m-grey-50);
}
}
}
}
// .col--entity,
// .col--entity *,
// .col--values,
// .col--values *,
[class*='col'],
[class*='col'] * {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// .subCol--value {
// align-items: center;
// }
// ***********************************************
.m-analyticsTable__row--data {
&:hover {
@include m-theme() {
background-color: rgba(themed($m-blue-grey-50), 0.5);
}
.cell {
.m-analyticsTable__col--entity .m-analyticsTable__entityTitle i {
visibility: visible;
}
}
.m-analyticsTable__col--entity {
flex-direction: row;
align-items: center;
@include m-theme() {
border-right: 1px solid themed($m-grey-50);
}
.m-analyticsTable__entityWrapper {
display: flex;
overflow: hidden;
flex-direction: column;
width: 87%;
}
}
[class*='m-analyticsTable__col'],
[class*='m-analyticsTable__col'] * {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
// ***********************************************
.m-analyticsTable__col--values {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
.m-analyticsTable__subCol--value {
flex: 1 1 0;
align-items: center;
&:last-of-type {
padding-right: 50px;
}
}
}
// ***********************************************
.m-analyticsTable__col--entity {
flex-direction: column;
padding-left: 50px;
min-width: 170px;
.m-analyticsTable__usernameWrapper a {
font-weight: 300;
text-decoration: none;
}
.m-analyticsTable__entityTitle {
display: flex;
> * {
display: inline-block;
}
span {
max-width: 90%;
}
i {
font-size: 12px;
margin: 3px 0 0 3px;
visibility: hidden;
@include m-theme() {
color: themed($m-grey-800);
}
}
}
.m-analyticsTable__entityDetails {
display: inline;
@include m-theme() {
color: themed($m-grey-200);
}
> * {
margin-right: $minds-margin;
}
.m-analyticsTable__usernameWrapper {
white-space: nowrap;
height: 50px;
padding: 8px 8px 8px 0;
overflow: hidden;
text-overflow: ellipsis;
}
a {
@include m-theme() {
color: themed($m-grey-300);
}
}
}
}
.col--entity {
flex-direction: column;
padding-left: 24px;
// flex-basis: 20vw;
// ***********************************************
.usernameWrapper a {
font-weight: 400;
text-decoration: none;
.m-analyticsTable__col--entity {
flex: 2 3 0;
}
.m-analyticsTable__col--values {
flex: 2 2 0;
}
// ***********************************************
@media screen and (max-width: $min-tablet) {
.m-analyticsTable__wrapper {
.m-analyticsTable__col--entity {
flex: 3 1 0;
.m-analyticsTable__entityType,
.m-analyticsTable__entityPublishDate {
display: none;
}
.entityTitle {
display: flex;
> * {
display: inline-block;
max-width: 200px;
}
}
.m-analyticsTable__col--values {
flex: 2 2 0;
i {
font-size: 12px;
margin: 3px 0 0 3px;
display: none;
@include m-theme() {
color: themed($m-grey-800);
}
}
.m-analyticsTable__subCol--value:last-of-type {
padding-right: 16px;
}
.entityDetails {
display: inline;
.m-analyticsTable__row--header {
@include m-theme() {
color: themed($m-grey-200);
}
> * {
margin-right: $minds-margin;
border: none !important;
}
.usernameWrapper {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
a {
}
.m-analyticsTable__row--data {
.m-analyticsTable__col--entity {
padding-left: 16px;
@include m-theme() {
color: themed($m-grey-300);
border: none !important;
}
}
}
}
.col--values {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
.subCol--value {
flex: 1 1 16vw;
&:first-of-type {
padding-left: 24px;
}
&:last-of-type {
padding-right: 16px;
}
.m-analyticsTable__cell {
height: 48px;
}
}
}
}
.col--entity {
flex: 2 3 0;
}
.col--values {
flex: 1 2 0;
}
// MOBILE /////////////////////
.tableWrapper.isMobile {
.col--entity {
flex: 1 3 0;
}
.col--values {
flex: 2 1 0;
}
.row--header {
border: none !important;
}
.row--data {
.col--entity.cell {
border: none !important;
@media screen and (max-width: $max-mobile) {
.m-analyticsTable__wrapper {
.m-analyticsTable__col--entity {
flex: 2 1 0;
padding-left: 24px;
}
}
}
///////////////////////////@at-root @media screen and (max-width: 800px) {
// @media screen and (max-width: 910px) {
// .m-referrals-dashboard__row.m-referrals-dashboard__headerRow {
// span {
// white-space: normal !important;
// max-width: 35px;
// }
// .m-referrals-dashboard__registerCol,
// .m-referrals-dashboard__rewardsCol {
// align-items: flex-start !important;
// }
// .m-referrals-dashboard__rewardsCol span {
// max-width: 46px;
// }
// }
// .m-referrals-dashboard__cell.m-referrals-dashboard__rewardsCol {
// flex: 2 3 0;
// .m-referrals-dashboard__pingButtonContainer button {
// padding: 8px;
// span {
// display: none;
// }
// }
// }
// }
// .m-referrals-dashboard__cell.m-referrals-dashboard__registerCol {
// display: none;
// }
// .m-referrals-dashboard__userCol {
// min-width: 100px;
// }
// .m-referrals-dashboard__statusCol {
// min-width: 60px;
// }
// .m-referrals-dashboard__rewardsCol {
// max-width: 75px;
// }
// }
......@@ -13,7 +13,6 @@ import {
AnalyticsDashboardService,
Visualisation,
} from '../../dashboard.service';
import isMobileOrTablet from '../../../../../helpers/is-mobile-or-tablet';
@Component({
selector: 'm-analytics__table',
......@@ -28,7 +27,6 @@ export class AnalyticsTableComponent implements OnInit, OnDestroy {
reformattedBuckets: Array<any> = [];
minds = window.Minds;
user;
isMobile: boolean;
loadingSubscription: Subscription;
loading: boolean;
valueColCount: number = 1;
......@@ -39,14 +37,12 @@ export class AnalyticsTableComponent implements OnInit, OnDestroy {
})
);
selectedMetric;
constructor(
private analyticsService: AnalyticsDashboardService,
protected cd: ChangeDetectorRef // public session: Session
protected cd: ChangeDetectorRef
) {}
ngOnInit() {
this.isMobile = isMobileOrTablet();
this.metricSubscription = this.selectedMetric$.subscribe(metric => {
this.selectedMetric = metric;
this.visualisation = metric.visualisation;
......@@ -71,7 +67,9 @@ export class AnalyticsTableComponent implements OnInit, OnDestroy {
const reformattedBucket = {};
const reformattedValues = [];
if (!bucket.values['entity']) return;
if (!bucket.values['entity']) {
return;
}
this.columns.forEach((column, i) => {
if (i === 0) {
......
<div class="page" [ngClass]="{ isMobile: isMobile }">
<m-analytics__menu></m-analytics__menu>
<section
class="main"
*ngIf="ready$ | async"
[ngClass]="{ isMobile: isMobile }"
>
<!-- <section class="main" [ngClass]="{ isMobile: isMobile }"> -->
<div class="mainHeader">
<div class="dashboardTitle">
<h3 class="selectedCatLabel">
<div class="m-analytics__dashboard">
<m-sidebarMenu></m-sidebarMenu>
<section class="m-analytics__main" *ngIf="ready$ | async">
<div class="m-analytics__mainHeader">
<div class="m-analytics__dashboardTitle">
<h3 class="m-analytics__selectedCatLabel">
{{ category$ | async | titlecase }}
</h3>
<p
class="selectedCatDescription"
*ngIf="description$ | async as description"
>
{{ description }}
<ng-container *ngIf="(category$ | async) === 'earnings'">
<a *ngIf="!session.getLoggedInUser().pro" routerLink="/pro"
>Upgrade to PRO</a
>
<a
*ngIf="
session.getLoggedInUser().pro &&
!(
session.getLoggedInUser().merchant &&
session.getLoggedInUser().merchant['id']
)
"
routerLink="/wallet/usd"
>Enable payouts</a
>.
</ng-container>
</p>
</div>
<div class="globalFilters">
<!-- <div *ngIf="session.isAdmin()" class="channelSearch"> -->
<!-- <div class="channelSearch">
<div class="m-analytics__globalFilters">
<!-- <div *ngIf="session.isAdmin()" class="m-analytics__channelSearch"> -->
<!-- <div class="m-analytics__channelSearch">
<m-analytics__search></m-analytics__search>
</div> -->
<div class="channelFilter" *ngIf="session.isAdmin()">
<!-- <div class="m-analytics__channelFilter" *ngIf="session.isAdmin()">
<m-analytics__filter [filter]="channelFilter"></m-analytics__filter>
</div>
<div class="timespanFilter">
<m-analytics__filter [filter]="timespanFilter"></m-analytics__filter>
</div> -->
<div class="m-analytics__timespanFilter">
<m-analytics__filter
[filter]="timespanFilter"
[showLabel]="false"
></m-analytics__filter>
</div>
</div>
</div>
<div class="layoutWrapper">
<p
class="m-analytics__selectedCatDescription"
*ngIf="description$ | async as description"
>
{{ description }}
<ng-container *ngIf="(category$ | async) === 'earnings'">
<a *ngIf="!session.getLoggedInUser().pro" routerLink="/pro"
>Upgrade to PRO</a
>
<a
*ngIf="
session.getLoggedInUser().pro &&
!(
session.getLoggedInUser().merchant &&
session.getLoggedInUser().merchant['id']
)
"
routerLink="/wallet/usd"
>Enable payouts</a
>.
</ng-container>
</p>
<div class="m-analytics__layoutWrapper">
<m-analytics__layout--chart
class="m-analytics__layout"
></m-analytics__layout--chart>
<!-- <m-analytics__layout--table
class="m-analytics__layout"
*ngIf="(category$ | async).type === 'table'"
></m-analytics__layout--table>
<m-analytics__layout--summary
<!-- <m-analytics__layout--summary
class="m-analytics__layout"
*ngIf="(category$ | async).type === 'summary'"
></m-analytics__layout--summary> -->
......
m-analytics__dashboard {
display: block;
position: relative;
width: 100%;
}
// ----------------------------------------
// MOBILE
.isMobile {
&.page {
padding: 0;
.main {
padding: 0;
.mainHeader {
padding-left: 24px;
display: block;
}
.layoutWrapper {
box-shadow: none;
padding: 0;
}
}
}
.m-analytics__menu {
padding: 0 8px;
}
}
// ----------------------------------------
.page {
.m-analytics__dashboard {
padding: 16px;
display: flex;
box-sizing: border-box;
flex-direction: row;
height: 100%;
// display: flex;
// box-sizing: border-box;
// flex-direction: row;
width: 100%;
@include m-theme() {
background-color: themed($m-white);
......@@ -42,41 +18,44 @@ m-analytics__dashboard {
h3 {
font-size: 26px;
font-weight: 500;
margin: 24px 16px 24px 0;
}
}
.main {
flex: 4 1 0px;
padding: 16px;
m-sidebarMenu {
display: inline-block;
vertical-align: top;
width: 29%;
}
.mainHeader {
display: flex;
justify-content: space-between;
align-items: flex-start;
.m-analytics__main {
display: inline-block;
width: 65%;
// flex: 4 1 0px;
padding: 16px;
@include m-theme() {
background-color: themed($m-white);
}
}
.m-analytics__mainHeader {
display: flex;
justify-content: space-between;
align-items: flex-start;
// h3 {
// line-height: 0;
// margin-top: 40px;
// }
p {
@include m-theme() {
color: themed($m-grey-300);
}
a {
@include m-theme() {
color: themed($m-blue);
}
text-decoration: none;
m-analytics__filter {
margin: 0;
.m-analyticsFilter__wrapper {
> * {
width: 180px;
}
}
}
.globalFilters {
.m-analytics__globalFilters {
margin: 24px 0;
display: flex;
align-items: baseline;
.channelSearch {
// border-radius: 5px;
.m-analytics__channelSearch {
margin-right: 8px;
@include m-theme() {
border: themed($m-grey-50);
......@@ -84,46 +63,93 @@ m-analytics__dashboard {
}
}
m-analytics__filter {
display: table;
}
.timespanFilter {
.filterWrapper {
.m-analytics__timespanFilter {
.m-analytics__filterWrapper {
margin-top: 0px;
}
.filterLabel {
.m-analytics__filterLabel {
display: none;
}
}
}
.layoutWrapper {
}
.m-analytics__selectedCatDescription {
margin: 8px 16px 32px 0;
font-weight: 400;
@include m-theme() {
color: themed($m-grey-300);
}
a {
font-weight: 400;
text-decoration: none;
@include m-theme() {
box-shadow: 0px 0px 10px -3px rgba(themed($m-black-always), 0.3);
color: themed($m-blue);
}
}
.m-analytics__layout {
position: relative;
display: block;
width: 100%;
}
.m-analytics__layoutWrapper {
@include m-theme() {
box-shadow: 0px 0px 10px -3px rgba(themed($m-black-always), 0.3);
}
}
// *************************************
.m-analytics__layout {
position: relative;
display: block;
width: 100%;
}
// @media screen and (max-width: $min-tablet) {
@media screen and (max-width: 300px) {
.page {
flex-direction: column;
.sidebar {
.catContainer {
@include m-theme() {
background-color: themed($m-grey-200);
color: themed($m-white);
@media screen and (max-width: $min-tablet) {
m-analytics__dashboard {
.m-analytics__dashboard {
display: block;
padding: 0;
// flex-direction: column;
.m-analytics__main {
padding: 0;
max-width: none;
width: 100%;
.m-analytics__mainHeader {
padding-left: 24px;
m-analytics__filter {
margin: 0 32px 8px 0;
.m-analyticsFilter__wrapper {
> * {
width: 160px;
}
}
}
}
.m-analytics__selectedCatDescription {
margin: 0 24px 24px 24px;
}
.m-analytics__layoutWrapper {
padding: 0;
@include m-theme() {
box-shadow: none;
}
}
}
}
}
}
// :(
@media screen and (max-width: $max-mobile) {
m-analytics__dashboard {
.m-analytics__dashboard {
.m-analytics__main {
.m-analytics__mainHeader {
m-analytics__filter {
.m-analyticsFilter__wrapper {
> * {
width: 140px;
}
}
}
}
}
}
}
}
......@@ -17,9 +17,6 @@ import { Session } from '../../../services/session';
import { AnalyticsDashboardService } from './dashboard.service';
import { Filter } from './../../../interfaces/dashboard';
// import categories from './categories.default';
import isMobileOrTablet from '../../../helpers/is-mobile-or-tablet';
@Component({
selector: 'm-analytics__dashboard',
templateUrl: './dashboard.component.html',
......@@ -27,9 +24,6 @@ import isMobileOrTablet from '../../../helpers/is-mobile-or-tablet';
providers: [AnalyticsDashboardService],
})
export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
isMobile: boolean;
// subscription: Subscription;
paramsSubscription: Subscription;
ready$ = this.analyticsService.ready$;
......@@ -56,12 +50,10 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
// TODO: why wasn't this working? didn't reroute
if (!this.session.isLoggedIn()) {
this.router.navigate(['/login']);
return;
}
this.isMobile = isMobileOrTablet();
this.title.setTitle('Analytics');
......@@ -105,8 +97,7 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
}
updateTimespan(timespanId) {
// TODO
// update url
// TODO: update url
// this.analyticsService.updateTimespan(timespanId);
}
......@@ -120,6 +111,8 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
if (this.paramsSubscription) this.paramsSubscription.unsubscribe();
if (this.paramsSubscription) {
this.paramsSubscription.unsubscribe();
}
}
}
......@@ -45,6 +45,7 @@ export interface Filter {
label: string;
options: Option[];
description: string;
expanded?: boolean;
}
export interface Option {
......@@ -118,14 +119,8 @@ let _state: UserState = fakeData[0];
const deepDiff = (prev, curr) => JSON.stringify(prev) === JSON.stringify(curr);
// **********************************************************************
// **********************************************************************
@Injectable()
export class AnalyticsDashboardService {
/**
* Initialize the state subject and make it an observable
*/
private store = new BehaviorSubject<UserState>(_state);
private state$ = this.store.asObservable();
......@@ -282,8 +277,8 @@ export class AnalyticsDashboardService {
} else {
filter.push(selectedFilterStr);
}
console.log('update filter called: ' + selectedFilterStr);
console.log(filter);
// console.log('update filter called: ' + selectedFilterStr);
// console.log(filter);
this.updateState({ ..._state, filter });
}
......@@ -292,7 +287,7 @@ export class AnalyticsDashboardService {
/** Update internal state cache and emit from store... */
private updateState(state: UserState) {
console.log('update state called');
// console.log('update state called');
this.store.next((_state = state));
}
......@@ -315,8 +310,4 @@ export class AnalyticsDashboardService {
map(response => response)
);
}
getData() {
console.warn('call was made to legacy function DashboardService.getData()');
}
}
......@@ -3,22 +3,21 @@ const fakeData: Array<any> = [
// CHART TESTS
loading: false,
category: 'traffic',
description: 'imma traffic description',
timespan: '30d',
timespans: [
{
id: '30d',
label: 'Last 30 days',
interval: 'day',
comparison_interval: 28,
comparison_interval: 30,
from_ts_ms: 1567296000000,
from_ts_iso: '2019-09-01T00:00:00+00:00',
selected: true,
selected: false,
},
{
id: '1y',
label: '1 year ago',
interval: 'month',
id: '12m',
label: 'Last 12 months',
interval: 'day',
comparison_interval: 365,
from_ts_ms: 1538352000000,
from_ts_iso: '2018-10-01T00:00:00+00:00',
......@@ -117,10 +116,25 @@ const fakeData: Array<any> = [
'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentiuti atque corrupti quos dolores',
visualisation: null,
},
{
id: 'active_users',
label: 'Active UsersA',
permissions: ['admin', 'user'],
summary: {
current_value: 120962,
comparison_value: 120962,
comparison_interval: 28,
comparison_positive_inclination: true,
},
unit: 'number',
description:
'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentiuti atque corrupti quos dolores',
visualisation: null,
},
{
id: 'signups',
label: 'Signups',
permissions: ['admin'],
permissions: ['admin', 'user'],
summary: {
current_value: 53060,
comparison_value: 60577,
......@@ -173,7 +187,7 @@ const fakeData: Array<any> = [
{
key: 1567641600000,
date: '2019-09-05T00:00:00+00:00',
value: 5,
value: 1,
},
{
key: 1567296000000,
......@@ -345,7 +359,7 @@ const fakeData: Array<any> = [
{ id: 'all', label: 'All', available: true, selected: false },
{
id: 'browser',
label: 'Browser',
label: 'BrowserBrowserBrowserBrowserBrowserBrowser',
available: true,
selected: false,
},
......@@ -498,7 +512,7 @@ const fakeData: Array<any> = [
],
columns: [
{ id: 'entity', label: 'Views' },
{ id: 'views::total', label: 'Total' },
{ id: 'views::total', label: 'Total Views' },
{ id: 'views::organic', label: 'Organic' },
{ id: 'views::single', label: 'Single' },
],
......
<div class="spinnerContainer" *ngIf="loading$ | async">
<div class="m-analytics__spinnerContainer" *ngIf="loading$ | async">
<div class="mdl-spinner mdl-js-spinner is-active" [mdl]></div>
</div>
<ng-container *ngIf="selectedMetric && selectedMetric.visualisation">
......@@ -6,7 +6,7 @@
*ngIf="selectedMetric.visualisation.type === 'chart'"
></m-analytics__metrics>
<div
class="filterableChartWrapper"
class="m-analytics__filterableChartWrapper"
[ngClass]="{ isTable: isTable, isMobile: isMobile }"
>
<m-analytics__chart
......
.filterableChartWrapper {
.m-analytics__filterableChartWrapper {
position: relative;
padding: 16px;
// width: 80%;
margin-bottom: 48px;
@include m-theme() {
border-top: 1px solid themed($m-grey-50);
border-top: 1px solid rgba(themed($m-grey-50), 0.5);
background-color: themed($m-white);
}
&.isTable {
padding: 0;
width: 100%;
}
&.isMobile {
border-top: none;
min-width: 420px;
}
}
.spinnerContainer {
.m-analytics__spinnerContainer {
height: 30%;
width: 100%;
display: flex;
......
......@@ -3,18 +3,18 @@ import {
OnInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
OnDestroy,
} from '@angular/core';
import { Observable, Subscription, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { AnalyticsDashboardService } from '../../dashboard.service';
import isMobileOrTablet from '../../../../../helpers/is-mobile-or-tablet';
@Component({
selector: 'm-analytics__layout--chart',
templateUrl: './layout-chart.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AnalyticsLayoutChartComponent implements OnInit {
export class AnalyticsLayoutChartComponent implements OnInit, OnDestroy {
subscription: Subscription;
loading$ = this.analyticsService.loading$;
selectedMetric$ = combineLatest(
......@@ -28,7 +28,6 @@ export class AnalyticsLayoutChartComponent implements OnInit {
);
selectedMetric;
isTable: boolean = false;
isMobile: boolean;
constructor(
private analyticsService: AnalyticsDashboardService,
......@@ -37,7 +36,6 @@ export class AnalyticsLayoutChartComponent implements OnInit {
ngOnInit() {
this.subscription = this.selectedMetric$.subscribe(metric => {
console.log('new metric');
this.selectedMetric = metric;
this.isTable =
......@@ -46,7 +44,6 @@ export class AnalyticsLayoutChartComponent implements OnInit {
this.selectedMetric.visualisation.type === 'table';
this.detectChanges();
});
this.isMobile = isMobileOrTablet();
}
detectChanges() {
......
<p>
analytics__layout--table works!
</p>