...
 
Commits (32)
......@@ -39,3 +39,4 @@ cypress/videos
!/.gitlab
!/.githooks
!/.prettierrc
!.gitattributes
context('Rewards Product Page', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
if (!sessionCookie) {
return cy.login(true);
}
});
});
beforeEach(() => {
cy.preserveCookies();
});
const joinRewards = '.m-marketing__mainWrapper .mf-button';
it('should have a join rewards button', () => {
cy.visit('/rewards');
cy.get(joinRewards)
.should('be.visible')
.should('contain', 'Join Rewards')
.click();
cy.location('pathname').should(
'contains',
'/wallet/tokens/contributions'
);
});
});
context('Token Page', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
if (!sessionCookie) {
return cy.login(true);
}
});
});
beforeEach(() => {
cy.preserveCookies();
cy.visit('/token');
});
it('should have the ability to trigger Buy Tokens modal', () => {
const tokensInput = 'm-blockchain--purchase input[name=amount]';
const buyTokensButton =
'm-blockchain--purchase .m-blockchainTokenPurchase__action .mf-button';
const anyBuyTokensModal =
'm-blockchain--purchase m-modal .m-modal-container';
cy.get(tokensInput)
.focus()
.clear()
.type('0');
cy.get(buyTokensButton).should('be.disabled');
cy.get(tokensInput)
.focus()
.clear()
.type('1');
cy.get(buyTokensButton)
.should('not.be.disabled')
.click();
cy.get('.m-get-metamask--cancel-btn.m-btn').click();
cy.get(anyBuyTokensModal).should('be.visible');
});
it('should have the ability to trigger Buy Eth modal', () => {
const buyEthLink =
'm-blockchain--purchase .m-blockchainTokenPurchase__ethRate a';
const buyEthModal = 'm-blockchain__eth-modal .m-modal-container';
cy.get(buyEthLink).click();
cy.get(buyEthModal).should('be.visible');
});
});
context('Boost Product Page', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
if (!sessionCookie) {
return cy.login(true);
}
});
});
beforeEach(() => {
cy.preserveCookies();
});
const createBoostButton = '.m-marketing__mainWrapper .mf-button';
it('should have a create boost button', () => {
cy.visit('/boost');
cy.get(createBoostButton)
.should('be.visible')
.should('contain', 'Create Boost')
.click();
cy.location('pathname').should(
'contains',
'/boost/console/newsfeed/create'
);
});
});
context('Pro Product Page', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
if (!sessionCookie) {
return cy.login(true);
}
});
});
beforeEach(() => {
cy.preserveCookies();
});
const contactUsButton = '.m-marketing__mainWrapper .mf-button';
it('should have a contact us button', () => {
cy.visit('/nodes', {
onBeforeLoad(_window) {
cy.stub(_window, 'open');
},
});
cy.get(contactUsButton)
.should('be.visible')
.should('contain', 'Contact us for details')
.click();
cy.window()
.its('open')
.should('be.called');
});
});
context('Plus Product Page', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
if (!sessionCookie) {
return cy.login(true);
}
});
});
beforeEach(() => {
cy.preserveCookies();
});
const upgradeButton = 'm-plus--subscription .mf-button';
const wirePaymentsComponent = 'm-wire__paymentscreator .m-wire--creator';
it('should open the Wire Payment modal', () => {
cy.visit('/plus');
cy.get(upgradeButton)
.should('be.visible')
.should('contain', 'Upgrade to Plus')
.click();
cy.get(wirePaymentsComponent).should('be.visible');
});
it('should automatically open the Wire Payment modal', () => {
cy.visit('/plus?i=yearly&c=tokens');
cy.get(wirePaymentsComponent).should('be.visible');
});
});
context('Pro Product Page', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
if (!sessionCookie) {
return cy.login(true);
}
});
});
beforeEach(() => {
cy.preserveCookies();
});
const upgradeButton = 'm-pro--subscription .mf-button';
const wirePaymentsComponent = 'm-wire__paymentscreator .m-wire--creator';
it('should show a coming soon button', () => {
cy.visit('/pro');
cy.get(upgradeButton)
.should('be.visible')
.should('contain', 'Coming soon')
.click();
});
// it('should open the Wire Payment modal', () => {
//
// cy.visit('/pro');
//
// cy.get(upgradeButton)
// .should('be.visible')
// .should('contain', 'Upgrade to Pro')
// .click();
//
// cy.get(wirePaymentsComponent).should('be.visible');
// });
//
// it('should automatically open the Wire Payment modal', () => {
// cy.visit('/pro?i=yearly&c=tokens');
//
// cy.get(wirePaymentsComponent).should('be.visible');
// });
});
......@@ -21,7 +21,6 @@ context('Pro Settings', () => {
43: '#tile_ratio_4\:3', // 4:3
11: '#tile_ratio_1\:1' , // 1:1
},
logoGuid: '#logo_guid',
}
const hashtags = {
......
context('Upgrades page', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
if (!sessionCookie) {
return cy.login(true);
}
});
});
beforeEach(() => {
cy.preserveCookies();
cy.visit('/upgrades');
});
it('should scroll to upgrades table', () => {
cy.viewport(1200, 600); // Only on desktop
const scrollButton = '[data-cy="m-upgrades__upgrade-now-button"]';
const heading = '.m-upgradesUpgradeOptions__header h2';
cy.get(scrollButton)
.should('contain', 'Upgrade now')
.click();
cy.wait(1500);
cy.isInViewport(heading);
});
// TODO: Toggles tests (make them testable)
it('should have the ability to trigger Buy Tokens modal', () => {
const tokensInput = 'm-blockchain--purchase input[name=amount]';
const buyTokensButton =
'm-blockchain--purchase .m-blockchainTokenPurchase__action .mf-button';
const anyBuyTokensModal =
'm-blockchain--purchase m-modal .m-modal-container';
cy.get(tokensInput)
.focus()
.clear()
.type('0');
cy.get(buyTokensButton).should('be.disabled');
cy.get(tokensInput)
.focus()
.clear()
.type('1');
cy.get(buyTokensButton)
.should('not.be.disabled')
.click();
cy.get('.m-get-metamask--cancel-btn.m-btn').click();
cy.get(anyBuyTokensModal).should('be.visible');
});
it('should have the ability to trigger Buy Eth modal', () => {
const buyEthLink =
'm-blockchain--purchase .m-blockchainTokenPurchase__ethRate a';
const buyEthModal = 'm-blockchain__eth-modal .m-modal-container';
cy.get(buyEthLink).click();
cy.get(buyEthModal).should('be.visible');
});
it('should navigate to Plus and trigger a Wire', () => {
const upgradeButton = cy.get(
'[data-cy="m-upgradeOptions__upgrade-to-plus-button"]'
);
upgradeButton.click();
cy.location('pathname').should('contain', '/plus');
});
it('should navigate to Pro and trigger a Wire', () => {
const upgradeButton = cy.get(
'[data-cy="m-upgradeOptions__upgrade-to-pro-button"]'
);
upgradeButton.click();
cy.location('pathname').should('contain', '/pro');
});
it('should navigate to Nodes', () => {
const upgradeButton = cy.get(
'[data-cy="m-upgradeOptions__contact-us-nodes-button"]'
);
upgradeButton.click();
cy.location('pathname').should('contain', '/nodes');
});
});
......@@ -5,10 +5,10 @@
* @desc Spec tests for Wire transactions.
*/
import generateRandomId from "../support/utilities";
import generateRandomId from "../../support/utilities";
// Issue to re-enable https://gitlab.com/minds/front/issues/1846
context.skip('Wire', () => {
context.skip('Wire Creator', () => {
const receiver = {
username: generateRandomId(),
......
context('Pay Product Page', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
if (!sessionCookie) {
return cy.login(true);
}
});
});
beforeEach(() => {
cy.preserveCookies();
});
const monetizeChannelButton = '.m-marketing__mainWrapper .mf-button';
it('should have a monetize channel button', () => {
cy.visit('/pay');
cy.get(monetizeChannelButton)
.should('be.visible')
.should('contain', 'Monetize your channel')
.click();
cy.location('pathname').should(
'contains',
'/wallet/tokens/contributions'
);
});
});
......@@ -255,3 +255,36 @@ function b64toBlob(b64Data, contentType, sliceSize = 512) {
blob.lastModifiedDate = new Date();
return blob;
}
/**
* Check if certain element is on viewport
* @param {*} element
*/
Cypress.Commands.add('isInViewport', element => {
cy.get(element).then($el => {
const bottom = Cypress.$(cy.state('window')).height();
const rect = $el[0].getBoundingClientRect();
expect(rect.top).not.to.be.greaterThan(bottom);
expect(rect.bottom).not.to.be.greaterThan(bottom);
expect(rect.top).not.to.be.greaterThan(bottom);
expect(rect.bottom).not.to.be.greaterThan(bottom);
})
});
/**
* Check if certain element is on viewport
* @param {*} element
*/
Cypress.Commands.add('isNotInViewport', element => {
cy.get(element).then($el => {
const bottom = Cypress.$(cy.state('window')).height();
const rect = $el[0].getBoundingClientRect();
expect(rect.top).to.be.greaterThan(bottom);
expect(rect.bottom).to.be.greaterThan(bottom);
expect(rect.top).to.be.greaterThan(bottom);
expect(rect.bottom).to.be.greaterThan(bottom);
})
});
......@@ -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.
......@@ -443,7 +443,9 @@ export class ButtonsPlugin {
const $input = this.$element.querySelector('.medium-media-buttons');
if ($input) {
$input.parentNode.removeChild($input);
try {
$input.parentNode.removeChild($input);
} catch (e) {}
}
}
......
......@@ -6,29 +6,25 @@
<ul class="m-grid__column-10">
<li>
<a
href="https://www.independent.co.uk/news/business/indyventure/minds-facebook-alternative-deletefacebook-social-network-data-a8475841.html"
href="https://www.wsj.com/articles/facebook-on-notice-as-vietnam-tightens-grip-on-social-media-11547036594"
target="_blank"
>
<img
[src]="cdnAssetsUrl + 'assets/marketing/press-logos/independent.png'"
alt="Independent"
[src]="cdnAssetsUrl + 'assets/marketing/wsj.png'"
alt="The Wall Street Journal"
/>
</a>
</li>
<li>
<a
href="https://www.foxnews.com/tech/alternate-social-media-squash-extremist-content-without-violating-first-amendment"
href="https://www.wired.com/story/minds-anti-facebook/"
target="_blank"
>
<img
[src]="cdnAssetsUrl + 'assets/marketing/foxnews.png'"
alt="Fox News"
/>
<img [src]="cdnAssetsUrl + 'assets/marketing/wired.png'" alt="Wired" />
</a>
</li>
<li *ngIf="false">
<img [src]="cdnAssetsUrl + 'assets/marketing/forbes.png'" alt="Forbes" />
</li>
<li>
<a
href="https://techcrunch.com/2018/04/16/minds-aims-to-decentralize-the-social-network/"
......@@ -40,35 +36,62 @@
/>
</a>
</li>
<li>
<a
href="https://mobile.reuters.com/article/amp/idUSKBN1K7147"
href="http://podcasts.joerogan.net/podcasts/bill-ottman"
target="_blank"
>
<img
[src]="cdnAssetsUrl + 'assets/marketing/reuters.png'"
alt="Reuters"
[src]="cdnAssetsUrl + 'assets/marketing/tjre.png'"
alt="The Joe Rogan Experience"
/>
</a>
</li>
<li>
<a
href="https://www.wired.com/story/minds-anti-facebook/"
href="https://www.foxnews.com/tech/alternate-social-media-squash-extremist-content-without-violating-first-amendment"
target="_blank"
>
<img [src]="cdnAssetsUrl + 'assets/marketing/wired.png'" alt="Wired" />
<img
[src]="cdnAssetsUrl + 'assets/marketing/foxnews.png'"
alt="Fox News"
/>
</a>
</li>
<li>
<a
href="http://podcasts.joerogan.net/podcasts/bill-ottman"
href="https://www.independent.co.uk/news/business/indyventure/minds-facebook-alternative-deletefacebook-social-network-data-a8475841.html"
target="_blank"
>
<img
[src]="cdnAssetsUrl + 'assets/marketing/tjre.png'"
alt="The Joe Rogan Experience"
[src]="cdnAssetsUrl + 'assets/marketing/press-logos/independent.png'"
alt="Independent"
/>
</a>
</li>
<li>
<a
href="https://www.reuters.com/article/us-vietnam-cyber-usa/u-s-lawmakers-urge-google-facebook-to-resist-vietnam-cybersecurity-law-idUSKBN1K7147"
target="_blank"
>
<img
[src]="cdnAssetsUrl + 'assets/marketing/reuters.png'"
alt="Reuters"
/>
</a>
</li>
<li>
<a
href="https://www.npr.org/2019/08/06/748810962/debate-over-policing-free-speech-intensifies-as-8chan-struggles-to-stay-online"
target="_blank"
>
<img [src]="cdnAssetsUrl + 'assets/marketing/npr.png'" alt="npr" />
</a>
</li>
</ul>
</div>
......@@ -2,14 +2,19 @@
.m-marketing__asFeaturedIn {
max-width: 1084px;
margin: 40px auto 0;
margin: 45px auto 80px;
.m-marketing__asFeaturedIn--noMargin & {
margin-bottom: 0;
}
@media screen and (max-width: $m-grid-min-vp) {
&.m-grid {
display: block;
margin-bottom: 45px;
}
margin: 20px 0 45px;
margin: 20px 0 50px;
padding: 0 12px;
}
......@@ -45,20 +50,8 @@
object-fit: contain;
@media screen and (max-width: $m-grid-min-vp) {
width: 40px;
height: 40px;
}
}
&.m-marketingAsFeaturedIn__item--bigger {
img {
width: 96px;
height: 96px;
@media screen and (max-width: $m-grid-min-vp) {
width: 40px;
height: 40px;
}
width: 32px;
height: 32px;
}
}
}
......
<div class="m-marketing__footer">
<div class="m-grid m-marketingFooter__columns">
<div
class="m-grid__column-3 m-grid__column-12--mobile m-marketingFooter__column"
class="m-grid__column-4 m-grid__column-12--mobile m-marketingFooter__column m-marketingFooter__brandColumn"
>
<h4 i18n>About</h4>
<div class="m-marketingFooter__mindsLogo"></div>
<ul>
<li hidden>
<a href="#" i18n>
Company
</a>
</li>
<h4 class="m-marketingFooter__sloganText" i18n>
Take back control of your social media
</h4>
<li hidden>
<a href="#" i18n>
Mission
</a>
</li>
<div class="m-marketingFooter__text">&copy; {{ year }} Minds, Inc.</div>
</div>
<li hidden>
<a href="#" i18n>
Features
</a>
</li>
<div
class="m-grid__column-2 m-grid__column-12--mobile m-marketingFooter__column"
>
<h4 i18n>About</h4>
<ul>
<li>
<a routerLink="/mobile" i18n>
Mobile
......@@ -32,7 +26,7 @@
<li>
<a routerLink="/jobs" i18n>
Jobs
Careers
</a>
</li>
......@@ -67,7 +61,7 @@
</div>
<div
class="m-grid__column-3 m-grid__column-12--mobile m-marketingFooter__column"
class="m-grid__column-2 m-grid__column-12--mobile m-marketingFooter__column"
>
<h4 i18n>Business</h4>
......@@ -96,6 +90,12 @@
</a>
</li>
<li>
<a routerLink="/pay" i18n>
Pay
</a>
</li>
<li>
<a routerLink="/nodes" i18n>
Nodes
......@@ -109,8 +109,8 @@
</li>
<li>
<a routerLink="/wire" i18n>
Pay
<a routerLink="/rewards" i18n>
Rewards
</a>
</li>
......@@ -123,7 +123,7 @@
</div>
<div
class="m-grid__column-3 m-grid__column-12--mobile m-marketingFooter__column"
class="m-grid__column-2 m-grid__column-12--mobile m-marketingFooter__column"
>
<h4>Developers</h4>
......@@ -161,7 +161,7 @@
</div>
<div
class="m-grid__column-3 m-grid__column-12--mobile m-marketingFooter__column"
class="m-grid__column-2 m-grid__column-12--mobile m-marketingFooter__column"
>
<h4>Support</h4>
......@@ -189,30 +189,19 @@
Status
</a>
</li>
<li hidden>
<a routerLink="/p/contact" i18n>
Contact
</a>
</li>
</ul>
</div>
</div>
<div class="m-marketing__sep m-marketing__sep--big"></div>
<div
class="m-marketing__sep m-marketing__sep--dashed m-marketing__sep--big"
></div>
<div class="m-grid m-marketingFooter__columns">
<div
class="m-grid__column-2 m-grid__column-12--mobile m-marketingFooter__column m-marketingFooter__column--noMobileSpacing"
i18n
>
<div class="m-marketingFooter__text">&copy; {{ year }} Minds, Inc.</div>
</div>
<div
class="m-grid__column-10 m-grid__column-12--mobile m-marketingFooter__column"
class="m-grid__column-12 m-grid__column-12--mobile m-marketingFooter__column"
>
<ul class="m-marketingFooter__inlineList">
<ul class="m-marketingFooter__inlineList m-marketingFooter__legalLinks">
<li>
<a routerLink="/p/terms" i18n>
Terms of Service
......
......@@ -2,10 +2,10 @@
m-marketing__footer {
display: block;
margin-top: 105px;
margin: 105px 0 95px;
@media screen and (max-width: $m-grid-min-vp) {
margin-top: 80px;
margin: 80px 0 0;
}
@include m-theme() {
......@@ -19,12 +19,14 @@ m-marketing__footer {
.m-marketing__footer {
padding: 60px 0 48px;
border-top: 1px dashed;
@media screen and (max-width: $m-grid-min-vp) {
padding: 32px 0;
}
@include m-theme() {
border-color: themed($m-grey-50);
color: themed($m-grey-800);
}
......@@ -47,6 +49,17 @@ m-marketing__footer {
}
}
&.m-marketingFooter__brandColumn {
width: 60%;
margin: 0 auto;
@media screen and (max-width: $m-grid-min-vp) {
width: 60%;
grid-row: 999;
margin: 32px 0 0;
}
}
h4 {
font-weight: 500;
font-size: 16px;
......@@ -60,11 +73,34 @@ m-marketing__footer {
@include m-theme() {
color: themed($m-grey-800);
}
&.m-marketingFooter__sloganText {
margin: 0 0 21px;
}
}
.m-marketingFooter__mindsLogo {
width: 116px;
height: 43px;
margin: 0 0 35px;
background: url('<%= APP_CDN %>/assets/logos/logo.svg') no-repeat center
left;
background-size: contain;
@include m-on-theme(dark) {
background: url('<%= APP_CDN %>/assets/logos/logo-white.svg') no-repeat
center left;
background-size: contain;
}
}
.m-marketingFooter__text {
font-size: 14px;
line-height: 26px;
@include m-theme() {
color: themed($m-grey-300);
}
}
ul {
......@@ -90,7 +126,7 @@ m-marketing__footer {
a {
color: inherit;
font-weight: 300;
font-weight: normal;
text-decoration: none;
}
}
......@@ -108,6 +144,16 @@ m-marketing__footer {
margin-right: 0;
}
}
&.m-marketingFooter__legalLinks {
text-align: right;
padding-right: 92px;
@media screen and (max-width: $m-grid-min-vp) {
text-align: inherit;
padding-right: initial;
}
}
}
}
}
......
......@@ -7,4 +7,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
})
export class MarketingFooterComponent {
readonly year: number = new Date().getFullYear();
readonly cdnAssetsUrl: string = window.Minds.cdn_assets_url;
}
......@@ -20,6 +20,10 @@ m-marketing {
width: 100%;
margin: 40px 0;
&.m-marketing__sep--dashed {
border-top-style: dashed;
}
&.m-marketing__sep--big {
margin: 60px 0;
}
......
@import '../../../../foundation/grid-values';
.m-marketing__main,
.m-marketing__section {
a {
font: inherit;
text-decoration: none;
cursor: pointer;
@include m-theme() {
color: themed($m-blue);
}
}
.m-marketing--hideMobile {
@media screen and (max-width: $m-grid-min-vp) {
display: none;
}
}
.m-marketing__title,
h1 {
font-size: 22px;
line-height: 44px;
font-weight: bold;
opacity: 0.7;
margin: 12px 0 4px;
@include m-theme() {
color: themed($m-grey-800);
}
}
.m-marketing__subtitle,
h2 {
font-weight: 900;
font-size: 42px;
line-height: 44px;
margin: 0 0 23px;
position: relative;
z-index: 0;
@media screen and (max-width: $m-grid-min-vp) {
font-size: 32px;
line-height: 34px;
margin: 0 0 18px;
}
&.m-marketing__subtitle--asTitle {
font-size: 48px;
line-height: 53px;
margin: 0 0 26px;
@media screen and (max-width: $m-grid-min-vp) {
font-size: 38px;
line-height: 46px;
margin: 0 0 21px;
}
}
em {
font-style: inherit;
text-decoration: inherit;
white-space: nowrap;
position: relative;
&::after {
content: '';
display: inline-block;
position: absolute;
background: rgba(80, 226, 195, 0.3);
top: 0.36em;
left: -0.03em;
right: -0.03em;
bottom: 0.22em;
pointer-events: none;
z-index: -1;
}
}
em + em::after {
left: -0.25em;
}
}
p.m-marketing__description {
font-size: 18px;
line-height: 27px;
margin: 0 0 36px;
}
ul.m-marketing__points {
list-style: disc;
margin: 0 0 45px;
padding: 0 0 0 1em;
font-size: 16px;
line-height: 21px;
> li {
margin-bottom: 19px;
&:last-child {
margin-bottom: 0;
}
}
}
.m-marketing__actionButtons {
> * {
margin: 0 25px 0 0;
@media screen and (max-width: $m-grid-min-vp) {
margin: 0 0 20px;
}
&:last-child {
margin-right: initial;
}
}
}
.m-marketing__links {
@media screen and (max-width: $m-grid-min-vp) {
text-align: center;
}
h3 {
margin: 0;
font-size: 15px;
line-height: 20px;
opacity: 0.5;
font-weight: normal;
@include m-theme() {
color: themed($m-grey-800);
}
}
ul {
margin: 13px 0 0 0;
padding: 0;
list-style: none;
> li {
margin: 0 0 10px;
font-size: 15px;
line-height: 20px;
&:last-child {
margin: 0;
}
}
}
a {
> * {
vertical-align: middle;
}
i.material-icons {
margin-left: 0.35em;
font-size: 16px;
line-height: 16px;
opacity: 0.4;
@include m-theme() {
color: themed($m-grey-800);
}
}
}
}
}
@import '../../../../foundation/grid-values';
.m-marketing__main,
.m-marketing__section {
// Common
overflow-x: hidden;
.m-marketing--hideMobile {
@media screen and (max-width: $m-grid-min-vp) {
display: none;
}
}
.m-marketing__title,
h1 {
font-size: 22px;
line-height: 44px;
font-weight: bold;
opacity: 0.7;
margin: 12px 0 4px;
@include m-theme() {
color: themed($m-grey-800);
}
}
.m-marketing__subtitle,
h2 {
font-weight: 900;
font-size: 42px;
line-height: 44px;
margin: 0 0 23px;
position: relative;
z-index: 0;
@media screen and (max-width: $m-grid-min-vp) {
font-size: 32px;
line-height: 34px;
margin: 0 0 18px;
}
&.m-marketing__subtitle--asTitle {
font-size: 48px;
line-height: 53px;
margin: 0 0 26px;
@media screen and (max-width: $m-grid-min-vp) {
font-size: 38px;
line-height: 46px;
margin: 0 0 21px;
}
}
em {
font-style: inherit;
text-decoration: inherit;
white-space: nowrap;
position: relative;
&::after {
content: '';
display: inline-block;
position: absolute;
background: rgba(80, 226, 195, 0.3);
top: 0.36em;
left: -0.03em;
right: -0.03em;
bottom: 0.22em;
pointer-events: none;
z-index: -1;
}
}
em + em::after {
left: -0.25em;
}
}
p.m-marketing__description {
font-size: 18px;
line-height: 27px;
margin: 0 0 36px;
}
ul.m-marketing__points {
list-style: disc;
margin: 0 0 45px;
padding: 0 0 0 1em;
font-size: 16px;
line-height: 21px;
> li {
margin-bottom: 19px;
&:last-child {
margin-bottom: 0;
}
}
}
// Style 1
&.m-marketing__section--style-1 {
@include m-theme() {
background: linear-gradient(
......@@ -272,160 +176,4 @@
}
}
}
// Style 2
&.m-marketing__section--style-2 {
.m-marketing__wrapper {
position: relative;
z-index: 0;
padding: 72px 0 32px;
@media screen and (max-width: $m-grid-min-vp) {
padding: 0;
}
}
.m-marketing__body {
position: relative;
@media screen and (max-width: $m-grid-min-vp) {
padding: 0 30px 30px;
}
&::before {
content: '';
position: absolute;
top: 0;
right: -290px;
bottom: -56px;
left: 0;
transform: translate(-86px, -56px);
clip-path: polygon(0% 0%, 0% 100%, 100% 92%, 100% 0%);
z-index: -1;
@include m-theme() {
background: linear-gradient(
180deg,
themed($m-marketing-bg-colored-gradient-start) 0%,
themed($m-marketing-bg-colored-gradient-end) 99.99%
);
}
@media screen and (max-width: $m-grid-min-vp) {
right: 0;
bottom: -3vw;
transform: none;
clip-path: none;
}
}
h1 {
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
margin: 15px 0 15px;
text-align: center;
}
}
h2 {
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
font-size: 28px;
line-height: 32px;
margin: 0 0 17px;
text-align: center;
}
}
}
p.m-marketing__description {
margin-bottom: 42px;
padding-right: 200px;
@include m-theme() {
color: themed($m-grey-300);
}
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
padding-right: 0;
margin-bottom: 30px;
font-size: 16px;
line-height: 23px;
text-align: center;
}
}
.m-marketing__image {
position: relative;
z-index: 0;
img {
object-fit: contain;
clip-path: polygon(0% 1%, 0% 97%, 100% 100%, 100% 0%);
@media screen and (max-width: $m-grid-min-vp) {
width: 100vw;
height: 100vw;
object-fit: cover;
clip-path: polygon(0% 2%, 0% 97%, 100% 100%, 100% 0%);
}
}
span {
display: inline-block;
position: relative;
// Deco
&::before {
content: '';
display: block;
position: absolute;
width: 393px;
height: 193px;
bottom: 0;
right: 0;
transform: translate(45px, 32px);
background: url('<%= APP_CDN %>/assets/marketing/deco_1.svg')
no-repeat;
z-index: -1;
@media screen and (max-width: $m-grid-min-vp) {
content: initial;
display: none;
}
}
&::after {
content: '';
display: block;
position: absolute;
top: 0;
right: 0;
width: 284px;
height: 262px;
transform: translate(35px, -35px);
background: url('<%= APP_CDN %>/assets/marketing/deco_2-straight.svg')
no-repeat;
z-index: -1;
@media screen and (max-width: $m-grid-min-vp) {
content: initial;
display: none;
}
}
}
}
}
}
@import '../../../../foundation/grid-values';
.m-marketing__main,
.m-marketing__section {
&.m-marketing__section--style-2 {
.m-marketing__wrapper {
position: relative;
z-index: 0;
padding: 72px 0 0;
margin-bottom: 95px;
@media screen and (max-width: $m-grid-min-vp) {
padding: 0;
margin-bottom: 15px;
}
}
.m-marketing__body {
position: relative;
@media screen and (max-width: $m-grid-min-vp) {
padding: 0 30px 30px;
}
&::before {
content: '';
position: absolute;
top: 0;
right: -290px;
bottom: -56px;
left: 0;
transform: translate(-86px, -56px);
clip-path: polygon(0% 0%, 0% 100%, 100% 92%, 100% 0%);
z-index: -1;
@include m-theme() {
background: linear-gradient(
180deg,
themed($m-marketing-bg-colored-gradient-start) 0%,
themed($m-marketing-bg-colored-gradient-end) 99.99%
);
}
@media screen and (max-width: $m-grid-min-vp) {
right: 0;
bottom: -3vw;
transform: none;
clip-path: none;
}
}
h1 {
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
margin: 15px 0 15px;
text-align: center;
}
}
h2 {
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
font-size: 28px;
line-height: 32px;
margin: 0 0 17px;
text-align: center;
}
}
}
p.m-marketing__description {
margin-bottom: 42px;
padding-right: 200px;
@include m-theme() {
color: themed($m-grey-300);
}
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
padding-right: 0;
margin-bottom: 30px;
font-size: 16px;
line-height: 23px;
text-align: center;
}
}
.m-marketing__image {
position: relative;
z-index: 0;
img {
object-fit: cover;
width: 438px;
height: 547px;
clip-path: polygon(0% 1%, 0% 97%, 100% 100%, 100% 0%);
@media screen and (max-width: $m-grid-min-vp) {
width: 100vw;
height: 100vw;
clip-path: polygon(0% 2%, 0% 97%, 100% 100%, 100% 0%);
}
}
span {
display: inline-block;
position: relative;
// Deco
&::before {
content: '';
display: block;
position: absolute;
width: 393px;
height: 193px;
bottom: 0;
right: 0;
transform: translate(45px, 32px);
background: url('<%= APP_CDN %>/assets/marketing/deco_1.svg')
no-repeat;
z-index: -1;
@media screen and (max-width: $m-grid-min-vp) {
content: initial;
display: none;
}
}
&::after {
content: '';
display: block;
position: absolute;
top: 0;
right: 0;
width: 284px;
height: 262px;
transform: translate(35px, -35px);
background: url('<%= APP_CDN %>/assets/marketing/deco_2-straight.svg')
no-repeat;
z-index: -1;
@media screen and (max-width: $m-grid-min-vp) {
content: initial;
display: none;
}
}
}
}
}
}
@import '../../../../foundation/grid-values';
.m-marketing__main,
.m-marketing__section {
&.m-marketing__section--style-3 {
margin-bottom: 100px;
@media screen and (max-width: $m-grid-min-vp) {
margin-bottom: 80px;
}
.m-marketing__wrapper {
position: relative;
z-index: 0;
padding: 80px 0 80px;
@media screen and (max-width: $m-grid-min-vp) {
padding: 0;
}
}
.m-marketing__body {
position: relative;
margin: auto 0;
padding: 0;
min-height: 330px;
@media screen and (max-width: $m-grid-min-vp) {
padding: 0 30px 0;
min-height: 0;
}
&::before {
content: '';
position: absolute;
top: 0;
right: -360px;
left: 0;
bottom: auto;
height: calc(100% + 160px);
transform: translate(-230px, -80px);
clip-path: polygon(0% 6%, 0% 95%, 100% 100%, 100% 0%);
z-index: -1;
@include m-theme() {
background: linear-gradient(
180deg,
themed($m-marketing-bg-colored-gradient-start) 0%,
themed($m-marketing-bg-colored-gradient-end) 99.99%
);
}
@media screen and (max-width: $m-grid-min-vp) {
content: initial;
display: none;
}
}
h2 {
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
font-size: 28px;
line-height: 32px;
margin: 20px 0 17px;
text-align: center;
}
}
}
p.m-marketing__description {
margin-bottom: 42px;
padding-right: 200px;
@include m-theme() {
color: themed($m-grey-300);
}
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
padding-right: 0;
margin-bottom: 30px;
font-size: 16px;
line-height: 23px;
text-align: center;
}
}
ul.m-marketing__points {
@include m-theme() {
color: themed($m-grey-300);
}
@include m-on-theme(dark) {
color: #ffffff;
}
> li em {
font-style: normal;
@include m-theme() {
color: themed($m-black);
}
@include m-on-theme(dark) {
color: #ffffff;
font-weight: bold;
}
}
}
.m-marketing__image {
position: relative;
z-index: 0;
grid-column-start: 1;
grid-row: 1;
img.m-marketing__image--1 {
object-fit: cover;
width: 438px;
height: 518px;
clip-path: polygon(0% 1%, 0% 100%, 100% 96%, 100% 0%);
@media screen and (max-width: $m-grid-min-vp) {
width: 100vw;
height: 100vw;
clip-path: polygon(0% 1%, 0% 100%, 100% 97%, 100% 0%);
}
}
img.m-marketing__image--2 {
object-fit: contain;
width: 358px;
height: 191px;
position: absolute;
right: 0;
bottom: 35px;
transform: translate(15px, 0);
@media screen and (max-width: $m-grid-min-vp) {
right: auto;
left: 50%;
bottom: 0;
transform: translate(-50%, 50%);
width: 85vw;
height: 45.35vw;
}
}
span {
display: inline-block;
position: relative;
width: 100%;
@media screen and (max-width: $m-grid-min-vp) {
margin-bottom: calc(
20vw + 40px
); // A little bit less than half UX image + normal margin
&.m-marketing__image--noUxSample {
margin-bottom: 40px;
}
}
// Deco
&::after {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
width: 191px;
height: 191px;
transform: translate(-60px, -58px);
background: url('<%= APP_CDN %>/assets/marketing/deco_3.svg')
no-repeat;
z-index: -1;
@media screen and (max-width: $m-grid-min-vp) {
content: initial;
display: none;
}
}
}
}
}
}
@import '../../../../foundation/grid-values';
.m-marketing__main,
.m-marketing__section {
&.m-marketing__section--style-4 {
margin-bottom: 100px;
@media screen and (max-width: $m-grid-min-vp) {
margin-bottom: 80px;
}
.m-marketing__wrapper {
position: relative;
z-index: 0;
@media screen and (max-width: $m-grid-min-vp) {
padding: 0;
}
}
.m-marketing__body {
position: relative;
margin: auto 0;
padding: 0;
min-height: 360px;
@media screen and (max-width: $m-grid-min-vp) {
padding: 0 30px 0;
min-height: 0;
}
h2 {
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
font-size: 28px;
line-height: 32px;
margin: 20px 0 17px;
text-align: center;
}
}
}
p.m-marketing__description {
margin-bottom: 42px;
padding-right: 200px;
@include m-theme() {
color: themed($m-grey-300);
}
@include m-on-theme(dark) {
color: #ffffff;
}
@media screen and (max-width: $m-grid-min-vp) {
padding-right: 0;
margin-bottom: 30px;
font-size: 16px;
line-height: 23px;
text-align: center;
}
}
ul.m-marketing__points {
@include m-theme() {
color: themed($m-grey-300);
}
@include m-on-theme(dark) {
color: #ffffff;
}
> li em {
font-style: normal;
@include m-theme() {
color: themed($m-black);
}
@include m-on-theme(dark) {
color: #ffffff;
font-weight: bold;
}
}
}
.m-marketing__image {
position: relative;
z-index: 0;
@media screen and (max-width: $m-grid-min-vp) {
grid-row: 1;
}
img.m-marketing__image--1 {
object-fit: cover;
width: 438px;
height: 547px;
clip-path: polygon(0% 1%, 0% 96%, 100% 100%, 100% 0%);
@media screen and (max-width: $m-grid-min-vp) {
width: 100vw;
height: 100vw;
clip-path: polygon(0% 1%, 0% 97%, 100% 100%, 100% 0%);
}
}
img.m-marketing__image--2 {
object-fit: contain;
width: 358px;
height: 191px;
position: absolute;
left: 0;
bottom: 35px;
transform: translate(-15px, 0);
@media screen and (max-width: $m-grid-min-vp) {
right: auto;
left: 50%;
bottom: 0;
transform: translate(-50%, 50%);
width: 85vw;
height: 45.35vw;
}
}
span {
display: inline-block;
position: relative;
width: 100%;
text-align: right;
@media screen and (max-width: $m-grid-min-vp) {
margin-bottom: calc(
20vw + 40px
); // A little bit less than half UX image + normal margin
&.m-marketing__image--noUxSample {
margin-bottom: 40px;
}
}
// Deco
&::after {
content: '';
display: block;
position: absolute;
right: 0;
bottom: 0;
width: 169px;
height: 169px;
transform: translate(50px, 50px);
background: url('<%= APP_CDN %>/assets/marketing/deco_4.svg')
no-repeat;
z-index: -1;
@media screen and (max-width: $m-grid-min-vp) {
content: initial;
display: none;
}
}
}
}
}
}
@import '../../../../foundation/grid-values';
.m-marketing__main,
.m-marketing__section {
&.m-marketing__section--tail {
h2 {
font-size: 24px;
line-height: 32px;
font-weight: 700;
margin: 0 0 70px;
@include m-theme() {
color: themed($m-grey-300);
}
@media screen and (max-width: $m-grid-min-vp) {
margin: 0 0 35px;
}
}
.m-marketing__body {
position: relative;
text-align: center;
padding: 0 36px;
h3 {
margin: 0 0 24px;
font-size: 24px;
line-height: 32px;
font-weight: 900;
@media screen and (max-width: $m-grid-min-vp) {
margin: 0 0 12px;
}
}
p.m-marketing__description {
font-size: 16px;
line-height: 21px;
@include m-theme() {
color: themed($m-grey-300);
}
em {
font-style: normal;
@include m-theme() {
color: themed($m-black);
}
@include m-on-theme(dark) {
color: #ffffff;
font-weight: bold;
}
}
}
&.m-marketing__body--extra {
margin-top: 135px;
@media screen and (max-width: $m-grid-min-vp) {
margin-top: 0;
}
}
}
.m-marketingSectionTail__table {
.m-marketing__body {
@media screen and (max-width: $m-grid-min-vp) {
margin-bottom: 45px;
&:last-child {
margin-bottom: 55px;
}
}
}
> *::after {
content: '';
display: block;
position: absolute;
width: 1px;
height: 100%;
top: 0;
right: 0;
transform: translateX(($m-grid-gap / 2) + 1px);
@include m-theme() {
background: linear-gradient(
to bottom,
rgba(themed($m-grey-100), 0) 0%,
rgba(themed($m-grey-100), 0) 25%,
rgba(themed($m-grey-100), 0.9) 50%,
rgba(themed($m-grey-100), 0) 75%,
rgba(themed($m-grey-100), 0) 100%
);
}
@media screen and (max-width: $m-grid-min-vp) {
content: initial;
display: initial;
}
}
> *:last-child::after {
content: initial;
display: initial;
}
}
}
}
<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));
}
}
import { TestBed } from '@angular/core/testing';
import { TagsPipe } from './tags';
import { FeaturesService } from '../../services/features.service';
import { MockService } from '../../utils/mock';
import { SiteService } from '../services/site.service';
describe('TagPipe', () => {
let featuresServiceMock: any = MockService(FeaturesService, {
const featuresServiceMock: any = MockService(FeaturesService, {
has: feature => {
return true;
},
});
const siteServiceMock: any = MockService(SiteService, {
props: {
isProDomain: { get: () => false },
pro: { get: () => false },
},
});
let pipe;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TagsPipe],
providers: [
{
provide: FeaturesService,
useValue: featuresServiceMock,
},
],
});
pipe = new TagsPipe(featuresServiceMock, siteServiceMock);
});
it('should transform when # in the middle ', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring#name';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain(
......@@ -32,7 +32,6 @@ describe('TagPipe', () => {
});
it('should transform when # preceded by space ', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring #name';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain(
......@@ -41,7 +40,6 @@ describe('TagPipe', () => {
});
it('should transform when # preceded by [] ', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring [#name';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain(
......@@ -50,7 +48,6 @@ describe('TagPipe', () => {
});
it('should transform when # preceded by () ', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring (#name)';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain(
......@@ -59,7 +56,6 @@ describe('TagPipe', () => {
});
it('should transform uppercase text following # to lower case ', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textString #NaMe';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain(
......@@ -68,7 +64,6 @@ describe('TagPipe', () => {
});
it('should correctly parse when duplicates substrings present', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = '#hash #hashlonger';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain(
......@@ -80,28 +75,24 @@ describe('TagPipe', () => {
});
it('should transform when @ preceded by () ', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring (@name';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a class="tag"');
});
it('should transform when @ preceded by [] ', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring [@name';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a class="tag"');
});
it('should transform when @ preceded by space', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring @name';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a class="tag"');
});
it('should transform when @ followed by `.com`', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring @name.com';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a class="tag"');
......@@ -109,7 +100,6 @@ describe('TagPipe', () => {
});
it('should transform two adjacent tags', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = '@test1 @test2';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toEqual(
......@@ -118,7 +108,6 @@ describe('TagPipe', () => {
});
it('should transform many adjacent tags', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string =
'@test1 @test2 @test3 @test4 @test5 @test6 @test7 @test8 @test9 @test10 @test11 @test12 @test13 @test14 @test15';
const transformedString = pipe.transform(<any>string);
......@@ -135,14 +124,12 @@ describe('TagPipe', () => {
});
it('should transform to an email', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring@name.com';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a href="mailto:textstring@name.com"');
});
it('should not transform when @ not present', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring name';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toEqual(string);
......@@ -150,35 +137,30 @@ describe('TagPipe', () => {
});
it('should transform url http', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring http://minds.com/';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a href="http://minds.com/');
});
it('should transform url with https', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring https://minds.com/';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a href="https://minds.com/');
});
it('should transform url with ftp', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring ftp://minds.com/';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a href="ftp://minds.com/');
});
it('should transform url with file', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'textstring file://minds.com/';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain('<a href="file://minds.com/');
});
it('should transform url with a hashtag', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'text http://minds.com/#position';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain(
......@@ -187,7 +169,6 @@ describe('TagPipe', () => {
});
it('should transform url with a hashtag and @', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = 'text http://minds.com/#position@some';
const transformedString = pipe.transform(<any>string);
expect(transformedString).toContain(
......@@ -196,7 +177,6 @@ describe('TagPipe', () => {
});
it('should transform many tags', () => {
const pipe = new TagsPipe(featuresServiceMock);
const string = `text http://minds.com/#position@some @name
@name1 #hash1#hash2 #hash3 ftp://s.com name@mail.com
`;
......
import { Pipe, Inject, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { FeaturesService } from '../../services/features.service';
import { SiteService } from '../services/site.service';
@Pipe({
name: 'tags',
......@@ -31,7 +32,11 @@ export class TagsPipe implements PipeTransform {
hash: {
rule: /(^|\s||)#(\w+)/gim,
replace: m => {
if (this.featureService.has('top-feeds')) {
if (this.siteService.isProDomain) {
return `${
m.match[1]
}<a href="/all;query=${m.match[2].toLowerCase()}">#${m.match[2]}</a>`;
} else if (this.featureService.has('top-feeds')) {
return `${
m.match[1]
}<a href="/newsfeed/global/top;hashtag=${m.match[2].toLowerCase()};period=24h">#${
......@@ -49,7 +54,10 @@ export class TagsPipe implements PipeTransform {
},
};
constructor(private featureService: FeaturesService) {}
constructor(
private featureService: FeaturesService,
private siteService: SiteService
) {}
/**
* Push a match to results array
......
......@@ -184,36 +184,14 @@ export class AdminBoosts {
}
onKeyPress(e: KeyboardEvent) {
if (this.reasonModalOpened || e.ctrlKey || e.altKey || e.shiftKey) {
//If an input is focused, disregard.
if (document.activeElement.tagName === 'INPUT') {
return;
}
e.stopPropagation();
// numbers
switch (e.key.toLowerCase()) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '0':
const keyValue = Number.parseInt(e.key);
this.boosts[0].quality = keyValue > 0 ? keyValue * 10 : 100;
break;
case 'arrowleft':
return this.accept();
case 'arrowright':
return this.openReasonsModal();
case 'e':
//mark as nsfw and reject
this.eTag(this.boosts[0]);
break;
case 'n':
//mark as nsfw and accept
this.accept(this.boosts[0], true);
......@@ -221,9 +199,6 @@ export class AdminBoosts {
case 'a':
this.accept();
break;
case 'r':
this.openReasonsModal();
break;
}
}
......
......@@ -17,12 +17,36 @@
appearance: none;
text-decoration: none;
> * {
vertical-align: middle;
margin-right: 0.35em; // A space
&:last-child {
margin-right: initial;
}
}
i.material-icons {
line-height: 1em;
font-size: 20px;
height: auto;
color: inherit;
opacity: 0.4;
}
@include m-theme() {
background: themed($m-blue);
color: themed($m-white-always);
border-color: themed($m-blue);
}
&.mf-button--smaller {
font-size: 15px;
line-height: 21px;
padding: 10px 20px;
border-radius: 3px;
}
&.mf-button--alt {
@include m-theme() {
background: themed($m-aqua);
......@@ -55,12 +79,20 @@
&.mf-button--hollow {
@include m-theme() {
background: themed($m-white);
background: transparent; // Within theme because of cascading
color: themed($m-black);
border-color: themed($m-blue);
}
}
&.mf-button--hollow-mono {
@include m-theme() {
background: transparent; // Within theme because of cascading
color: themed($m-black);
border-color: themed($m-grey-200);
}
}
&[disabled] {
cursor: default;
opacity: 0.6;
......
......@@ -85,7 +85,6 @@ export interface MindsUser {
pro_published?: boolean;
pro_settings?: {
logo_image: string;
logo_guid: string;
tag_list?: Tag[];
background_image: string;
title: string;
......@@ -97,6 +96,9 @@ export interface MindsUser {
featured_content?: Array<string>;
tile_ratio?: string;
styles?: { [key: string]: string };
domain: string;
has_custom_logo?: boolean;
has_custom_background?: boolean;
};
mode: ChannelMode;
}
......
......@@ -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;
}
}
......@@ -27,8 +27,9 @@ export class AnalyticsFiltersComponent implements OnInit, OnDestroy {
ngOnInit() {
// TODO: remove all of this once channel search is ready
// Temporarily remove channel search from channel filter options
this.analyticsService.filters$.subscribe(filters => {
this.subscription = this.analyticsService.filters$.subscribe(filters => {
this.filters = filters;
const channelFilter = filters.find(filter => filter.id === 'channel');
channelFilter.options = channelFilter.options.filter(option => {
......@@ -48,5 +49,7 @@ export class AnalyticsFiltersComponent implements OnInit, OnDestroy {
this.cd.detectChanges();
}
ngOnDestroy() {}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
<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>
......@@ -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) {
......
This diff is collapsed.
<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
......
<p>
analytics__layout--table works!
</p>
<div class="m-blockchain--marketing--blogs">
<m-blog--tile *ngFor="let blog of blogs" [entity]="blog"> </m-blog--tile>
</div>
.m-blockchain--marketing--modal {
padding: 36px 16px 16px;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.