Skip to content
Commits on Source (46)
### Summary
(What is the Merge request intending to do, in plain language)
(Be sure to associate any related issues or merge requests)
### Steps to test
(Steps to demonstrate merge achieves goal)
(Include any platform specific directions)
### Estimated Regression Scope
(What features do these changes effect in your estimation?)
...@@ -3,32 +3,38 @@ ...@@ -3,32 +3,38 @@
* @desc E2E testing for Minds Pro's pages. * @desc E2E testing for Minds Pro's pages.
*/ */
context('Pro Page', () => { context('Pro Page', () => {
if (Cypress.env().pro_password) { // required to run tests against pro user only. if (Cypress.env().pro_password) {
// required to run tests against pro user only.
const topBar = '.m-proChannel__topbar'; const topBar = '.m-proChannel__topbar';
let categories = [ let categories = [
{ label: 'Technology', tag: '#technology' }, { label: 'Technology', tag: '#technology' },
{ label: 'Food', tag: '#food' }, { label: 'Food', tag: '#food' },
{ label: 'News', tag: '#news' } { label: 'News', tag: '#news' },
]; ];
let footerLinks = [ let footerLinks = [
{ label: 'Minds', link: 'https://www.minds.com/' }, { label: 'Minds', link: 'https://www.minds.com/' },
{ label: 'Careers', link: 'https://www.minds.com/careers' }, { label: 'Careers', link: 'https://www.minds.com/careers' },
]; ];
const proButton = 'data-minds-sidebar-admin-pro-button'; const proButton = 'data-minds-sidebar-admin-pro-button';
function resetSettings() { function resetSettings() {
cy.visit(`/pro/settings`); cy.visit(`/pro/settings`);
cy.route("POST", "**/api/v2/pro/settings").as("settings"); cy.route('POST', '**/api/v2/pro/settings').as('settings');
cy.get('#title').focus().clear().type('Title'); cy.get('#title')
cy.get('#headline').focus().clear().type('This is a headline'); .focus()
.clear()
.type('Title');
cy.get('#headline')
.focus()
.clear()
.type('This is a headline');
cy.contains('Hashtags') cy.contains('Hashtags').click();
.click();
// remove all hashtags // remove all hashtags
removeInputs(); removeInputs();
...@@ -37,8 +43,7 @@ context('Pro Page', () => { ...@@ -37,8 +43,7 @@ context('Pro Page', () => {
let cat = categories[i]; let cat = categories[i];
addTag(cat.label, cat.tag, i); addTag(cat.label, cat.tag, i);
} }
cy.contains('Footer') cy.contains('Footer').click();
.click();
cy.get('#footer_text') cy.get('#footer_text')
.clear() .clear()
...@@ -54,30 +59,36 @@ context('Pro Page', () => { ...@@ -54,30 +59,36 @@ context('Pro Page', () => {
cy.contains('Save') cy.contains('Save')
.click() .click()
.wait('@settings').then((xhr) => { .wait('@settings')
.then(xhr => {
expect(xhr.status).to.equal(200); expect(xhr.status).to.equal(200);
expect(xhr.response.body).to.deep.equal({ status: 'success' }); expect(xhr.response.body).to.deep.equal({ status: 'success' });
} });
);
} }
function removeInputs() { function removeInputs() {
cy.get('.m-draggableList__list .m-proSettings__field .m-proSettings__flexInputs').should('be.visible').within($el => { cy.get(
for (let i = $el.length - 1; i >= 0; i--) { // flexInput. Start from the last one '.m-draggableList__list .m-proSettings__field .m-proSettings__dragDropRow--input'
let c = $el[i]; )
for (let j = 0; j < c.children.length; j++) { // inputs and the X button .should('be.visible')
let cc = c.children[j]; .within($el => {
if (cc.nodeName === 'I') { // if it's the X button, click on it for (let i = $el.length - 1; i >= 0; i--) {
cy.wrap(cc).click(); // flexInput. Start from the last one
let c = $el[i];
for (let j = 0; j < c.children.length; j++) {
// inputs and the X button
let cc = c.children[j];
if (cc.nodeName === 'I') {
// if it's the X button, click on it
cy.wrap(cc).click();
}
} }
} }
} });
});
} }
function addTag(label, tag, index) { function addTag(label, tag, index) {
cy.contains('+ Add Tag') cy.contains('+ Add Tag').click();
.click();
cy.get(`#tag-label-${index}`) cy.get(`#tag-label-${index}`)
.clear() .clear()
...@@ -89,8 +100,7 @@ context('Pro Page', () => { ...@@ -89,8 +100,7 @@ context('Pro Page', () => {
} }
function addFooterLink(label, link, index) { function addFooterLink(label, link, index) {
cy.contains('Add Link') cy.contains('Add Link').click();
.click();
cy.get(`#footer_link-title-${index}`) cy.get(`#footer_link-title-${index}`)
.clear() .clear()
...@@ -116,42 +126,51 @@ context('Pro Page', () => { ...@@ -116,42 +126,51 @@ context('Pro Page', () => {
}); });
it('should load the feed tab', () => { it('should load the feed tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/activities/top**").as("activities"); cy.route('GET', '**/api/v2/pro/content/*/activities/top**').as(
'activities'
);
cy.contains('Feed') cy.contains('Feed')
.click() .click()
.wait('@activities').then((xhr) => { .wait('@activities')
expect(xhr.status).to.equal(200); .then(xhr => {
}); expect(xhr.status).to.equal(200);
}) });
});
it('should load the videos tab', () => { it('should load the videos tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/videos/top**").as("videos"); cy.route('GET', '**/api/v2/pro/content/*/videos/top**').as('videos');
cy.contains('Videos') cy.contains('Videos')
.click() .click()
.wait('@videos').then((xhr) => { .wait('@videos')
expect(xhr.status).to.equal(200); .then(xhr => {
}); expect(xhr.status).to.equal(200);
}) });
});
it('should load the images tab', () => { it('should load the images tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/images/top**").as("images"); cy.route('GET', '**/api/v2/pro/content/*/images/top**').as('images');
cy.contains('Images') cy.contains('Images')
.click() .click()
.wait('@images').then((xhr) => { .wait('@images')
expect(xhr.status).to.equal(200); .then(xhr => {
}); expect(xhr.status).to.equal(200);
});
// should have sub-categories // should have sub-categories
cy.get('m-pro--channel--categories > .m-proChannel__category').each(($el, $index) => { cy.get('m-pro--channel--categories > .m-proChannel__category').each(
let c = categories.slice(0); ($el, $index) => {
c.unshift({ label: 'All', tag: '#all' }); let c = categories.slice(0);
expect($el.text()).to.contain(c[$index].label); c.unshift({ label: 'All', tag: '#all' });
}); expect($el.text()).to.contain(c[$index].label);
}
);
cy.get('m-pro--channel .m-overlay-modal').should('not.be.visible'); cy.get('m-pro--channel .m-overlay-modal').should('not.be.visible');
// click on tile // click on tile
cy.get('.m-proChannelListContent__list li:first-child m-pro--channel-tile').click(); cy.get(
'.m-proChannelListContent__list li:first-child m-pro--channel-tile'
).click();
cy.wait(200); cy.wait(200);
// media modal should appear // media modal should appear
...@@ -159,35 +178,41 @@ context('Pro Page', () => { ...@@ -159,35 +178,41 @@ context('Pro Page', () => {
// close media modal // close media modal
cy.get('m-pro--channel .m-overlay-modal--close').click(); cy.get('m-pro--channel .m-overlay-modal--close').click();
}) });
it('should load the articles tab', () => { it('should load the articles tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/blogs/top**").as("blogs"); cy.route('GET', '**/api/v2/pro/content/*/blogs/top**').as('blogs');
cy.contains('Articles') cy.contains('Articles')
.click() .click()
.wait('@blogs').then((xhr) => { .wait('@blogs')
expect(xhr.status).to.equal(200); .then(xhr => {
}); expect(xhr.status).to.equal(200);
}) });
});
it('should load the groups tab', () => { it('should load the groups tab', () => {
cy.route("GET", "**/api/v2/pro/content/*/groups/top**").as("groups"); cy.route('GET', '**/api/v2/pro/content/*/groups/top**').as('groups');
cy.contains('Groups') cy.contains('Groups')
.click() .click()
.wait('@groups').then((xhr) => { .wait('@groups')
expect(xhr.status).to.equal(200); .then(xhr => {
}); expect(xhr.status).to.equal(200);
}) });
});
it('should have a footer', () => { it('should have a footer', () => {
// should have a footer text // should have a footer text
cy.get('.m-proChannelFooter__text').contains('This is the footer text'); cy.get('.m-proChannelFooter__text').contains('This is the footer text');
// should have footer links // should have footer links
cy.get('.m-proChannel__footer .m-proChannelFooter .m-proChannelFooter__link').should('be.visible').each(($el, $index) => { cy.get(
expect($el.text()).to.contain(footerLinks[$index].label); '.m-proChannel__footer .m-proChannelFooter .m-proChannelFooter__link'
expect($el.attr('href')).to.contain(footerLinks[$index].link); )
}); .should('be.visible')
}) .each(($el, $index) => {
expect($el.text()).to.contain(footerLinks[$index].label);
expect($el.attr('href')).to.contain(footerLinks[$index].link);
});
});
} }
}) });
...@@ -3,30 +3,31 @@ ...@@ -3,30 +3,31 @@
* @desc E2E testing for Minds Pro's settings. * @desc E2E testing for Minds Pro's settings.
*/ */
context('Pro Settings', () => { context('Pro Settings', () => {
if (Cypress.env().pro_password) { // required to run tests against pro user only. if (Cypress.env().pro_password) {
// required to run tests against pro user only.
const title = '#title'; const title = '#title';
const headline = '#headline'; const headline = '#headline';
const previewButton = '.m-proSettings__previewBtn';
const activityContainer = 'minds-activity'; const activityContainer = 'minds-activity';
const footerText = '#footer_text'; const footerText = '#footer_text';
const theme = { const theme = {
primaryColor: '#primary_color', textColor: '#textColor',
plainBackgroundColor: '#plain_background_color', primaryColor: '#primaryColor',
plainBackgroundColor: '#plainBgColor',
schemeLight: '#scheme_light', schemeLight: '#scheme_light',
schemeDark: '#scheme_dark', schemeDark: '#scheme_dark',
aspectRatio: { aspectRatio: {
169: '#tile_ratio_16\:9', // 16:9 169: '#tile_ratio_16:9', // 16:9
1610: '#tile_ratio_16\:10', // 16:10 1610: '#tile_ratio_16:10', // 16:10
43: '#tile_ratio_4\:3', // 4:3 43: '#tile_ratio_4:3', // 4:3
11: '#tile_ratio_1\:1' , // 1:1 11: '#tile_ratio_1:1', // 1:1
}, },
} };
const hashtags = { const hashtags = {
labelInput0: '#tag-label-0', labelInput0: '#tag-label-0',
hashtagInput0: '#tag-tag-0', hashtagInput0: '#tag-tag-0',
labelInput1: '#tag-label-1', labelInput1: '#tag-label-1',
hashtagInput1: '#tag-tag-1', hashtagInput1: '#tag-tag-1',
label1: 'label1', label1: 'label1',
label2: 'label2', label2: 'label2',
...@@ -34,40 +35,47 @@ context('Pro Settings', () => { ...@@ -34,40 +35,47 @@ context('Pro Settings', () => {
hashtag1: '#hashtag1', hashtag1: '#hashtag1',
hashtag2: '#hashtag2', hashtag2: '#hashtag2',
hashtag3: '#hashtag3', hashtag3: '#hashtag3',
} };
const footer = { const footer = {
hrefInput: `#footer_link-href-0`, hrefInput: `#footer_link-href-0`,
titleInput: `#footer_link-title-0`, titleInput: `#footer_link-title-0`,
} };
const strings = { const strings = {
title: "Minds Pro E2E", title: 'Minds Pro E2E',
headline: "This headline is a test", headline: 'This headline is a test',
footer: "This is a footer", footer: 'This is a footer',
footerTitle: "Minds", footerTitle: 'Minds',
footerHref: 'https://www.minds.com/', footerHref: 'https://www.minds.com/',
} };
before(() => { before(() => {
cy.login(true, Cypress.env().pro_username, Cypress.env().pro_password); cy.login(true, Cypress.env().pro_username, Cypress.env().pro_password);
}); });
after(() => { after(() => {
cy.visit("/pro/settings") // cy.visit(`/${Cypress.env().username}`);
cy.visit('/pro/' + Cypress.env().pro_username + '/settings/hashtags')
.location('pathname') .location('pathname')
.should('eq', '/pro/settings'); .should(
'eq',
'/pro/' + Cypress.env().pro_username + '/settings/hashtags'
);
clearHashtags(); clearHashtags();
}); });
beforeEach(()=> { beforeEach(() => {
cy.preserveCookies(); cy.preserveCookies();
cy.server(); cy.server();
cy.route("POST", "**/api/v2/pro/settings").as("settings"); cy.route('POST', '**/api/v2/pro/settings').as('settings');
cy.visit("/pro/settings") cy.visit('/pro/' + Cypress.env().pro_username + '/settings/general')
.location('pathname') .location('pathname')
.should('eq', '/pro/settings'); .should(
'eq',
'/pro/' + Cypress.env().pro_username + '/settings/general'
);
}); });
it('should update the title and headline', () => { it('should update the title and headline', () => {
...@@ -84,40 +92,36 @@ context('Pro Settings', () => { ...@@ -84,40 +92,36 @@ context('Pro Settings', () => {
saveAndPreview(); saveAndPreview();
//check tab title. //check tab title.
cy.title() cy.title().should(
.should('eq', strings.title+' - '+strings.headline+" | Minds"); 'eq',
strings.title + ' - ' + strings.headline + ' | Minds'
);
}); });
// Need to find a way around the color input in Cypress. // Need to find a way around the color input in Cypress.
it('should allow the user to set a dark theme for posts', () => { it('should allow the user to set a dark theme for posts', () => {
cy.contains('Theme') cy.contains('Theme').click();
.click();
cy.get(theme.schemeDark) cy.get(theme.schemeDark).click();
.click();
saveAndPreview(); saveAndPreview();
cy.contains('Feed') cy.contains('Feed').click();
.click();
cy.get(activityContainer) cy.get(activityContainer)
.should('have.css', 'background-color') .should('have.css', 'background-color')
.and('eq', 'rgb(35, 35, 35)'); .and('eq', 'rgb(35, 35, 35)');
}); });
it('should allow the user to set a light theme for posts', () => { it('should allow the user to set a light theme for posts', () => {
cy.contains('Theme') cy.contains('Theme').click();
.click();
cy.get(theme.schemeLight) cy.get(theme.schemeLight).click();
.click();
saveAndPreview(); saveAndPreview();
cy.contains('Feed') cy.contains('Feed').click();
.click();
cy.get(activityContainer) cy.get(activityContainer)
.should('have.css', 'background-color') .should('have.css', 'background-color')
...@@ -125,12 +129,10 @@ context('Pro Settings', () => { ...@@ -125,12 +129,10 @@ context('Pro Settings', () => {
}); });
it('should allow the user to set category hashtags', () => { it('should allow the user to set category hashtags', () => {
cy.contains('Hashtags') cy.contains('Hashtags').click();
.click();
cy.contains('Add').click();
cy.contains('+ Add Tag')
.click();
cy.get(hashtags.labelInput0) cy.get(hashtags.labelInput0)
.clear() .clear()
.type(hashtags.label1); .type(hashtags.label1);
...@@ -138,10 +140,9 @@ context('Pro Settings', () => { ...@@ -138,10 +140,9 @@ context('Pro Settings', () => {
cy.get(hashtags.hashtagInput0) cy.get(hashtags.hashtagInput0)
.clear() .clear()
.type(hashtags.hashtag1); .type(hashtags.hashtag1);
cy.contains('+ Add Tag') cy.contains('Add').click();
.click();
cy.get(hashtags.labelInput1) cy.get(hashtags.labelInput1)
.first() .first()
.clear() .clear()
...@@ -151,7 +152,7 @@ context('Pro Settings', () => { ...@@ -151,7 +152,7 @@ context('Pro Settings', () => {
.first() .first()
.clear() .clear()
.type(hashtags.hashtag2); .type(hashtags.hashtag2);
saveAndPreview(); saveAndPreview();
//check the labels are present and clickable. //check the labels are present and clickable.
...@@ -160,20 +161,18 @@ context('Pro Settings', () => { ...@@ -160,20 +161,18 @@ context('Pro Settings', () => {
}); });
it('should allow the user to set footer', () => { it('should allow the user to set footer', () => {
cy.contains('Footer') cy.contains('Footer').click();
.click();
cy.get(footerText) cy.get(footerText)
.clear() .clear()
.type(strings.footer); .type(strings.footer);
cy.contains('Add Link') cy.contains('Add Link').click();
.click();
cy.get(footer.hrefInput) cy.get(footer.hrefInput)
.clear() .clear()
.type(strings.footerHref); .type(strings.footerHref);
cy.get(footer.titleInput) cy.get(footer.titleInput)
.clear() .clear()
.type(strings.footerTitle); .type(strings.footerTitle);
...@@ -189,50 +188,45 @@ context('Pro Settings', () => { ...@@ -189,50 +188,45 @@ context('Pro Settings', () => {
function saveAndPreview() { function saveAndPreview() {
//save and await response //save and await response
cy.contains('Save') cy.contains('Save')
.click() .click()
.wait('@settings').then((xhr) => { .wait('@settings')
.then(xhr => {
expect(xhr.status).to.equal(200); expect(xhr.status).to.equal(200);
expect(xhr.response.body).to.deep.equal({ status: 'success' }); expect(xhr.response.body).to.deep.equal({ status: 'success' });
}); });
//go to pro page //go to pro page
cy.get(previewButton) cy.contains('View Pro Channel').click();
.click();
} }
function clearHashtags() { function clearHashtags() {
cy.contains('Hashtags') cy.contains('Hashtags').click();
.click();
cy.contains('Add').click();
cy.contains('+ Add Tag')
.click(); cy.contains('clear').click({ multiple: true });
saveAndPreview();
cy.contains('clear')
.click({multiple: true});
saveAndPreview();
} }
// //
// it.only('should update the theme', () => { // it.only('should update the theme', () => {
// // nav to theme tab // // nav to theme tab
// cy.contains('Theme') // cy.contains('Theme')
// .click(); // .click();
// cy.get(theme.plainBackgroundColor).then(elem => { // cy.get(theme.plainBackgroundColor).then(elem => {
// elem.val('#00dd00'); // elem.val('#00dd00');
// //save and await response // //save and await response
// cy.contains('Save') // cy.contains('Save')
// .click() // .click()
// .wait('@settings').then((xhr) => { // .wait('@settings').then((xhr) => {
// expect(xhr.status).to.equal(200); // expect(xhr.status).to.equal(200);
// expect(xhr.response.body).to.deep.equal({ status: 'success' }); // expect(xhr.response.body).to.deep.equal({ status: 'success' });
// }); // });
// //go to pro page // //go to pro page
// cy.get(previewButton) // cy.contains('View Pro Channel').click();
// .click();
// });
// }) // })
} }
}) });
...@@ -1985,6 +1985,12 @@ ...@@ -1985,6 +1985,12 @@
"integrity": "sha512-4GbNCDs98uHCT/OMv40qQC/OpoPbYn9XdXeTiFwHBBFO6eJhYEPUu2zDKirXSbHlvDV8oZ9l8EQ+HrEx/YS9DQ==", "integrity": "sha512-4GbNCDs98uHCT/OMv40qQC/OpoPbYn9XdXeTiFwHBBFO6eJhYEPUu2zDKirXSbHlvDV8oZ9l8EQ+HrEx/YS9DQ==",
"dev": true "dev": true
}, },
"@types/sizzle": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
"integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
"dev": true
},
"@types/source-list-map": { "@types/source-list-map": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
...@@ -5308,13 +5314,14 @@ ...@@ -5308,13 +5314,14 @@
"dev": true "dev": true
}, },
"cypress": { "cypress": {
"version": "3.4.1", "version": "3.6.1",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-3.4.1.tgz", "resolved": "https://registry.npmjs.org/cypress/-/cypress-3.6.1.tgz",
"integrity": "sha512-1HBS7t9XXzkt6QHbwfirWYty8vzxNMawGj1yI+Fu6C3/VZJ8UtUngMW6layqwYZzLTZV8tiDpdCNBypn78V4Dg==", "integrity": "sha512-6n0oqENdz/oQ7EJ6IgESNb2M7Bo/70qX9jSJsAziJTC3kICfEMmJUlrAnP9bn+ut24MlXQST5nRXhUP5nRIx6A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@cypress/listr-verbose-renderer": "0.4.1", "@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/xvfb": "1.2.4", "@cypress/xvfb": "1.2.4",
"@types/sizzle": "2.3.2",
"arch": "2.1.1", "arch": "2.1.1",
"bluebird": "3.5.0", "bluebird": "3.5.0",
"cachedir": "1.3.0", "cachedir": "1.3.0",
...@@ -5341,6 +5348,7 @@ ...@@ -5341,6 +5348,7 @@
"request-progress": "3.0.0", "request-progress": "3.0.0",
"supports-color": "5.5.0", "supports-color": "5.5.0",
"tmp": "0.1.0", "tmp": "0.1.0",
"untildify": "3.0.3",
"url": "0.11.0", "url": "0.11.0",
"yauzl": "2.10.0" "yauzl": "2.10.0"
}, },
...@@ -19068,6 +19076,12 @@ ...@@ -19068,6 +19076,12 @@
} }
} }
}, },
"untildify": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz",
"integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==",
"dev": true
},
"upath": { "upath": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
......
<ng-container *ngIf="!isProDomain"> <ng-container *ngIf="ready">
<m-v2-topbar *mIfFeature="'top-feeds'; else legacyTopbar"> <ng-container *ngIf="!isProDomain">
<ng-container search> <m-v2-topbar *mIfFeature="'top-feeds'; else legacyTopbar">
<m-search--bar [defaultSizes]="false"></m-search--bar>
</ng-container>
<ng-container icons>
<m-notifications--topbar-toggle
*ngIf="session.isLoggedIn()"
></m-notifications--topbar-toggle>
</ng-container>
</m-v2-topbar>
<ng-template #legacyTopbar>
<m-topbar class="m-noshadow">
<ng-container search> <ng-container search>
<m-search--bar></m-search--bar> <m-search--bar [defaultSizes]="false"></m-search--bar>
</ng-container> </ng-container>
<ng-container icons> <ng-container icons>
<m-notifications--topbar-toggle></m-notifications--topbar-toggle> <m-notifications--topbar-toggle
<m-wallet--topbar-toggle></m-wallet--topbar-toggle> *ngIf="session.isLoggedIn()"
></m-notifications--topbar-toggle>
</ng-container> </ng-container>
</m-topbar> </m-v2-topbar>
</ng-template> <ng-template #legacyTopbar>
<m-topbar class="m-noshadow">
<ng-container search>
<m-search--bar></m-search--bar>
</ng-container>
<m-sidebar--markers <ng-container icons>
<m-notifications--topbar-toggle></m-notifications--topbar-toggle>
<m-wallet--topbar-toggle></m-wallet--topbar-toggle>
</ng-container>
</m-topbar>
</ng-template>
<m-sidebar--markers
[class.has-v2-navbar]="featuresService.has('top-feeds')"
></m-sidebar--markers>
</ng-container>
<m-body
[class.has-v2-navbar]="featuresService.has('top-feeds')" [class.has-v2-navbar]="featuresService.has('top-feeds')"
></m-sidebar--markers> [class.is-pro-domain]="isProDomain"
</ng-container> >
<m-announcement [id]="'blockchain:sale'" *ngIf="false">
<span
class="m-blockchain--wallet-address-notice--action"
routerLink="/tokens"
i18n="@@BLOCKCHAIN__SALE__NOTICE"
>
The MINDS token is now live. Learn more here.
</span>
</m-announcement>
<m-blockchain--wallet-address-notice></m-blockchain--wallet-address-notice>
<router-outlet></router-outlet>
</m-body>
<m-messenger *ngIf="minds.LoggedIn && !isProDomain"></m-messenger>
<m-hovercard-popup></m-hovercard-popup>
<m-body <m-overlay-modal></m-overlay-modal>
[class.has-v2-navbar]="featuresService.has('top-feeds')" <m--blockchain--transaction-overlay></m--blockchain--transaction-overlay>
[class.is-pro-domain]="isProDomain" <m-modal--tos-updated *ngIf="session.isLoggedIn()"></m-modal--tos-updated>
> <m-juryDutySession__summons
<m-announcement [id]="'blockchain:sale'" *ngIf="false"> *ngIf="session.isLoggedIn() && !isProDomain"
<span ></m-juryDutySession__summons>
class="m-blockchain--wallet-address-notice--action"
routerLink="/tokens" <m-modal-signup-on-scroll *ngIf="!isProDomain"></m-modal-signup-on-scroll>
i18n="@@BLOCKCHAIN__SALE__NOTICE"
> <m-channel--onboarding *ngIf="showOnboarding"></m-channel--onboarding>
The MINDS token is now live. Learn more here.
</span> <m-cookies-notice *ngIf="!session.isLoggedIn()"> </m-cookies-notice>
</m-announcement> </ng-container>
<m-blockchain--wallet-address-notice></m-blockchain--wallet-address-notice>
<router-outlet></router-outlet>
</m-body>
<m-messenger *ngIf="minds.LoggedIn && !isProDomain"></m-messenger>
<m-hovercard-popup></m-hovercard-popup>
<m-overlay-modal></m-overlay-modal>
<m--blockchain--transaction-overlay></m--blockchain--transaction-overlay>
<m-modal--tos-updated *ngIf="session.isLoggedIn()"></m-modal--tos-updated>
<m-juryDutySession__summons
*ngIf="session.isLoggedIn() && !isProDomain"
></m-juryDutySession__summons>
<m-modal-signup-on-scroll *ngIf="!isProDomain"></m-modal-signup-on-scroll>
<m-channel--onboarding *ngIf="showOnboarding"></m-channel--onboarding>
<m-cookies-notice *ngIf="!session.isLoggedIn()"> </m-cookies-notice>
import { Component, HostBinding } from '@angular/core'; import { ChangeDetectorRef, Component, HostBinding } from '@angular/core';
import { NotificationService } from './modules/notifications/notification.service'; import { NotificationService } from './modules/notifications/notification.service';
import { AnalyticsService } from './services/analytics'; import { AnalyticsService } from './services/analytics';
...@@ -18,6 +18,7 @@ import { ThemeService } from './common/services/theme.service'; ...@@ -18,6 +18,7 @@ import { ThemeService } from './common/services/theme.service';
import { BannedService } from './modules/report/banned/banned.service'; import { BannedService } from './modules/report/banned/banned.service';
import { DiagnosticsService } from './services/diagnostics.service'; import { DiagnosticsService } from './services/diagnostics.service';
import { SiteService } from './common/services/site.service'; import { SiteService } from './common/services/site.service';
import { SsoService } from './common/services/sso.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { RouterHistoryService } from './common/services/router-history.service'; import { RouterHistoryService } from './common/services/router-history.service';
import { PRO_DOMAIN_ROUTES } from './modules/pro/pro.module'; import { PRO_DOMAIN_ROUTES } from './modules/pro/pro.module';
...@@ -29,8 +30,11 @@ import { PRO_DOMAIN_ROUTES } from './modules/pro/pro.module'; ...@@ -29,8 +30,11 @@ import { PRO_DOMAIN_ROUTES } from './modules/pro/pro.module';
}) })
export class Minds { export class Minds {
name: string; name: string;
minds = window.Minds; minds = window.Minds;
ready: boolean = false;
showOnboarding: boolean = false; showOnboarding: boolean = false;
showTOSModal: boolean = false; showTOSModal: boolean = false;
...@@ -57,7 +61,9 @@ export class Minds { ...@@ -57,7 +61,9 @@ export class Minds {
private bannedService: BannedService, private bannedService: BannedService,
private diagnostics: DiagnosticsService, private diagnostics: DiagnosticsService,
private routerHistoryService: RouterHistoryService, private routerHistoryService: RouterHistoryService,
private site: SiteService private site: SiteService,
private sso: SsoService,
private cd: ChangeDetectorRef
) { ) {
this.name = 'Minds'; this.name = 'Minds';
...@@ -67,8 +73,29 @@ export class Minds { ...@@ -67,8 +73,29 @@ export class Minds {
} }
async ngOnInit() { async ngOnInit() {
this.diagnostics.setUser(this.minds.user); try {
this.diagnostics.listen(); // Listen for user changes this.diagnostics.setUser(this.minds.user);
this.diagnostics.listen(); // Listen for user changes
if (this.sso.isRequired()) {
await this.sso.connect();
}
} catch (e) {
console.error('ngOnInit()', e);
}
this.ready = true;
this.detectChanges();
try {
await this.initialize();
} catch (e) {
console.error('initialize()', e);
}
}
async initialize() {
this.blockListService.fetch();
if (!this.site.isProDomain) { if (!this.site.isProDomain) {
this.notificationService.getNotifications(); this.notificationService.getNotifications();
...@@ -136,4 +163,9 @@ export class Minds { ...@@ -136,4 +163,9 @@ export class Minds {
get isProDomain() { get isProDomain() {
return this.site.isProDomain; return this.site.isProDomain;
} }
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
} }
import { Cookie } from '../../services/cookie'; import { Cookie } from '../../services/cookie';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { Location } from '@angular/common';
import { SiteService } from '../services/site.service';
/** /**
* API Class * API Class
*/ */
export class MindsHttpClient { export class MindsHttpClient {
base: string = '/'; base: string = '/';
origin: string = '';
cookie: Cookie = new Cookie(); cookie: Cookie = new Cookie();
static _(http: HttpClient, site: SiteService) { static _(http: HttpClient) {
return new MindsHttpClient(http, site); return new MindsHttpClient(http);
} }
constructor(public http: HttpClient, protected site: SiteService) { constructor(public http: HttpClient) {}
if (this.site.isProDomain) {
this.base = window.Minds.site_url;
this.origin = document.location.host;
}
}
/** /**
* Return a GET request * Return a GET request
...@@ -81,22 +73,11 @@ export class MindsHttpClient { ...@@ -81,22 +73,11 @@ export class MindsHttpClient {
'X-VERSION': environment.version, 'X-VERSION': environment.version,
}; };
if (this.origin) {
const PRO_XSRF_JWT = this.cookie.get('PRO-XSRF-JWT') || '';
headers['X-MINDS-ORIGIN'] = this.origin;
headers['X-PRO-XSRF-JWT'] = PRO_XSRF_JWT;
}
const builtOptions = { const builtOptions = {
headers: new HttpHeaders(headers), headers: new HttpHeaders(headers),
cache: true, cache: true,
}; };
if (this.origin) {
builtOptions['withCredentials'] = true;
}
return Object.assign(options, builtOptions); return Object.assign(options, builtOptions);
} }
} }
......
...@@ -122,6 +122,11 @@ import { DashboardLayoutComponent } from './components/dashboard-layout/dashboar ...@@ -122,6 +122,11 @@ import { DashboardLayoutComponent } from './components/dashboard-layout/dashboar
import { ShadowboxLayoutComponent } from './components/shadowbox-layout/shadowbox-layout.component'; import { ShadowboxLayoutComponent } from './components/shadowbox-layout/shadowbox-layout.component';
import { ShadowboxHeaderComponent } from './components/shadowbox-header/shadowbox-header.component'; import { ShadowboxHeaderComponent } from './components/shadowbox-header/shadowbox-header.component';
import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime'; import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime';
import { DropdownSelectorComponent } from './components/dropdown-selector/dropdown-selector.component';
import { ShadowboxSubmitButtonComponent } from './components/shadowbox-submit-button/shadowbox-submit-button.component';
import { FormDescriptorComponent } from './components/form-descriptor/form-descriptor.component';
import { FormToastComponent } from './components/form-toast/form-toast.component';
import { SsoService } from './services/sso.service';
PlotlyModule.plotlyjs = PlotlyJS; PlotlyModule.plotlyjs = PlotlyJS;
...@@ -235,6 +240,10 @@ PlotlyModule.plotlyjs = PlotlyJS; ...@@ -235,6 +240,10 @@ PlotlyModule.plotlyjs = PlotlyJS;
DashboardLayoutComponent, DashboardLayoutComponent,
ShadowboxLayoutComponent, ShadowboxLayoutComponent,
ShadowboxHeaderComponent, ShadowboxHeaderComponent,
DropdownSelectorComponent,
FormDescriptorComponent,
FormToastComponent,
ShadowboxSubmitButtonComponent,
], ],
exports: [ exports: [
MINDS_PIPES, MINDS_PIPES,
...@@ -330,9 +339,14 @@ PlotlyModule.plotlyjs = PlotlyJS; ...@@ -330,9 +339,14 @@ PlotlyModule.plotlyjs = PlotlyJS;
PageLayoutComponent, PageLayoutComponent,
DashboardLayoutComponent, DashboardLayoutComponent,
ShadowboxLayoutComponent, ShadowboxLayoutComponent,
DropdownSelectorComponent,
FormDescriptorComponent,
FormToastComponent,
ShadowboxSubmitButtonComponent,
], ],
providers: [ providers: [
SiteService, SiteService,
SsoService,
{ {
provide: AttachmentService, provide: AttachmentService,
useFactory: AttachmentService._, useFactory: AttachmentService._,
...@@ -348,7 +362,7 @@ PlotlyModule.plotlyjs = PlotlyJS; ...@@ -348,7 +362,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
{ {
provide: MindsHttpClient, provide: MindsHttpClient,
useFactory: MindsHttpClient._, useFactory: MindsHttpClient._,
deps: [HttpClient, SiteService], deps: [HttpClient],
}, },
{ {
provide: NSFWSelectorCreatorService, provide: NSFWSelectorCreatorService,
......
...@@ -63,7 +63,7 @@ export class MindsAvatar { ...@@ -63,7 +63,7 @@ export class MindsAvatar {
if (this.object.type !== 'user') { if (this.object.type !== 'user') {
this.src = `${this.minds.cdn_url}fs/v1/avatars/${this.object.guid}/large/${this.object.icontime}`; this.src = `${this.minds.cdn_url}fs/v1/avatars/${this.object.guid}/large/${this.object.icontime}`;
} else if (this.object.guid !== this.minds.user.guid) { } else if (!this.minds.user || this.object.guid !== this.minds.user.guid) {
this.src = `${this.minds.cdn_url}icon/${this.object.guid}/large/${this.object.icontime}`; this.src = `${this.minds.cdn_url}icon/${this.object.guid}/large/${this.object.icontime}`;
} }
} }
...@@ -130,6 +130,6 @@ export class MindsAvatar { ...@@ -130,6 +130,6 @@ export class MindsAvatar {
* @returns true if the object guid matches the currently logged in user guid * @returns true if the object guid matches the currently logged in user guid
*/ */
isOwnerAvatar(): boolean { isOwnerAvatar(): boolean {
return this.object.guid === this.minds.user.guid; return this.minds.user && this.object.guid === this.minds.user.guid;
} }
} }
...@@ -241,7 +241,7 @@ export class ChartV2Component implements OnInit, OnDestroy { ...@@ -241,7 +241,7 @@ export class ChartV2Component implements OnInit, OnDestroy {
margin: { margin: {
t: this.isMini ? 0 : 16, t: this.isMini ? 0 : 16,
b: this.isMini ? 0 : 80, b: this.isMini ? 0 : 80,
l: this.isMini ? 0 : 0, l: 0,
r: this.isMini ? 0 : 80, r: this.isMini ? 0 : 80,
pad: 16, pad: 16,
}, },
...@@ -377,7 +377,7 @@ export class ChartV2Component implements OnInit, OnDestroy { ...@@ -377,7 +377,7 @@ export class ChartV2Component implements OnInit, OnDestroy {
return rows.map(row => { return rows.map(row => {
if (key === 'date') { if (key === 'date') {
return row[key].slice(0, 10); return row[key].slice(0, 10);
} else if (this.segments[0].unit === 'usd') { } else if (this.rawData.unit && this.rawData.unit === 'usd') {
return row[key] / 100; return row[key] / 100;
} else { } else {
return row[key]; return row[key];
......
...@@ -15,6 +15,13 @@ m-dashboardLayout { ...@@ -15,6 +15,13 @@ m-dashboardLayout {
position: relative; position: relative;
display: block; display: block;
width: 100%; width: 100%;
a {
font-weight: 400;
text-decoration: none;
@include m-theme() {
color: themed($m-blue);
}
}
} }
@media screen and (max-width: $min-tablet) { @media screen and (max-width: $min-tablet) {
......
import { Component, OnInit } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'm-dashboardLayout', selector: 'm-dashboardLayout',
templateUrl: './dashboard-layout.component.html', templateUrl: './dashboard-layout.component.html',
}) })
export class DashboardLayoutComponent implements OnInit { export class DashboardLayoutComponent {
constructor() {} constructor() {}
ngOnInit() {}
} }
...@@ -38,3 +38,11 @@ ...@@ -38,3 +38,11 @@
} }
} }
} }
.m-tooltip--hidden {
.m-tooltip--bubble {
position: absolute;
right: 0;
bottom: 0;
}
}
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
/** /**
* Date picker / selector - can be adapted to add time. * Date picker / selector.
*/ */
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: 'm-date-selector', selector: 'm-date-selector',
template: ` template: `
<label class="m-dateSelector__label" *ngIf="label">{{ label }}</label> <label class="m-dateSelector__label" *ngIf="label">{{ label }} </label>
<input <input
class="m-dateSelector__input" class="m-dateSelector__input"
[ngClass]="{ 'm-dateSelector__input--hidden': hideInput }"
[owlDateTimeTrigger]="dt" [owlDateTimeTrigger]="dt"
[owlDateTime]="dt" [owlDateTime]="dt"
[min]="min" [min]="min"
...@@ -17,7 +18,15 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; ...@@ -17,7 +18,15 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
[(ngModel)]="date" [(ngModel)]="date"
(ngModelChange)="onDateChange($event)" (ngModelChange)="onDateChange($event)"
/> />
<owl-date-time [pickerType]="'calendar'" #dt></owl-date-time> <owl-date-time [pickerType]="calendarType" #dt></owl-date-time>
<m-tooltip
*ngIf="tooltipIcon"
icon="{{ tooltipIcon }}"
[owlDateTimeTrigger]="dt"
i18n-label="{{ i18n }}"
>
{{ tooltipText }}
</m-tooltip>
`, `,
}) })
export class DateSelectorComponent { export class DateSelectorComponent {
...@@ -26,6 +35,13 @@ export class DateSelectorComponent { ...@@ -26,6 +35,13 @@ export class DateSelectorComponent {
@Input() dateFormat: string = 'short'; // legacy. TODO: implement localization. @Input() dateFormat: string = 'short'; // legacy. TODO: implement localization.
@Input() label: string; // label for input. @Input() label: string; // label for input.
@Input() hideInput = false; // text input showing the date.
@Input() calendarType = 'calendar'; // timer/calendar/both.
@Input() i18n?: string; // i18n string to accompany tooltip text.
@Input() tooltipIcon?: string; // tooltip icon.
@Input() tooltipText?: string; // tooltip text.
protected _date: Date; protected _date: Date;
@Input('date') // parse input into Date object. @Input('date') // parse input into Date object.
......
<div
class="m-draggableList__listItem m-draggableList__listHeader"
*ngIf="headers"
(click)="clickedHeaderRow($event)"
>
<ng-container *ngFor="let header of headers">
<div class="m-draggableList__cell">{{ header | titlecase }}</div>
</ng-container>
<div class="m-draggableList__cell"></div>
</div>
<ul
dndDropzone
[dndHorizontal]="false"
[dndEffectAllowed]="dndEffectAllowed"
(dndStart)="dragging = true"
(dndDrop)="onDrop($event)"
class="m-draggableList__list"
[ngClass]="{ dragging: dragging }"
>
<div class="dndPlaceholder" dndPlaceholderRef></div>
<li
*ngFor="let item of data; let i = index; trackBy: trackByFunction"
[dndDraggable]="item"
[dndEffectAllowed]="'move'"
[dndDragImageOffsetFunction]="dragImageOffsetRight"
[dndDisableIf]="disabled"
class="m-draggableList__listItem"
>
<ng-container
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ item: item, i: i }"
></ng-container>
<div class="m-draggableList__cell">
<i class="handle material-icons" dndHandle>open_with</i>
<i class="material-icons" (click)="removeItem(i)">
clear
</i>
</div>
</li>
</ul>
@import 'themes'; @import 'themes';
m-draggable-list { m-draggableList {
width: 100%;
@include m-theme() {
box-shadow: 0 1px 4px 0 rgba(themed($m-black), 0.1);
}
ul.m-draggableList__list { ul.m-draggableList__list {
width: 100%;
list-style: none; list-style: none;
padding: 0; padding: 0;
padding-inline-start: 0;
margin: 0; margin: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: all ease 300ms; transition: all ease 300ms;
&.dndDragover { &.dndDragover {
padding-top: 16px; // padding-top: 16px;
padding-bottom: 16px; // padding-bottom: 16px;
@include m-theme() {
background-color: rgba(themed($m-black), 0.05);
box-shadow: 0 1px 4px 0 rgba(themed($m-black), 0.1);
}
}
&.dragging {
li.m-draggableList__listItem {
&:first-child {
@include m-theme() {
border-top: 1px solid themed($m-grey-50);
}
}
}
} }
}
li.m-draggableList__listItem { .m-draggableList__listItem {
padding: 8px; display: flex;
border: 1px solid #ddd; align-items: center;
list-style-type: none;
padding: 0;
margin: 0;
@include m-theme() {
border: 1px solid themed($m-grey-50);
color: themed($m-grey-800);
}
// &:first-child {
&:not(.m-draggableList__listHeader) {
@include m-theme() {
border-top: none;
}
}
// }
&.m-draggableList__listHeader {
@include m-theme() {
// border-bottom: none;
color: themed($m-grey-300);
}
}
}
input.m-draggableList__cell {
width: 0;
min-width: 0;
}
.m-draggableList__cell {
padding: 10px 20px;
flex: 1 1 0px;
box-sizing: border-box;
@include m-theme() {
border: none;
border-right: 1px solid themed($m-grey-50);
background-color: themed($m-white);
}
&input {
width: 0;
min-width: 0;
}
&:last-child {
//icon cell
padding: 10px 15px;
flex: 0 0 80px;
max-width: 80px;
height: 40px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
.handle { @include m-theme() {
@include m-theme() { border-right: none;
color: themed($grey-600); }
} }
}
i {
cursor: pointer;
width: auto;
height: auto;
transition: all 0.3s ease;
@include m-theme() {
color: themed($m-grey-300);
}
&.handle {
font-size: 20px;
padding-right: 8px;
@include m-theme() {
} }
} }
&:hover {
transform: scale(1.15);
@include m-theme() {
color: themed($m-grey-200);
}
}
}
.dndPlaceholder {
min-height: 100px;
@include m-theme() {
border: 1px dashed rgba(themed($m-grey-100), 0.8);
}
} }
} }
import { Component, ContentChild, Input, TemplateRef } from '@angular/core'; import {
import { DndDropEvent, EffectAllowed } from 'ngx-drag-drop'; Component,
ContentChild,
Input,
TemplateRef,
Output,
EventEmitter,
ChangeDetectorRef,
} from '@angular/core';
import {
DndDropEvent,
EffectAllowed,
DndDragImageOffsetFunction,
} from 'ngx-drag-drop';
@Component({ @Component({
selector: 'm-draggable-list', selector: 'm-draggableList',
template: ` templateUrl: 'list.component.html',
<ul
dndDropzone
[dndHorizontal]="false"
[dndEffectAllowed]="dndEffectAllowed"
(dndDrop)="onDrop($event)"
class="m-draggableList__list"
>
<div
class="dndPlaceholder"
dndPlaceholderRef
style="min-height:100px;border:1px dashed green;background-color:rgba(0, 0, 0, 0.1)"
></div>
<li
*ngFor="let item of data; let i = index; trackBy: trackByFunction"
[dndDraggable]="item"
[dndEffectAllowed]="'move'"
class="m-draggableList__listItem"
>
<i class="handle material-icons" dndHandle>reorder</i>
<ng-container
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ item: item, i: i }"
></ng-container>
</li>
</ul>
`,
}) })
export class DraggableListComponent { export class DraggableListComponent {
@Input() data: Array<any>; @Input() data: Array<any>;
@Input() dndEffectAllowed: EffectAllowed = 'copyMove'; @Input() dndEffectAllowed: EffectAllowed = 'copyMove';
@Input() id: string; @Input() id: string;
@Input() headers: string[];
@Input() disabled: boolean;
@ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>; @ContentChild(TemplateRef, { static: false }) template: TemplateRef<any>;
@Output() emptyListHeaderRowClicked: EventEmitter<any> = new EventEmitter();
@Output() arrayChanged: EventEmitter<any> = new EventEmitter();
dragging: boolean = false;
trackByFunction(index, item) { trackByFunction(index, item) {
return this.id ? item[this.id] + index : index; return this.id ? item[this.id] + index : index;
} }
constructor(private cd: ChangeDetectorRef) {}
onDrop(event: DndDropEvent) { onDrop(event: DndDropEvent) {
this.dragging = false;
if ( if (
this.data && this.data &&
(event.dropEffect === 'copy' || event.dropEffect === 'move') (event.dropEffect === 'copy' || event.dropEffect === 'move')
...@@ -50,7 +44,7 @@ export class DraggableListComponent { ...@@ -50,7 +44,7 @@ export class DraggableListComponent {
let dragIndex = this.data.findIndex( let dragIndex = this.data.findIndex(
item => event.data[this.id] === item[this.id] item => event.data[this.id] === item[this.id]
); );
let dropIndex = event.index || this.data.length; let dropIndex = event.index;
// remove element // remove element
this.data.splice(dragIndex, 1); this.data.splice(dragIndex, 1);
...@@ -60,6 +54,43 @@ export class DraggableListComponent { ...@@ -60,6 +54,43 @@ export class DraggableListComponent {
} }
this.data.splice(dropIndex, 0, event.data); this.data.splice(dropIndex, 0, event.data);
this.arrayChanged.emit(this.data);
} }
} }
removeItem(index) {
this.data.splice(index, 1);
this.arrayChanged.emit(this.data);
}
clickedHeaderRow($event) {
if (this.data.length === 0) {
this.emptyListHeaderRowClicked.emit($event);
}
}
dragImageOffsetRight: DndDragImageOffsetFunction = (
event: DragEvent,
dragImage: HTMLElement
) => {
return {
x: dragImage.offsetWidth - 57,
y: event.offsetY + 10,
};
};
/**
* If input is focused then disable dragging
*/
onFocusIn(e: FocusEvent | MouseEvent) {
this.disabled = true;
}
/**
* Re-enable when input not focused
* TODO: Make this smarter.. what if something else disabled the dragging?
*/
onFocusOut(e: FocusEvent | MouseEvent) {
this.disabled = false;
}
} }
<div class="m-analyticsFilter__labelWrapper" *ngIf="showLabel"> <div class="m-dropdownSelector__labelWrapper" *ngIf="showLabel">
<span>{{ filter.label }}</span> <span>{{ filter.label }}</span>
<m-tooltip icon="help"> <m-tooltip icon="help">
<div>{{ filter?.description }}</div> <div>{{ filter?.description }}</div>
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
</m-tooltip> </m-tooltip>
</div> </div>
<div <div
class="m-analyticsFilter__wrapper" class="m-dropdownSelector__wrapper"
[ngClass]="{ [ngClass]="{
expanded: expanded, expanded: expanded,
dropUp: dropUp dropUp: dropUp
...@@ -21,27 +21,23 @@ ...@@ -21,27 +21,23 @@
(blur)="expanded = false" (blur)="expanded = false"
tabindex="0" tabindex="0"
> >
<div <div class="m-dropdownSelector__header" (click)="expanded = !expanded">
class="m-analyticsFilter__header m-analyticsFilter__row" <span class="m-dropdownSelector__option">
(click)="expanded = !expanded"
>
<span class="m-analyticsFilter__option m-analyticsFilter__option--selected">
{{ selectedOption.label }} {{ selectedOption.label }}
</span> </span>
<i class="material-icons" *ngIf="!expanded">keyboard_arrow_down</i> <i class="material-icons" *ngIf="!expanded">keyboard_arrow_down</i>
<i class="material-icons" *ngIf="expanded">keyboard_arrow_up</i> <i class="material-icons" *ngIf="expanded">keyboard_arrow_up</i>
</div> </div>
<div class="m-analyticsFilter__optionsContainer"> <div class="m-dropdownSelector__optionsContainer">
<ng-container *ngFor="let option of filter.options"> <ng-container *ngFor="let option of filter.options">
<div <div
class="m-analyticsFilter__option m-analyticsFilter__row" class="m-dropdownSelector__option"
(click)="updateFilter(option)" (click)="updateFilter(option)"
[ngClass]="{ [ngClass]="{
unavailable: option.available === false unavailable: option.available === false
}" }"
> >
{{ option.label }} {{ option.label }}
<!-- <span>{{ option.label }}</span> -->
</div> </div>
</ng-container> </ng-container>
</div> </div>
......
$rounded-top: 3px 3px 0 0; $rounded-top: 3px 3px 0 0;
$rounded-bottom: 0 0 3px 3px; $rounded-bottom: 0 0 3px 3px;
m-analytics__filter { m-dropdownSelector {
position: relative; position: relative;
margin: 0 24px 36px 0; margin: 0 24px 36px 0;
z-index: 2; z-index: 2;
display: block; display: block;
} }
.m-analyticsFilter__labelWrapper { .m-dropdownSelector__labelWrapper {
position: absolute; position: absolute;
bottom: 115%; bottom: 115%;
white-space: nowrap; white-space: nowrap;
...@@ -45,168 +45,179 @@ m-analytics__filter { ...@@ -45,168 +45,179 @@ m-analytics__filter {
} }
} }
.m-analyticsFilter__wrapper { .m-dropdownSelector__wrapper {
cursor: pointer; cursor: pointer;
&:focus { &:focus {
outline: 0; outline: 0;
} }
> * {
width: 180px;
box-sizing: border-box;
}
.m-analyticsFilter__optionsContainer {
padding: 8px 0;
.m-analyticsFilter__option {
transform: translateY(25%);
}
}
&.expanded { &.expanded {
@include m-theme() { @include m-theme() {
box-shadow: 0px 1px 15px 0 rgba(themed($m-black), 0.15); box-shadow: 0px 1px 15px 0 rgba(themed($m-black), 0.15);
} }
.m-analyticsFilter__header { .m-dropdownSelector__header {
@include m-theme() { @include m-theme() {
border-color: themed($m-blue); border-color: themed($m-blue);
} }
} }
.m-analyticsFilter__optionsContainer { .m-dropdownSelector__optionsContainer {
display: block; display: block;
} }
&:not(.dropUp) { &:not(.dropUp) {
.m-analyticsFilter__header { .m-dropdownSelector__header {
@include m-theme() { @include m-theme() {
border-radius: $rounded-top; border-radius: $rounded-top;
} }
} }
.m-analyticsFilter__optionsContainer { .m-dropdownSelector__optionsContainer {
border-top: none; border-top: none;
border-radius: $rounded-bottom; border-radius: $rounded-bottom;
} }
} }
&.dropUp { &.dropUp {
.m-analyticsFilter__header { .m-dropdownSelector__header {
border-radius: $rounded-bottom; border-radius: $rounded-bottom;
} }
.m-analyticsFilter__optionsContainer { .m-dropdownSelector__optionsContainer {
bottom: 100%; bottom: 100%;
border-radius: $rounded-top; border-radius: $rounded-top;
border-bottom: none; border-bottom: none;
@include m-theme() {
box-shadow: 0px -4px 16px -4px rgba(themed($m-black), 0.15);
}
} }
} }
} }
.m-analyticsFilter__header { > * {
position: relative; width: 180px;
border-radius: 3px; box-sizing: border-box;
transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); }
}
.m-dropdownSelector__header {
position: relative;
border-radius: 3px;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
@include m-theme() {
background-color: themed($m-white);
color: themed($m-grey-300);
}
@include m-theme() {
border: 1px solid themed($m-grey-100);
}
.m-dropdownSelector__label {
margin-right: 10px;
}
i {
flex-grow: 0;
width: 24px;
height: 24px;
padding-top: 2px;
}
.m-dropdownSelector__option {
@include m-theme() { @include m-theme() {
background-color: themed($m-white); color: themed($m-grey-500);
color: themed($m-grey-300);
}
@include m-theme() {
border: 1px solid themed($m-grey-100);
} }
.m-analyticsFilter__label { }
margin-right: 10px; }
}
i { .m-dropdownSelector__optionsContainer {
flex-grow: 0; box-sizing: border-box;
width: 24px; position: absolute;
height: 24px; display: none;
} border-radius: 3px;
.m-analyticsFilter__option--selected { left: 0px;
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
@include m-theme() {
border: 1px solid themed($m-blue);
border-top: 1px solid themed($m-blue);
background-color: themed($m-white);
box-shadow: 0px 8px 16px 0px rgba(themed($m-black), 0.15);
}
.m-dropdownSelector__option {
&:hover:not(.unavailable) {
@include m-theme() { @include m-theme() {
color: themed($m-grey-500); color: themed($m-grey-500);
background-color: rgba(themed($m-grey-100), 0.2);
} }
} }
} &:first-child {
padding-top: 14px;
.m-analyticsFilter__optionsContainer { }
position: absolute; &:last-child {
display: none; padding-bottom: 14px;
border-radius: 3px;
left: 0px;
transition: box-shadow 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
@include m-theme() {
border: 1px solid themed($m-blue);
border-top: 1px solid themed($m-blue);
background-color: themed($m-white);
} }
} }
}
.m-analyticsFilter__row { .m-dropdownSelector__header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 46px; padding-right: 10px;
padding: 0 20px; }
&.m-analyticsFilter__header { .m-dropdownSelector__option {
padding-right: 10px; display: inline-block;
} padding: 10px 20px;
box-sizing: border-box;
width: inherit;
border-radius: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@include m-theme() {
color: themed($m-grey-300);
} }
.m-analyticsFilter__option {
display: inline-block;
box-sizing: border-box;
width: inherit;
border-radius: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.unavailable {
display: none;
text-decoration: line-through;
@include m-theme() { @include m-theme() {
color: themed($m-grey-300); color: themed($m-grey-50);
}
&.unavailable {
text-decoration: line-through;
@include m-theme() {
color: themed($m-grey-50);
}
}
&:hover:not(.unavailable) {
@include m-theme() {
color: themed($m-grey-600);
}
} }
} }
} }
@media screen and (max-width: $min-tablet) { @media screen and (max-width: $min-tablet) {
m-analytics__filter { m-dropdownSelector {
.m-analyticsFilter__labelWrapper { .m-dropdownSelector__labelWrapper {
.m-tooltip--bubble { .m-tooltip--bubble {
width: 120px; width: 120px;
} }
} }
.m-analyticsFilter__wrapper {
> * {
width: 140px;
}
}
} }
} }
@media screen and (max-width: $max-mobile) { @media screen and (max-width: $max-mobile) {
m-analytics__filter { m-dropdownSelector {
.m-analyticsFilter__wrapper { .m-dropdownSelector__wrapper {
.m-analyticsFilter__header { > * {
width: 160px;
}
.m-dropdownSelector__header {
padding-right: 10px;
i { i {
display: none; display: none;
} }
} }
.m-analyticsFilter__row { .m-dropdownSelector__optionsContainer {
padding: 0 18px; .m-dropdownSelector__option {
height: 40px; &:first-child {
&.m-analyticsFilter__header { padding-top: 11px;
padding-right: 10px; }
&:last-child {
padding-bottom: 11px;
}
} }
} }
.m-analyticsFilter__option--selected { .m-dropdownSelector__option {
margin-right: 0; margin-right: 0;
padding: 8px 18px;
} }
} }
} }
......