diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b3f1807f1..25a272d39 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -833,6 +833,9 @@ "settings_account": { "message": "Account" }, + "settings_import_export": { + "message": "Import & Export Settings" + }, "settings_trackers": { "message": "Trackers" }, @@ -1234,6 +1237,48 @@ } } }, + "android_tab_overview": { + "message": "Overview" + }, + "android_tab_site_blocking": { + "message": "Site Blocking" + }, + "android_tab_global_blocking": { + "message": "Global Blocking" + }, + "android_site_blocking_header": { + "message": "Trackers on this site" + }, + "android_global_blocking_header": { + "message": "Global Tracking" + }, + "android_blocking_reset": { + "message": "Reset Settings" + }, + "android_block": { + "message": "Block" + }, + "android_unblock": { + "message": "Unblock" + }, + "android_restrict": { + "message": "Restrict" + }, + "android_unrestrict": { + "message": "Undo" + }, + "android_trust": { + "message": "Trust" + }, + "android_untrust": { + "message": "Undo" + }, + "android_anonymize": { + "message": "Anonymize" + }, + "android_anonymized": { + "message": "Anonymized" + }, "hub_side_navigation_home": { "message": "Home" }, diff --git a/app/content-scripts/notifications.js b/app/content-scripts/notifications.js index 375e28caa..c9ecc7c56 100644 --- a/app/content-scripts/notifications.js +++ b/app/content-scripts/notifications.js @@ -1,5 +1,5 @@ /** - * Ghostery NotificationsContentScript + * Ghostery Notifications Content Script * * This file provides notification alerts for the CMP, update dialogs * and import/export functionality @@ -7,7 +7,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -607,10 +607,12 @@ const NotificationsContentScript = (function(win, doc) { * @memberOf NotificationsContentScript * @package */ - const exportFile = function(content) { + const exportFile = function(content, type) { const textFileAsBlob = new Blob([content], { type: 'text/plain' }); + const ext = type === 'Ghostery-Backup' ? 'ghost' : 'json'; const d = new Date(); - const fileNameToSaveAs = `Ghostery-Backup-${d.getMonth() + 1}-${d.getDate()}-${d.getFullYear()}.ghost`; + const dStr = `${d.getMonth() + 1}-${d.getDate()}-${d.getFullYear()}`; + const fileNameToSaveAs = `${type}-${dStr}.${ext}`; let url = ''; if (window.URL) { url = window.URL.createObjectURL(textFileAsBlob); @@ -764,7 +766,8 @@ const NotificationsContentScript = (function(win, doc) { } else if (name === 'onFileImported') { updateBrowseWindow(message); } else if (name === 'exportFile') { - exportFile(message); + const { content, type } = message; + exportFile(content, type); } // trigger a response callback to src/background so that we can handle errors properly diff --git a/app/hub/Views/HomeView/HomeView.jsx b/app/hub/Views/HomeView/HomeView.jsx index 91727d0b1..8f911fd88 100644 --- a/app/hub/Views/HomeView/HomeView.jsx +++ b/app/hub/Views/HomeView/HomeView.jsx @@ -18,8 +18,7 @@ import { NavLink } from 'react-router-dom'; import globals from '../../../../src/classes/Globals'; import { ToggleCheckbox } from '../../../shared-components'; -const { IS_CLIQZ } = globals; -const IS_FIREFOX = (globals.BROWSER_INFO.name === 'firefox'); +const { IS_CLIQZ, BROWSER_INFO } = globals; /** * A Functional React component for rendering the Home View @@ -40,10 +39,10 @@ const HomeView = (props) => { const accountHref = globals.ACCOUNT_BASE_URL; let headerInfoText = t('hub_home_header_info'); - if (globals.BROWSER_INFO) { - if (IS_FIREFOX) { + if (BROWSER_INFO) { + if (BROWSER_INFO.name === 'firefox') { headerInfoText = t('hub_home_header_info_opted_out'); - } else if (IS_CLIQZ) { + } else if (IS_CLIQZ || BROWSER_INFO.name === 'ghostery_android') { headerInfoText = t('hub_home_header_info_cliqz'); } } diff --git a/app/hub/Views/SetupView/SetupView.jsx b/app/hub/Views/SetupView/SetupView.jsx index 8db394a37..f70cecabd 100644 --- a/app/hub/Views/SetupView/SetupView.jsx +++ b/app/hub/Views/SetupView/SetupView.jsx @@ -28,7 +28,7 @@ const SetupView = (props) => { const { extraRoutes, sendMountActions, steps } = props; return ( -
+
{steps.map(step => (
{ - const { sendMountActions, steps } = props; + const { sendMountActions, steps, isAndroid } = props; return ( -
+
{steps.map(step => ( } + render={() => } /> ))}
diff --git a/app/hub/Views/TutorialView/TutorialView.scss b/app/hub/Views/TutorialView/TutorialView.scss index ae47ef2ec..eebb3226e 100644 --- a/app/hub/Views/TutorialView/TutorialView.scss +++ b/app/hub/Views/TutorialView/TutorialView.scss @@ -149,8 +149,17 @@ left: 1%; padding-right: 12%; margin-bottom: 30px; + @media only screen and (max-width: 740px) { + left: auto; + padding-right: 0; + } + } + &.layout-detailed { + left: -11%; + @media only screen and (max-width: 740px) { + left: auto; + } } - &.layout-detailed { left: -11%; } } @media only screen and (max-width: 960px) { .TutorialLayoutView .TutorialView__tagline { diff --git a/app/hub/Views/TutorialView/TutorialViewContainer.jsx b/app/hub/Views/TutorialView/TutorialViewContainer.jsx index f42b3985f..db683ea04 100644 --- a/app/hub/Views/TutorialView/TutorialViewContainer.jsx +++ b/app/hub/Views/TutorialView/TutorialViewContainer.jsx @@ -14,6 +14,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import TutorialView from './TutorialView'; +import globals from '../../../../src/classes/Globals'; // Component Views import TutorialVideoView from '../TutorialViews/TutorialVideoView'; @@ -23,6 +24,9 @@ import TutorialLayoutView from '../TutorialViews/TutorialLayoutView'; import TutorialTrustView from '../TutorialViews/TutorialTrustView'; import TutorialAntiSuiteView from '../TutorialViews/TutorialAntiSuiteView'; +const { BROWSER_INFO } = globals; +const IS_ANDROID = (BROWSER_INFO.os === 'android'); + /** * @class Implement the Tutorial View for the Ghostery Hub * @extends Component @@ -88,7 +92,7 @@ class TutorialViewContainer extends Component { }, ]; - return ; + return ; } } diff --git a/app/hub/Views/TutorialView/__tests__/__snapshots__/TutorialView.test.jsx.snap b/app/hub/Views/TutorialView/__tests__/__snapshots__/TutorialView.test.jsx.snap index 99bd3af03..b26c5aca7 100644 --- a/app/hub/Views/TutorialView/__tests__/__snapshots__/TutorialView.test.jsx.snap +++ b/app/hub/Views/TutorialView/__tests__/__snapshots__/TutorialView.test.jsx.snap @@ -2,7 +2,7 @@ exports[`app/hub/Views/TutorialView component Snapshot tests with react-test-renderer tutorial view is rendered correctly 1`] = `
( +const TutorialAntiSuiteView = ({ isAndroid }) => (
@@ -26,17 +27,21 @@ const TutorialAntiSuiteView = () => (
{t('simple_view')} -
- {t('detailed_view')} -
- {t('detailed_view')} + { !isAndroid && ( +
+
+ {t('detailed_view')} +
+ {t('detailed_view')} +
+ )}
@@ -82,6 +87,8 @@ const TutorialAntiSuiteView = () => (
); -// No need for PropTypes. The SideNavigationViewContainer has no props. +TutorialAntiSuiteView.propTypes = { + isAndroid: PropTypes.bool.isRequired +}; export default TutorialAntiSuiteView; diff --git a/app/hub/Views/TutorialViews/TutorialAntiSuiteView/TutorialAntiSuiteViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialAntiSuiteView/TutorialAntiSuiteViewContainer.jsx index 5313f9b07..0f322785c 100644 --- a/app/hub/Views/TutorialViews/TutorialAntiSuiteView/TutorialAntiSuiteViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialAntiSuiteView/TutorialAntiSuiteViewContainer.jsx @@ -51,7 +51,8 @@ class TutorialAntiSuiteViewContainer extends Component { * @return {JSX} JSX for rendering the Tutorial Anti Suite View of the Hub app */ render() { - return ; + const { isAndroid } = this.props; + return ; } } diff --git a/app/hub/Views/TutorialViews/TutorialAntiSuiteView/__test__/TutorialAntiSuiteView.test.jsx b/app/hub/Views/TutorialViews/TutorialAntiSuiteView/__test__/TutorialAntiSuiteView.test.jsx index 6976c7b7d..caeb77ac8 100644 --- a/app/hub/Views/TutorialViews/TutorialAntiSuiteView/__test__/TutorialAntiSuiteView.test.jsx +++ b/app/hub/Views/TutorialViews/TutorialAntiSuiteView/__test__/TutorialAntiSuiteView.test.jsx @@ -19,14 +19,14 @@ import TutorialAntiSuiteView from '../TutorialAntiSuiteView'; describe('app/hub/Views/TutorialViews/TutorialAntiSuiteView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('tutorial anti-suite view is rendered correctly', () => { - const component = renderer.create().toJSON(); + const component = renderer.create().toJSON(); expect(component).toMatchSnapshot(); }); }); describe('Shallow snapshot tests rendered with Enzyme', () => { test('the happy path of the component', () => { - const component = shallow(); + const component = shallow(); expect(component.find('.TutorialAntiSuiteView').length).toBe(1); expect(component.find('img').length).toBe(2); expect(component.find('.TutorialView__keyItem').length).toBe(3); diff --git a/app/hub/Views/TutorialViews/TutorialAntiSuiteView/__test__/__snapshots__/TutorialAntiSuiteView.test.jsx.snap b/app/hub/Views/TutorialViews/TutorialAntiSuiteView/__test__/__snapshots__/TutorialAntiSuiteView.test.jsx.snap index 83a6fdc3e..bed91e7ce 100644 --- a/app/hub/Views/TutorialViews/TutorialAntiSuiteView/__test__/__snapshots__/TutorialAntiSuiteView.test.jsx.snap +++ b/app/hub/Views/TutorialViews/TutorialAntiSuiteView/__test__/__snapshots__/TutorialAntiSuiteView.test.jsx.snap @@ -17,16 +17,18 @@ exports[`app/hub/Views/TutorialViews/TutorialAntiSuiteView component Snapshot te className="TutorialAntiSuiteView__image antisuite-simple" src="/app/images/hub/tutorial/antisuite-simple.png" /> -
- detailed_view +
+
+ detailed_view +
+ detailed_view
- detailed_view
( +const TutorialBlockingView = ({ isAndroid }) => (
@@ -26,17 +27,21 @@ const TutorialBlockingView = () => (
{t('detailed_view')} -
- {t('hub_tutorial_detailed_expanded_view')} -
- {t('hub_tutorial_detailed_expanded_view')} + { !isAndroid && ( +
+
+ {t('hub_tutorial_detailed_expanded_view')} +
+ {t('hub_tutorial_detailed_expanded_view')} +
+ )}
@@ -60,6 +65,8 @@ const TutorialBlockingView = () => (
); -// No need for PropTypes. The SideNavigationViewContainer has no props. +TutorialBlockingView.propTypes = { + isAndroid: PropTypes.bool.isRequired +}; export default TutorialBlockingView; diff --git a/app/hub/Views/TutorialViews/TutorialBlockingView/TutorialBlockingViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialBlockingView/TutorialBlockingViewContainer.jsx index 5210ca461..856309f8a 100644 --- a/app/hub/Views/TutorialViews/TutorialBlockingView/TutorialBlockingViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialBlockingView/TutorialBlockingViewContainer.jsx @@ -44,7 +44,8 @@ class TutorialBlockingViewContainer extends Component { * @return {JSX} JSX for rendering the Tutorial Blocking View of the Hub app */ render() { - return ; + const { isAndroid } = this.props; + return ; } } diff --git a/app/hub/Views/TutorialViews/TutorialBlockingView/__tests__/TutorialBlockingView.test.jsx b/app/hub/Views/TutorialViews/TutorialBlockingView/__tests__/TutorialBlockingView.test.jsx index f15ea7611..5720f6348 100644 --- a/app/hub/Views/TutorialViews/TutorialBlockingView/__tests__/TutorialBlockingView.test.jsx +++ b/app/hub/Views/TutorialViews/TutorialBlockingView/__tests__/TutorialBlockingView.test.jsx @@ -19,14 +19,14 @@ import TutorialBlockingView from '../TutorialBlockingView'; describe('app/hub/Views/TutorialViews/TutorialBlockingView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('tutorial blocking view is rendered correctly', () => { - const component = renderer.create().toJSON(); + const component = renderer.create().toJSON(); expect(component).toMatchSnapshot(); }); }); describe('Shallow snapshot tests rendered with Enzyme', () => { test('the happy path of the component', () => { - const component = shallow(); + const component = shallow(); expect(component.find('.TutorialBlockingView').length).toBe(1); expect(component.find('.TutorialBlockingView__image').length).toBe(2); expect(component.find('.TutorialView__keyText').length).toBe(3); diff --git a/app/hub/Views/TutorialViews/TutorialBlockingView/__tests__/__snapshots__/TutorialBlockingView.test.jsx.snap b/app/hub/Views/TutorialViews/TutorialBlockingView/__tests__/__snapshots__/TutorialBlockingView.test.jsx.snap index f314105f1..6a430821b 100644 --- a/app/hub/Views/TutorialViews/TutorialBlockingView/__tests__/__snapshots__/TutorialBlockingView.test.jsx.snap +++ b/app/hub/Views/TutorialViews/TutorialBlockingView/__tests__/__snapshots__/TutorialBlockingView.test.jsx.snap @@ -17,16 +17,18 @@ exports[`app/hub/Views/TutorialViews/TutorialBlockingView component Snapshot tes className="TutorialBlockingView__image blocking-detailed" src="/app/images/hub/tutorial/blocking-detailed.png" /> -
- hub_tutorial_detailed_expanded_view +
+
+ hub_tutorial_detailed_expanded_view +
+ hub_tutorial_detailed_expanded_view
- hub_tutorial_detailed_expanded_view
( +const TutorialLayoutView = ({ isAndroid }) => (
-
+
{t('simple_view')} {t('detailed_view')}
@@ -43,6 +44,8 @@ const TutorialLayoutView = () => (
); -// No need for PropTypes. The SideNavigationViewContainer has no props. +TutorialLayoutView.propTypes = { + isAndroid: PropTypes.bool.isRequired +}; export default TutorialLayoutView; diff --git a/app/hub/Views/TutorialViews/TutorialLayoutView/TutorialLayoutViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialLayoutView/TutorialLayoutViewContainer.jsx index 59c5f0208..384bcfaa5 100644 --- a/app/hub/Views/TutorialViews/TutorialLayoutView/TutorialLayoutViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialLayoutView/TutorialLayoutViewContainer.jsx @@ -44,7 +44,8 @@ class TutorialLayoutViewContainer extends Component { * @return {JSX} JSX for rendering the Tutorial Layout View of the Hub app */ render() { - return ; + const { isAndroid } = this.props; + return ; } } diff --git a/app/hub/Views/TutorialViews/TutorialLayoutView/__tests__/TutorialLayoutView.test.jsx b/app/hub/Views/TutorialViews/TutorialLayoutView/__tests__/TutorialLayoutView.test.jsx index ea3bf45f5..af2a562ca 100644 --- a/app/hub/Views/TutorialViews/TutorialLayoutView/__tests__/TutorialLayoutView.test.jsx +++ b/app/hub/Views/TutorialViews/TutorialLayoutView/__tests__/TutorialLayoutView.test.jsx @@ -19,14 +19,14 @@ import TutorialLayoutView from '../TutorialLayoutView'; describe('app/hub/Views/TutorialViews/TutorialLayoutView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('tutorial layout view is rendered correctly', () => { - const component = renderer.create().toJSON(); + const component = renderer.create().toJSON(); expect(component).toMatchSnapshot(); }); }); describe('Shallow snapshot tests rendered with Enzyme', () => { test('the happy path of the component', () => { - const component = shallow(); + const component = shallow(); expect(component.find('.TutorialLayoutView').length).toBe(1); expect(component.find('.TutorialLayoutView__image').length).toBe(2); }); diff --git a/app/hub/Views/TutorialViews/TutorialLayoutView/__tests__/__snapshots__/TutorialLayoutView.test.jsx.snap b/app/hub/Views/TutorialViews/TutorialLayoutView/__tests__/__snapshots__/TutorialLayoutView.test.jsx.snap index 1b7dcb048..48c74fe4e 100644 --- a/app/hub/Views/TutorialViews/TutorialLayoutView/__tests__/__snapshots__/TutorialLayoutView.test.jsx.snap +++ b/app/hub/Views/TutorialViews/TutorialLayoutView/__tests__/__snapshots__/TutorialLayoutView.test.jsx.snap @@ -5,7 +5,7 @@ exports[`app/hub/Views/TutorialViews/TutorialLayoutView component Snapshot tests className="TutorialLayoutView TutorialView--mediumFlexColumn row align-center-middle flex-container" >
simple_view ( +const TutorialTrackerListView = ({ isAndroid }) => (
{t('hub_tutorial_trackerlist_title')}
@@ -38,6 +39,8 @@ const TutorialTrackerListView = () => (
); -// No need for PropTypes. The SideNavigationViewContainer has no props. +TutorialTrackerListView.propTypes = { + isAndroid: PropTypes.bool.isRequired +}; export default TutorialTrackerListView; diff --git a/app/hub/Views/TutorialViews/TutorialTrackerListView/TutorialTrackerListViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialTrackerListView/TutorialTrackerListViewContainer.jsx index 9aeab240b..dfe809712 100644 --- a/app/hub/Views/TutorialViews/TutorialTrackerListView/TutorialTrackerListViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialTrackerListView/TutorialTrackerListViewContainer.jsx @@ -44,13 +44,15 @@ class TutorialTrackerListViewContainer extends Component { * @return {JSX} JSX for rendering the Tutorial Tracker List View of the Hub app */ render() { - return ; + const { isAndroid } = this.props; + return ; } } // PropTypes ensure we pass required props of the correct type TutorialTrackerListViewContainer.propTypes = { index: PropTypes.number.isRequired, + isAndroid: PropTypes.bool.isRequired, actions: PropTypes.shape({ setTutorialNavigation: PropTypes.func.isRequired, }).isRequired, diff --git a/app/hub/Views/TutorialViews/TutorialTrackerListView/__tests__/TutorialTrackerListView.test.jsx b/app/hub/Views/TutorialViews/TutorialTrackerListView/__tests__/TutorialTrackerListView.test.jsx index 93662c432..c31fde285 100644 --- a/app/hub/Views/TutorialViews/TutorialTrackerListView/__tests__/TutorialTrackerListView.test.jsx +++ b/app/hub/Views/TutorialViews/TutorialTrackerListView/__tests__/TutorialTrackerListView.test.jsx @@ -19,14 +19,14 @@ import TutorialTrackerListView from '../TutorialTrackerListView'; describe('app/hub/Views/TutorialViews/TutorialTrackerListView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('tutorial tracker list view is rendered correctly', () => { - const component = renderer.create().toJSON(); + const component = renderer.create().toJSON(); expect(component).toMatchSnapshot(); }); }); describe('Shallow snapshot tests rendered with Enzyme', () => { test('the happy path of the component', () => { - const component = shallow(); + const component = shallow(); expect(component.find('.TutorialTrackerListView').length).toBe(1); expect(component.find('.TutorialTrackerListView__image').length).toBe(1); }); diff --git a/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustView.jsx b/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustView.jsx index 605aaa9cb..d477d66fb 100644 --- a/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustView.jsx +++ b/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustView.jsx @@ -12,13 +12,14 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; /** * A Functional React component for rendering the Tutorial Trust and Restrict View * @return {JSX} JSX for rendering the Tutorial Trust and Restrict View of the Hub app * @memberof HubComponents */ -const TutorialTrustView = () => ( +const TutorialTrustView = ({ isAndroid }) => (
@@ -26,17 +27,21 @@ const TutorialTrustView = () => (
{t('simple_view')} -
- {t('detailed_view')} -
- {t('detailed_view')} + { !isAndroid && ( +
+
+ {t('detailed_view')} +
+ {t('detailed_view')} +
+ )}
@@ -63,6 +68,8 @@ const TutorialTrustView = () => (
); -// No need for PropTypes. The SideNavigationViewContainer has no props. +TutorialTrustView.propTypes = { + isAndroid: PropTypes.bool.isRequired +}; export default TutorialTrustView; diff --git a/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustViewContainer.jsx index 42d8e341e..949d361af 100644 --- a/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustViewContainer.jsx @@ -44,7 +44,8 @@ class TutorialTrustViewContainer extends Component { * @return {JSX} JSX for rendering the Tutorial Trust View of the Hub app */ render() { - return ; + const { isAndroid } = this.props; + return ; } } diff --git a/app/hub/Views/TutorialViews/TutorialTrustView/__tests__/TutorialTrustView.test.jsx b/app/hub/Views/TutorialViews/TutorialTrustView/__tests__/TutorialTrustView.test.jsx index 92c050bfa..2cf8e90ec 100644 --- a/app/hub/Views/TutorialViews/TutorialTrustView/__tests__/TutorialTrustView.test.jsx +++ b/app/hub/Views/TutorialViews/TutorialTrustView/__tests__/TutorialTrustView.test.jsx @@ -19,14 +19,14 @@ import TutorialTrustView from '../TutorialTrustView'; describe('app/hub/Views/TutorialViews/TutorialTrustView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('tutorial trust view is rendered correctly', () => { - const component = renderer.create().toJSON(); + const component = renderer.create().toJSON(); expect(component).toMatchSnapshot(); }); }); describe('Shallow snapshot tests rendered with Enzyme', () => { test('the happy path of the component', () => { - const component = shallow(); + const component = shallow(); expect(component.find('.TutorialTrustView').length).toBe(1); expect(component.find('.TutorialTrustView__image').length).toBe(2); expect(component.find('.TutorialTrustView__key').length).toBe(1); diff --git a/app/hub/Views/TutorialViews/TutorialTrustView/__tests__/__snapshots__/TutorialTrustView.test.jsx.snap b/app/hub/Views/TutorialViews/TutorialTrustView/__tests__/__snapshots__/TutorialTrustView.test.jsx.snap index 27e9a3973..f34f2f7cd 100644 --- a/app/hub/Views/TutorialViews/TutorialTrustView/__tests__/__snapshots__/TutorialTrustView.test.jsx.snap +++ b/app/hub/Views/TutorialViews/TutorialTrustView/__tests__/__snapshots__/TutorialTrustView.test.jsx.snap @@ -17,16 +17,18 @@ exports[`app/hub/Views/TutorialViews/TutorialTrustView component Snapshot tests className="TutorialTrustView__image trustrestrict-simple" src="/app/images/hub/tutorial/trustrestrict-simple.png" /> -
- detailed_view +
+
+ detailed_view +
+ detailed_view
- detailed_view
+ + + + + + diff --git a/app/images/panel/checkbox-checked.svg b/app/images/panel/checkbox-checked.svg index cc7188ab5..c2f21d1f5 100644 --- a/app/images/panel/checkbox-checked.svg +++ b/app/images/panel/checkbox-checked.svg @@ -1,4 +1,4 @@ - + diff --git a/app/images/panel/checkbox-disabled.svg b/app/images/panel/checkbox-disabled.svg index c3ca2c2a9..7817ebb24 100644 --- a/app/images/panel/checkbox-disabled.svg +++ b/app/images/panel/checkbox-disabled.svg @@ -1,4 +1,4 @@ - + diff --git a/app/images/panel/checkbox.svg b/app/images/panel/checkbox.svg index c1dd4321a..9dd8b477f 100644 --- a/app/images/panel/checkbox.svg +++ b/app/images/panel/checkbox.svg @@ -1,4 +1,4 @@ - + diff --git a/app/panel-android/actions/trackerActions.js b/app/panel-android/actions/blockingActions.js similarity index 84% rename from app/panel-android/actions/trackerActions.js rename to app/panel-android/actions/blockingActions.js index f5ea7cf84..4ff46c5fe 100644 --- a/app/panel-android/actions/trackerActions.js +++ b/app/panel-android/actions/blockingActions.js @@ -1,10 +1,10 @@ /** - * Tracker Action creators + * Blocking Action creators * * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -68,6 +68,70 @@ function calculateDelta(oldState, newState) { return 0; } +// Modified version of _updateCliqzModuleWhitelist from app/panel/reducers/blocking.js +export function anonymizeSiteTracker({ actionData, state }) { + const updatedcliqzModuleData = JSON.parse(JSON.stringify(state.cliqzModuleData)); + const { antiTracking, adBlock } = state.cliqzModuleData; + const whitelistedUrls = { ...antiTracking.whitelistedUrls, ...adBlock.whitelistedUrls }; + const { unknownTracker } = actionData; + const { pageHost } = state.summary; + + const addToWhitelist = () => { + unknownTracker.sources.forEach((domain) => { + if (whitelistedUrls.hasOwnProperty(domain)) { + whitelistedUrls[domain].name = unknownTracker.name; + whitelistedUrls[domain].hosts.push(pageHost); + } else { + whitelistedUrls[domain] = { + name: unknownTracker.name, + hosts: [pageHost], + }; + } + }); + }; + + const removeFromWhitelist = (domain) => { + if (!whitelistedUrls[domain]) { return; } + + whitelistedUrls[domain].hosts = whitelistedUrls[domain].hosts.filter(hostUrl => ( + hostUrl !== pageHost + )); + + if (whitelistedUrls[domain].hosts.length === 0) { + delete whitelistedUrls[domain]; + } + }; + + if (unknownTracker.whitelisted) { + unknownTracker.sources.forEach(removeFromWhitelist); + + Object.keys(whitelistedUrls).forEach((domain) => { + if (whitelistedUrls[domain].name === unknownTracker.name) { + removeFromWhitelist(domain); + } + }); + } else { + addToWhitelist(); + } + + // Update Ad Blocking trackers + updatedcliqzModuleData.adBlock.unknownTrackers.forEach((trackerEl) => { + if (trackerEl.name === unknownTracker.name) { + trackerEl.whitelisted = !trackerEl.whitelisted; + } + }); + // Update Anti-Tracking trackers + updatedcliqzModuleData.antiTracking.unknownTrackers.forEach((trackerEl) => { + if (trackerEl.name === unknownTracker.name) { + trackerEl.whitelisted = !trackerEl.whitelisted; + } + }); + sendMessage('setPanelData', { cliqz_module_whitelist: whitelistedUrls }); + return { + cliqzModuleData: updatedcliqzModuleData, + }; +} + export function trustRestrictBlockSiteTracker({ actionData, state }) { const { blocking, summary, settings } = state; const { pageHost } = summary; diff --git a/app/panel-android/actions/handler.js b/app/panel-android/actions/handler.js index f04da2a7a..09d1aca99 100644 --- a/app/panel-android/actions/handler.js +++ b/app/panel-android/actions/handler.js @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,11 +12,14 @@ */ import { - handleTrustButtonClick, handleRestrictButtonClick, handlePauseButtonClick, cliqzFeatureToggle + updateSitePolicy, handleTrustButtonClick, handleRestrictButtonClick, handlePauseButtonClick, cliqzFeatureToggle } from './summaryActions'; import { - trustRestrictBlockSiteTracker, blockUnblockGlobalTracker, blockUnBlockAllTrackers, resetSettings -} from './trackerActions'; + anonymizeSiteTracker, trustRestrictBlockSiteTracker, blockUnblockGlobalTracker, blockUnBlockAllTrackers, resetSettings +} from './blockingActions'; +import { + updateDatabase, updateSettingCheckbox, selectItem, exportSettings, importSettingsDialog, importSettingsNative +} from './settingsActions'; // Handle all actions in Panel.jsx export default function handleAllActions({ actionName, actionData, state }) { @@ -32,7 +35,7 @@ export default function handleAllActions({ actionName, actionData, state }) { break; case 'handlePauseButtonClick': - updated = handlePauseButtonClick({ state }); + updated = handlePauseButtonClick({ actionData, state }); break; case 'cliqzFeatureToggle': @@ -43,6 +46,10 @@ export default function handleAllActions({ actionName, actionData, state }) { updated = trustRestrictBlockSiteTracker({ actionData, state }); break; + case 'anonymizeSiteTracker': + updated = anonymizeSiteTracker({ actionData, state }); + break; + case 'blockUnblockGlobalTracker': updated = blockUnblockGlobalTracker({ actionData, state }); break; @@ -55,6 +62,34 @@ export default function handleAllActions({ actionName, actionData, state }) { updated = resetSettings({ state }); break; + case 'updateSitePolicy': + updated = updateSitePolicy({ actionData, state }); + break; + + case 'updateDatabase': + updated = updateDatabase({ actionData, state }); + break; + + case 'updateSettingCheckbox': + updated = updateSettingCheckbox({ actionData, state }); + break; + + case 'selectItem': + updated = selectItem({ actionData, state }); + break; + + case 'exportSettings': + updated = exportSettings({ actionData, state }); + break; + + case 'importSettingsDialog': + updated = importSettingsDialog({ actionData, state }); + break; + + case 'importSettingsNative': + updated = importSettingsNative({ actionData, state }); + break; + default: updated = {}; } diff --git a/app/panel-android/actions/settingsActions.js b/app/panel-android/actions/settingsActions.js new file mode 100644 index 000000000..c850cc94e --- /dev/null +++ b/app/panel-android/actions/settingsActions.js @@ -0,0 +1,186 @@ +/** + * Tracker Action creators + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import moment from 'moment/min/moment-with-locales.min'; +import { sendMessage, sendMessageInPromise } from '../../panel/utils/msg'; +import { hashCode } from '../../../src/utils/common'; + +// Function taken from app/content-scripts/notifications.js +function _saveToFile({ content, type }) { + const textFileAsBlob = new Blob([content], { type: 'text/plain' }); + const ext = type === 'Ghostery-Backup' ? 'ghost' : 'json'; + const d = new Date(); + const dStr = `${d.getMonth() + 1}-${d.getDate()}-${d.getFullYear()}`; + const fileNameToSaveAs = `${type}-${dStr}.${ext}`; + let url = ''; + if (window.URL) { + url = window.URL.createObjectURL(textFileAsBlob); + } else { + url = window.webkitURL.createObjectURL(textFileAsBlob); + } + + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', fileNameToSaveAs); + document.body.appendChild(link); + link.click(); +} + +function _importFromFile(fileToLoad) { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = (fileLoadedEvent) => { + try { + const backup = JSON.parse(fileLoadedEvent.target.result); + if (backup.hash !== hashCode(JSON.stringify(backup.settings))) { + throw new Error('Invalid hash'); + } + const settings = (backup.settings || {}).conf || {}; + resolve(settings); + } catch (err) { + reject(err); + } + }; + fileReader.readAsText(fileToLoad, 'UTF-8'); + }); +} + +function _chooseFile() { + return new Promise((resolve) => { + const inputEl = document.createElement('input'); + inputEl.type = 'file'; + inputEl.addEventListener('change', () => { + resolve(inputEl.files[0]); + }); + inputEl.click(); + }); +} + +export function updateDatabase() { + // Send Message to Background + return sendMessageInPromise('update_database').then((result) => { + let resultText; + if (result && result.success === true) { + if (result.updated === true) { + resultText = t('settings_update_success'); + } else { + resultText = t('settings_update_up_to_date'); + } + } else { + resultText = t('settings_update_failed'); + } + + // Update State for PanelAndroid UI + return { + settings: { + dbUpdateText: resultText, + ...result.confData, + } + }; + }); +} + +export function updateSettingCheckbox({ actionData }) { + const { name, checked } = actionData; + const updatedState = {}; + + if (name === 'trackers_banner_status' || name === 'reload_banner_status') { + updatedState.panel = { [name]: checked }; + } else if (name === 'toggle_individual_trackers') { + updatedState.blocking = { [name]: checked }; + updatedState.settings = { [name]: checked }; + } else { + updatedState.settings = { [name]: checked }; + } + + // Send Message to Background + sendMessage('setPanelData', { [name]: checked }); + + // Update State for PanelAndroid UI + return updatedState; +} + +export function selectItem({ actionData }) { + const { event, value } = actionData; + + // Send Message to Background + sendMessage('setPanelData', { [event]: value }); + + // Update State for PanelAndroid UI + return { + settings: { + [event]: value, + }, + }; +} + +export function exportSettings({ state }) { + return sendMessageInPromise('getAndroidSettingsForExport').then((result) => { + const { needsReload } = state; + const settings_last_exported = Number((new Date()).getTime()); + const exportResultText = `${t('settings_export_success')} ${moment(settings_last_exported).format('LLL')}`; + + _saveToFile(result); + + return { + needsReload, + settings: { + actionSuccess: true, + settings_last_exported, + exportResultText, + } + }; + }); +} + +export function importSettingsNative({ actionData, state }) { + return new Promise((resolve) => { + const { needsReload } = state; + const settings_last_imported = Number((new Date()).getTime()); + const importResultText = `${t('settings_import_success')} ${moment(settings_last_imported).format('LLL')}`; + + _importFromFile(actionData).then((settings) => { + // Taken from panel/reducers/settings.js + const imported_settings = { ...settings }; + if (imported_settings.hasOwnProperty('alert_bubble_timeout')) { + imported_settings.alert_bubble_timeout = Math.min(30, imported_settings.alert_bubble_timeout); + } + + imported_settings.settings_last_imported = Number((new Date()).getTime()); + imported_settings.importResultText = `${t('settings_import_success')} ${moment(imported_settings.settings_last_imported).format('LLL')}`; + imported_settings.actionSuccess = true; + + // persist to background + sendMessage('setPanelData', imported_settings); + + return resolve({ + needsReload: true, + settings: { + actionSuccess: true, + settings_last_imported, + importResultText, + } + }); + }).catch(() => resolve({ + needsReload, + settings: { + actionSuccess: false, + importResultText: t('settings_import_file_error'), + } + })); + }); +} + +export function importSettingsDialog({ state }) { + return _chooseFile().then(fileToLoad => importSettingsNative({ actionData: fileToLoad, state })); +} diff --git a/app/panel-android/actions/summaryActions.js b/app/panel-android/actions/summaryActions.js index 401c8bb2a..fbf2103ad 100644 --- a/app/panel-android/actions/summaryActions.js +++ b/app/panel-android/actions/summaryActions.js @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -18,6 +18,57 @@ function getPageHostFromSummary(summary) { return summary.pageHost.toLowerCase().replace(/^(http[s]?:\/\/)?(www\.)?/, ''); } +export function updateSitePolicy({ actionData, state }) { + const { type, pageHost } = actionData; + const { summary } = state; + const { site_blacklist, site_whitelist } = summary; + + const host = pageHost.replace(/^www\./, ''); + + let updated_blacklist = site_blacklist.slice(0); + let updated_whitelist = site_whitelist.slice(0); + + if (type === 'whitelist') { + if (site_blacklist.includes(host)) { + // remove from backlist if site is whitelisted + updated_blacklist = removeFromArray(site_blacklist, site_blacklist.indexOf(host)); + } + if (!site_whitelist.includes(host)) { + // add to whitelist + updated_whitelist = addToArray(site_whitelist, host); + } else { + // remove from whitelist + updated_whitelist = removeFromArray(site_whitelist, site_whitelist.indexOf(host)); + } + } else { + if (site_whitelist.includes(host)) { + // remove from whitelisted if site is blacklisted + updated_whitelist = removeFromArray(site_whitelist, site_whitelist.indexOf(host)); + } + if (!site_blacklist.includes(host)) { + // add to blacklist + updated_blacklist = addToArray(site_blacklist, host); + } else { + // remove from blacklist + updated_blacklist = removeFromArray(site_blacklist, site_blacklist.indexOf(host)); + } + } + + // Send Message to Background + sendMessage('setPanelData', { + site_whitelist: updated_whitelist, + site_blacklist: updated_blacklist, + }); + + // Update State for PanelAndroid UI + return { + summary: { + site_whitelist: updated_whitelist, + site_blacklist: updated_blacklist, + }, + }; +} + export function handleTrustButtonClick({ state }) { const { summary } = state; // This pageHost has to be cleaned. @@ -101,24 +152,24 @@ export function handleRestrictButtonClick({ state }) { }; } -export function handlePauseButtonClick({ state }) { - const { summary } = state; - const currentState = summary.paused_blocking; +export function handlePauseButtonClick({ actionData }) { + const { paused_blocking, time } = actionData; sendMessage('setPanelData', { - paused_blocking: !currentState, + paused_blocking: time || paused_blocking, }); return { summary: { - paused_blocking: !currentState, - } + paused_blocking, + paused_blocking_timeout: time, + }, }; } export function cliqzFeatureToggle({ actionData }) { const { currentState, type } = actionData; - const key = `enable_${type}`; + const key = type; sendMessage('setPanelData', { [key]: !currentState, diff --git a/app/panel-android/components/GlobalTrackers.jsx b/app/panel-android/components/GlobalTrackers.jsx deleted file mode 100644 index e2f3e034f..000000000 --- a/app/panel-android/components/GlobalTrackers.jsx +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Global Trackers Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import Accordions from './content/Accordions'; -import DotsMenu from './content/DotsMenu'; - -export default class GlobalTrackers extends React.Component { - actions = [ - { - id: 'blockAllGlobal', - name: 'Block All', - callback: () => { - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'blockUnBlockAllTrackers', - actionData: { - block: true, - type: 'global', - } - }); - }, - }, - { - id: 'unblockAllGlobal', - name: 'Unblock All', - callback: () => { - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'blockUnBlockAllTrackers', - actionData: { - block: false, - type: 'global', - } - }); - }, - }, - { - id: 'resetSettings', - name: 'Reset Settings', - callback: () => { - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'resetSettings', - }); - }, - } - ]; - - get categories() { - const { categories } = this.props; - return categories; - } - - render() { - return ( -
-
-

Global Trackers

- -
- -
- ); - } -} - -GlobalTrackers.propTypes = { - categories: PropTypes.arrayOf(PropTypes.object), -}; - -GlobalTrackers.defaultProps = { - categories: [], -}; - -GlobalTrackers.contextTypes = { - callGlobalAction: PropTypes.func, -}; diff --git a/app/panel-android/components/Overview.jsx b/app/panel-android/components/Overview.jsx deleted file mode 100644 index bb5aea0dd..000000000 --- a/app/panel-android/components/Overview.jsx +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Overview Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import TrackersChart from './content/TrackersChart'; -import fromTrackersToChartData from '../utils/chart'; - -export default class Overview extends React.Component { - get isTrusted() { - const { siteProps } = this.context; - return siteProps.isTrusted; - } - - get isRestricted() { - const { siteProps } = this.context; - return siteProps.isRestricted; - } - - get isPaused() { - const { siteProps } = this.context; - return siteProps.isPaused; - } - - get categories() { - const { categories } = this.props; - return categories || []; - } - - get chartData() { - const trackers = this.categories.map(category => ({ - id: category.id, - numTotal: category.num_total, - })); - - return fromTrackersToChartData(trackers); - } - - get hostName() { - const { siteProps } = this.context; - return siteProps.hostName; - } - - get nTrackersBlocked() { - const { siteProps } = this.context; - return siteProps.nTrackersBlocked; - } - - handleTrustButtonClick = () => { - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'handleTrustButtonClick', - }); - } - - handleRestrictButtonClick = () => { - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'handleRestrictButtonClick', - }); - } - - handlePauseButtonClick = () => { - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'handlePauseButtonClick', - }); - } - - render() { - return ( -
-
- -

{this.hostName}

-

- - {this.nTrackersBlocked} - {' '} - - Trackers blocked -

-
- -
-
- -
-
- -
-
- -
-
-
- ); - } -} - -Overview.propTypes = { - categories: PropTypes.arrayOf(PropTypes.shape), -}; - -Overview.defaultProps = { - categories: [], -}; - -Overview.contextTypes = { - siteProps: PropTypes.shape, - callGlobalAction: PropTypes.func, -}; diff --git a/app/panel-android/components/Panel.jsx b/app/panel-android/components/Panel.jsx deleted file mode 100644 index 93d408c6e..000000000 --- a/app/panel-android/components/Panel.jsx +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Panel Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import Tabs from './content/Tabs'; -import Tab from './content/Tab'; -import Overview from './Overview'; -import FixedMenu from './content/FixedMenu'; -import SiteTrackers from './SiteTrackers'; -import GlobalTrackers from './GlobalTrackers'; -import TrackersChart from './content/TrackersChart'; -import { - getPanelData, getSummaryData, getSettingsData, getBlockingData -} from '../actions/panelActions'; -import getCliqzModuleData from '../actions/cliqzActions'; -import handleAllActions from '../actions/handler'; -import fromTrackersToChartData from '../utils/chart'; - -export default class Panel extends React.Component { - constructor(props) { - super(props); - this.state = { - panel: {}, - summary: {}, - settings: {}, - blocking: {}, - cliqzModuleData: {}, - }; - } - - getChildContext = () => ({ - siteProps: this.siteProps, - callGlobalAction: this.callGlobalAction, - }); - - componentDidMount() { - const tabId = new URLSearchParams(window.location.search).get('tabId'); - this.setPanelState(tabId); - this.setSummaryState(tabId); - this.setSettingsState(); - this.setBlockingState(tabId); - this.setCliqzDataState(tabId); - } - - get siteCategories() { - const { blocking } = this.state; - return blocking.categories || []; - } - - get globalCategories() { - const { settings } = this.state; - return settings.categories || []; - } - - get chartData() { - const trackers = this.siteCategories.map(category => ({ - id: category.id, - numTotal: category.num_total, - })); - - return fromTrackersToChartData(trackers); - } - - get siteProps() { - const { summary } = this.state; - const hostName = summary.pageHost || ''; - const pageHost = hostName.toLowerCase().replace(/^(http[s]?:\/\/)?(www\.)?/, ''); - - const siteWhitelist = summary.site_whitelist || []; - const siteBlacklist = summary.site_blacklist || []; - - const isTrusted = siteWhitelist.indexOf(pageHost) !== -1; - const isRestricted = siteBlacklist.indexOf(pageHost) !== -1; - const isPaused = summary.paused_blocking; - - const nTrackersBlocked = (summary.trackerCounts || {}).blocked || 0; - - return { - hostName, pageHost, isTrusted, isRestricted, isPaused, nTrackersBlocked - }; - } - - setPanelState = (tabId) => { - getPanelData(tabId).then((data) => { - this.setState({ - panel: data.panel, - }); - }); - } - - setSummaryState = (tabId) => { - getSummaryData(tabId).then((data) => { - this.setState({ - summary: data, - }); - }); - } - - setSettingsState = () => { - getSettingsData().then((data) => { - this.setState({ - settings: data, - }); - }); - } - - setBlockingState = (tabId) => { - getBlockingData(tabId).then((data) => { - this.setState({ - blocking: data, - }); - }); - } - - setCliqzDataState = (tabId) => { - getCliqzModuleData(tabId).then((data) => { - this.setState({ - cliqzModuleData: data, - }); - }); - } - - setGlobalState = (updated) => { - const newState = {}; - Object.keys(updated).forEach((key) => { - newState[key] = { ...this.state[key], ...updated[key] }; // eslint-disable-line react/destructuring-assignment - }); - - this.setState(newState); - } - - callGlobalAction = ({ actionName, actionData = {} }) => { - const updated = handleAllActions({ actionName, actionData, state: this.state }); - if (Object.keys(updated).length !== 0) { - this.setGlobalState(updated); - } - } - - render() { - const { panel, cliqzModuleData } = this.state; - return ( -
-
- -

{this.siteProps.hostName}

-

- - {this.siteProps.nTrackersBlocked} - {' '} - - Trackers blocked -

-
- - - - - - - - - - - - - - -
- ); - } -} - -Panel.childContextTypes = { - siteProps: PropTypes.shape, - callGlobalAction: PropTypes.func, -}; diff --git a/app/panel-android/components/PanelAndroid.jsx b/app/panel-android/components/PanelAndroid.jsx new file mode 100644 index 000000000..0dbd1bfa2 --- /dev/null +++ b/app/panel-android/components/PanelAndroid.jsx @@ -0,0 +1,288 @@ +/** + * Panel Android Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import ClassNames from 'classnames'; +import Settings from './content/Settings'; +import Tabs from './content/Tabs'; +import Tab from './content/Tab'; +import OverviewTab from './content/OverviewTab'; +import BlockingTab from './content/BlockingTab'; +import { + getPanelData, getSummaryData, getSettingsData, getBlockingData +} from '../actions/panelActions'; +import getCliqzModuleData from '../actions/cliqzActions'; +import handleAllActions from '../actions/handler'; +import { sendMessage, openAccountPageAndroid } from '../../panel/utils/msg'; + +class PanelAndroid extends React.Component { + constructor(props) { + super(props); + + this.state = { + needsReload: false, + view: 'overview', + panel: { + enable_ad_block: false, + enable_anti_tracking: false, + enable_smart_block: false, + smartBlock: { blocked: {}, unblocked: {} }, + }, + summary: { + categories: [], + trackerCounts: { + allowed: 0, + blocked: 0, + }, + sitePolicy: false, + paused_blocking: false, + }, + settings: {}, + blocking: { + siteNotScanned: false, + pageUrl: '', + categories: [], + }, + cliqzModuleData: { + adBlock: { trackerCount: 0, unknownTrackers: [] }, + antiTracking: { trackerCount: 0, unknownTrackers: [] }, + }, + }; + } + + componentDidMount() { + const tabId = new URLSearchParams(window.location.search).get('tabId'); + this.setPanelState(tabId); + this.setSummaryState(tabId); + this.setSettingsState(); + this.setBlockingState(tabId); + this.setCliqzDataState(tabId); + } + + get siteProps() { + const { summary } = this.state; + const hostName = summary.pageHost || ''; + const pageHost = hostName.toLowerCase().replace(/^(http[s]?:\/\/)?(www\.)?/, ''); + + const { + site_whitelist = [], + site_blacklist = [], + trackerCounts = {} + } = summary; + + const isTrusted = site_whitelist.indexOf(pageHost) !== -1; + const isRestricted = site_blacklist.indexOf(pageHost) !== -1; + const isPaused = summary.paused_blocking; + + const nTrackersBlocked = trackerCounts.blocked || 0; + + return { + hostName, pageHost, isTrusted, isRestricted, isPaused, nTrackersBlocked + }; + } + + setPanelState = (tabId) => { + getPanelData(tabId).then((data) => { + this.setState(prevState => ({ + panel: data.panel, + settings: { + ...prevState.settings, + reload_banner_status: data.panel.reload_banner_status, + trackers_banner_status: data.panel.trackers_banner_status, + } + })); + }); + } + + setSummaryState = (tabId) => { + getSummaryData(tabId).then((data) => { + this.setState({ summary: data }); + }); + } + + setSettingsState = () => { + getSettingsData().then((data) => { + this.setState(prevState => ({ + settings: { + ...prevState.settings, + ...data, + dbUpdateText: t('settings_update_now'), + } + })); + }); + } + + setBlockingState = (tabId) => { + getBlockingData(tabId).then((data) => { + this.setState({ blocking: data }); + }); + } + + setCliqzDataState = (tabId) => { + getCliqzModuleData(tabId).then((data) => { + this.setState({ cliqzModuleData: data }); + }); + } + + setGlobalState = (updated) => { + const newState = { needsReload: true }; + Object.keys(updated).forEach((key) => { + newState[key] = { ...this.state[key], ...updated[key] }; // eslint-disable-line react/destructuring-assignment + }); + + if (updated.needsReload === false) { + newState.needsReload = false; + } + + this.setState(newState); + } + + callGlobalAction = ({ actionName, actionData = {} }) => { + const updated = handleAllActions({ actionName, actionData, state: this.state }); + if (updated instanceof Promise) { + updated.then((result) => { + if (Object.keys(result).length !== 0) { + this.setGlobalState(result); + } + }); + } else if (Object.keys(updated).length !== 0) { + this.setGlobalState(updated); + } + } + + changeView = (newView) => { + this.setState({ view: newView }); + } + + massageCliqzTrackers = tracker => ({ + id: tracker.name, + catId: tracker.type, + cliqzAdCount: tracker.ads, + cliqzCookieCount: tracker.cookies, + cliqzFingerprintCount: tracker.fingerprints, + name: tracker.name, + sources: tracker.domains, + whitelisted: tracker.whitelisted, + blocked: false, // To appease BlockingTracker PropTypes + wtm: tracker.wtm, + }) + + reloadTab = () => { + const { panel } = this.state; + sendMessage('reloadTab', { tab_id: +panel.tab_id }); + window.close(); + } + + _renderSettings() { + const { summary, settings } = this.state; + + return ( + { this.changeView('overview'); }} + callGlobalAction={this.callGlobalAction} + /> + ); + } + + _renderOverview() { + const { + panel, + blocking, + summary, + settings, + cliqzModuleData, + } = this.state; + const { categories, toggle_individual_trackers } = blocking; + const { adBlock, antiTracking } = cliqzModuleData; + + const unknownTrackers = Array.from(new Set([ + ...antiTracking.unknownTrackers.map(this.massageCliqzTrackers), + ...adBlock.unknownTrackers.map(this.massageCliqzTrackers), + ])).sort((a, b) => { + const nameA = a.name.toLowerCase(); + const nameB = b.name.toLowerCase(); + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + return 0; + }); + const unknownCategory = { + id: 'unknown', + name: t('unknown'), + description: t('unknown_description'), + img_name: 'unknown', + num_total: unknownTrackers.length, + num_blocked: 0, // We don't want to see the Trackers Blocked text + trackers: unknownTrackers, + }; + + return ( + + + { this.changeView('settings'); }} + callGlobalAction={this.callGlobalAction} + /> + + + + + + + + + + + ); + } + + render() { + const { needsReload, view } = this.state; + const needsReloadClassNames = ClassNames('NeedsReload flex-container align-center-middle', { + 'NeedsReload--show': needsReload, + }); + + return ( +
+
+ {t('alert_reload')} +
+ {view === 'settings' && this._renderSettings()} + {view === 'overview' && this._renderOverview()} +
+ ); + } +} + +export default PanelAndroid; diff --git a/app/panel-android/components/SiteTrackers.jsx b/app/panel-android/components/SiteTrackers.jsx deleted file mode 100644 index be4937e1a..000000000 --- a/app/panel-android/components/SiteTrackers.jsx +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Site Trackers Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import Accordions from './content/Accordions'; -import DotsMenu from './content/DotsMenu'; - -export default class SiteTrackers extends React.Component { - actions = [ - { - id: 'blockAllSite', - name: 'Block All', - callback: () => { - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'blockUnBlockAllTrackers', - actionData: { - block: true, - type: 'site', - } - }); - }, - }, - { - id: 'unblockAllSite', - name: 'Unblock All', - callback: () => { - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'blockUnBlockAllTrackers', - actionData: { - block: false, - type: 'site', - } - }); - }, - } - ] - - render() { - const { categories } = this.props; - return ( -
-
-

Trackers on this site

- -
- -
- ); - } -} - -SiteTrackers.propTypes = { - categories: PropTypes.arrayOf(PropTypes.object), -}; - -SiteTrackers.defaultProps = { - categories: [], -}; - -SiteTrackers.contextTypes = { - callGlobalAction: PropTypes.func, -}; diff --git a/app/panel-android/components/content/Accordion.jsx b/app/panel-android/components/content/Accordion.jsx deleted file mode 100644 index aaa463d6a..000000000 --- a/app/panel-android/components/content/Accordion.jsx +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Accordion Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -import TrackerItem from './TrackerItem'; - -export default class Accordion extends React.Component { - itemHeight = 50; - - nExtraItems = 40; - - headerheight = 32; - - constructor(props) { - super(props); - this.myRef = React.createRef(); - - this.state = { - isActive: false, - openMenuIndex: -1, - currentItemsLength: 0, - }; - - this.isWaiting = false; - this.unMounted = false; - } - - componentDidMount() { - window.addEventListener('scroll', this.handleScroll); - } - - componentWillUnmount() { - this.unMounted = true; - window.removeEventListener('scroll', this.handleScroll); - } - - get blockingStatus() { - const { type, numBlocked, numTotal } = this.props; - const { siteProps } = this.context; - if (type === 'site-trackers') { - if (siteProps.isTrusted) { - return 'trusted'; - } - - if (siteProps.isRestricted) { - return 'restricted'; - } - - const trackers = this.getTrackers(true); - if (trackers.every(tracker => tracker.ss_allowed)) { - return 'trusted'; - } - - if (trackers.every(tracker => tracker.ss_blocked)) { - return 'restricted'; - } - - if (trackers.some(tracker => tracker.ss_allowed || tracker.ss_blocked)) { - return 'mixed'; - } - } - - if (numBlocked === numTotal) { - return 'blocked'; - } - - return ''; - } - - getTrackers = (force = false) => { - const { id, getTrackersFromCategory } = this.props; - const { isActive } = this.state; - if (!isActive && !force) { - return []; - } - - return getTrackersFromCategory(id); - } - - getMenuOpenStatus = (index) => { - const { openMenuIndex } = this.state; - return index === openMenuIndex; - } - - checkAndUpdateData = () => { - const { numTotal } = this.props; - const { isActive, currentItemsLength } = this.state; - if (this.unMounted || !isActive || currentItemsLength >= numTotal) { - return; - } - - const needToUpdateHeight = this.nExtraItems * this.itemHeight; // Update even before the bottom is visible - - const scrollTop = window.pageYOffset || document.documentElement.scrollTop; - const accordionContentNode = this.myRef.current; - const boundingRect = accordionContentNode.getBoundingClientRect(); - // Try lo load more when needed - if (scrollTop + window.innerHeight - (accordionContentNode.offsetTop + boundingRect.height) > -needToUpdateHeight) { - this.setState((prevState) => { - const itemsLength = Math.min(prevState.currentItemsLength + this.nExtraItems, numTotal); - return { currentItemsLength: itemsLength }; - }); - } - } - - toggleMenu = (index) => { - const { openMenuIndex } = this.state; - if (openMenuIndex === index) { - this.setState({ openMenuIndex: -1 }); - } else { - this.setState({ openMenuIndex: index }); - } - } - - handleScroll = () => { - // Don't call the checkAndUpdateData function so many times. Use throttle - if (this.isWaiting) { - return; - } - - this.isWaiting = true; - - setTimeout(() => { - this.isWaiting = false; - this.checkAndUpdateData(); - }, 200); - } - - toggleContent = () => { - const { index, toggleAccordion, numTotal } = this.props; - const { isActive } = this.state; - toggleAccordion(index); - - // Show some trackers when this category is expanded - const currentState = isActive; - const itemsLength = Math.min(this.nExtraItems, numTotal); - this.setState({ - isActive: !currentState, - currentItemsLength: itemsLength, - }); - } - - handleCategoryClicked = () => { - const { id, type } = this.props; - const { callGlobalAction } = this.context; - if (!this.blockingStatus) { - const blockingType = type === 'site-trackers' ? 'site' : 'global'; - callGlobalAction({ - actionName: 'blockUnBlockAllTrackers', - actionData: { - block: true, - type: blockingType, - categoryId: id, - } - }); - } else if (this.blockingStatus === 'blocked') { - const blockingType = type === 'site-trackers' ? 'site' : 'global'; - callGlobalAction({ - actionName: 'blockUnBlockAllTrackers', - actionData: { - block: false, - type: blockingType, - categoryId: id, - } - }); - } - } - - render() { - const { - index, - open, - numBlocked, - name, - numTotal, - logo, - id, - type - } = this.props; - const { isActive, currentItemsLength } = this.state; - const titleStyle = { backgroundImage: `url(/app/images/panel-android/categories/${logo}.svg)` }; - const contentStyle = { '--trackers-length': `${open ? (currentItemsLength * this.itemHeight) + this.headerheight : 0}px` }; - - return ( -
- -
-

{name}

-

- - {numTotal} - {' '} - TRACKERS - - {!!numBlocked && ( - - {numBlocked} - {' '} - Blocked - - )} -

-

- On this site -

-
-
-

- TRACKERS - Blocked -

-
    - {this.getTrackers(true).slice(0, currentItemsLength).map((tracker, ind) => ( - - ))} -
-
-
- ); - } -} - -Accordion.propTypes = { - toggleAccordion: PropTypes.func.isRequired, - index: PropTypes.number.isRequired, - getTrackersFromCategory: PropTypes.func.isRequired, - open: PropTypes.bool, - numBlocked: PropTypes.number, - name: PropTypes.string, - numTotal: PropTypes.number, - logo: PropTypes.string, - id: PropTypes.string, - type: PropTypes.string, -}; - -Accordion.defaultProps = { - open: false, - numBlocked: 0, - name: '', - numTotal: 0, - logo: '', - id: '', - type: '', -}; - -Accordion.contextTypes = { - siteProps: PropTypes.shape, - callGlobalAction: PropTypes.func, -}; diff --git a/app/panel-android/components/content/Accordions.jsx b/app/panel-android/components/content/Accordions.jsx deleted file mode 100644 index 99e4b7347..000000000 --- a/app/panel-android/components/content/Accordions.jsx +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Accordions Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -import Accordion from './Accordion'; - -class Accordions extends React.Component { - constructor(props) { - super(props); - this.state = { - openAccordionIndex: -1, - }; - } - - getOpenStatus = (index) => { - const { openAccordionIndex } = this.state; - return index === openAccordionIndex; - } - - getTrackersFromCategory = (categoryId) => { - const { categories } = this.props; - const category = categories[categories.findIndex(cat => cat.id === categoryId)]; - return category.trackers; - } - - toggleAccordion = (index) => { - const { openAccordionIndex } = this.state; - if (openAccordionIndex === index) { - this.setState({ openAccordionIndex: -1 }); - } else { - this.setState({ openAccordionIndex: index }); - } - } - - render() { - const { categories, type } = this.props; - return ( -
- { - categories.map((category, index) => ( - - )) - } -
- ); - } -} - -Accordions.propTypes = { - categories: PropTypes.arrayOf(PropTypes.object), - type: PropTypes.string, -}; - -Accordions.defaultProps = { - categories: [], - type: '', -}; - -export default Accordions; diff --git a/app/panel-android/components/content/BlockingCategories.jsx b/app/panel-android/components/content/BlockingCategories.jsx new file mode 100644 index 000000000..f367b8dcf --- /dev/null +++ b/app/panel-android/components/content/BlockingCategories.jsx @@ -0,0 +1,97 @@ +/** + * Blocking Categories Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import BlockingCategory from './BlockingCategory'; + +class BlockingCategories extends React.Component { + constructor(props) { + super(props); + + this.state = { + openCategoryIndex: -1, + blockingType: props.type, + }; + } + + static getDerivedStateFromProps(props, state) { + const { type } = props; + const { blockingType } = state; + + if (type !== blockingType) { + return { + openCategoryIndex: -1, + blockingType: type, + }; + } + return null; + } + + getOpenStatus = (index) => { + const { openCategoryIndex } = this.state; + return index === openCategoryIndex; + } + + toggleCategoryOpen = (index) => { + const { openCategoryIndex } = this.state; + if (openCategoryIndex === index) { + this.setState({ openCategoryIndex: -1 }); + } else { + this.setState({ openCategoryIndex: index }); + } + } + + render() { + const { + categories, + type, + siteProps, + settings, + callGlobalAction, + } = this.props; + + return ( +
+ { + categories.map((category, index) => ( + + )) + } +
+ ); + } +} + +BlockingCategories.propTypes = { + categories: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + type: PropTypes.oneOf([ + 'site', + 'global', + ]).isRequired, + siteProps: PropTypes.shape({}).isRequired, + callGlobalAction: PropTypes.func.isRequired, + settings: PropTypes.shape({}).isRequired, +}; + +export default BlockingCategories; diff --git a/app/panel-android/components/content/BlockingCategory.jsx b/app/panel-android/components/content/BlockingCategory.jsx new file mode 100644 index 000000000..c0b0b7dac --- /dev/null +++ b/app/panel-android/components/content/BlockingCategory.jsx @@ -0,0 +1,292 @@ +/** + * Blocking Category Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import ClassNames from 'classnames'; +import { FixedSizeList as List } from 'react-window'; +import BlockingTracker from './BlockingTracker'; + +class BlockingCategory extends React.Component { + constructor(props) { + super(props); + + this.state = { + openTrackerIndex: -1, + }; + + this.heightTracker = 50; + this.heightListHeader = 30; + this.maxListHeight = 750; + } + + getListHeight(count) { + return Math.min(this.maxListHeight, count * this.heightTracker); + } + + getListHeightWithHeader(count) { + return this.heightListHeader + this.getListHeight(count); + } + + get categorySelectStatus() { + const { type, siteProps, category } = this.props; + const { trackers, num_total, num_blocked } = category; + + if (type === 'site') { + if (siteProps.isTrusted) { + return 'trusted'; + } + + if (siteProps.isRestricted) { + return 'restricted'; + } + + if (category.id === 'unknown') { + return 'unknown'; + } + + if (trackers.every(tracker => tracker.ss_allowed)) { + return 'trusted'; + } + + if (trackers.every(tracker => tracker.ss_blocked)) { + return 'restricted'; + } + + if (trackers.some(tracker => tracker.ss_allowed || tracker.ss_blocked)) { + return 'ss_mixed'; + } + } + + if (num_blocked && num_blocked === num_total) { + return 'blocked'; + } + + if (num_blocked && num_blocked !== num_total) { + return 'mixed'; + } + + return ''; + } + + get numTrackersText() { + const { category } = this.props; + const { num_total } = category; + + return `${num_total} ${(num_total === 1) ? t('blocking_category_tracker') : t('blocking_category_trackers')}`; + } + + get numBlockedText() { + const { category } = this.props; + const { num_blocked } = category; + + return `${num_blocked} ${t('blocking_category_blocked')}`; + } + + getTrackerOpenStatus = (index) => { + const { openTrackerIndex } = this.state; + return index === openTrackerIndex; + } + + toggleTrackerSelectOpen = (index) => { + const { openTrackerIndex } = this.state; + if (openTrackerIndex === index) { + this.setState({ openTrackerIndex: -1 }); + } else { + this.setState({ openTrackerIndex: index }); + } + } + + clickCategorySelect = (event) => { + event.stopPropagation(); + const { category, type, callGlobalAction } = this.props; + const { id } = category; + const selectStatus = this.categorySelectStatus; + + if (selectStatus === '' || selectStatus === 'mixed') { + callGlobalAction({ + actionName: 'blockUnBlockAllTrackers', + actionData: { + block: true, + categoryId: id, + type, + } + }); + } else if (selectStatus === 'blocked') { + callGlobalAction({ + actionName: 'blockUnBlockAllTrackers', + actionData: { + block: false, + categoryId: id, + type, + } + }); + } + } + + renderCategorySelect() { + const categorySelect = this.categorySelectStatus; + // Hide category blocking for Unknown trackers + if (categorySelect === 'unknown') { + return false; + } + const categorySelectClassNames = ClassNames('BlockingSelectButton', { + BlockingSelectButton__mixed: categorySelect === 'mixed' || categorySelect === 'ss_mixed', + BlockingSelectButton__blocked: categorySelect === 'blocked', + BlockingSelectButton__trusted: categorySelect === 'trusted', + BlockingSelectButton__restricted: categorySelect === 'restricted', + }); + + return ( +
+
+
+ ); + } + + renderToggleArrow() { + const { open } = this.props; + const toggleClassNames = ClassNames('BlockingCategory__toggle', { + 'BlockingCategory--open': open, + }); + + return ( +
+ ); + } + + renderBlockingTracker = ({ index, style }) => { + const { + category, + type, + siteProps, + settings, + callGlobalAction, + } = this.props; + const { id, trackers } = category; + const tracker = trackers[index]; + + return ( +
+ { this.toggleTrackerSelectOpen(tracker.id); }} + open={this.getTrackerOpenStatus(tracker.id)} + siteProps={siteProps} + settings={settings} + callGlobalAction={callGlobalAction} + /> +
+
+ ); + } + + render() { + const { openTrackerIndex } = this.state; + const { + index, + category, + open, + toggleCategoryOpen, + } = this.props; + const { + id, + name, + img_name, + num_total, + num_blocked, + } = category; + const categoryImage = `/app/images/panel-android/categories/${img_name}.svg`; + + const categoryClassNames = ClassNames('BlockingCategory', { + BlockingCategory__unknown: id === 'unknown', + }); + + return ( +
+
{ toggleCategoryOpen(index); }}> + +
+

{name}

+
+ {this.numTrackersText} + { !!num_blocked && ( + {this.numBlockedText} + )} +
+
+
+ {this.renderCategorySelect()} + {this.renderToggleArrow()} +
+
+
+ {open && ( +
+
+ {t('blocking_category_trackers')} + {category.id === 'unknown' ? t('android_anonymized') : t('blocking_category_blocked')} +
+ + {this.renderBlockingTracker} + +
+ )} +
+
+ ); + } +} + +BlockingCategory.propTypes = { + index: PropTypes.number.isRequired, + category: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + num_total: PropTypes.number.isRequired, + num_blocked: PropTypes.number.isRequired, + trackers: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]).isRequired, + ss_allowed: PropTypes.bool, + ss_blocked: PropTypes.bool, + })).isRequired, + img_name: PropTypes.string.isRequired, + }).isRequired, + open: PropTypes.bool.isRequired, + toggleCategoryOpen: PropTypes.func.isRequired, + type: PropTypes.oneOf([ + 'site', + 'global', + ]).isRequired, + siteProps: PropTypes.shape({ + isTrusted: PropTypes.bool.isRequired, + isRestricted: PropTypes.bool.isRequired, + isPaused: PropTypes.bool.isRequired, + }).isRequired, + settings: PropTypes.shape({}).isRequired, + callGlobalAction: PropTypes.func.isRequired, +}; + +export default BlockingCategory; diff --git a/app/panel-android/components/content/BlockingTab.jsx b/app/panel-android/components/content/BlockingTab.jsx new file mode 100644 index 000000000..a60566d59 --- /dev/null +++ b/app/panel-android/components/content/BlockingTab.jsx @@ -0,0 +1,134 @@ +/** + * Blocking Tab Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import DotsMenu from './DotsMenu'; +import BlockingCategories from './BlockingCategories'; + +class BlockingTab extends React.Component { + constructor(props) { + super(props); + + const { callGlobalAction } = props; + this.siteActions = [ + { + id: 'blockAllSite', + name: t('blocking_block_all'), + callback: () => { + callGlobalAction({ + actionName: 'blockUnBlockAllTrackers', + actionData: { block: true, type: 'site' } + }); + } + }, { + id: 'unblockAllSite', + name: t('blocking_unblock_all'), + callback: () => { + callGlobalAction({ + actionName: 'blockUnBlockAllTrackers', + actionData: { block: false, type: 'site' } + }); + } + } + ]; + this.globalActions = [ + { + id: 'blockAllGlobal', + name: t('blocking_block_all'), + callback: () => { + callGlobalAction({ + actionName: 'blockUnBlockAllTrackers', + actionData: { block: true, type: 'global' } + }); + } + }, { + id: 'unblockAllGlobal', + name: t('blocking_unblock_all'), + callback: () => { + callGlobalAction({ + actionName: 'blockUnBlockAllTrackers', + actionData: { block: false, type: 'global' } + }); + } + }, { + id: 'resetSettings', + name: t('android_blocking_reset'), + callback: () => { + callGlobalAction({ + actionName: 'resetSettings', + }); + } + } + ]; + } + + get actions() { + const { type } = this.props; + if (type === 'site') { + return this.siteActions; + } + return this.globalActions; + } + + get headerText() { + const { type } = this.props; + return (type === 'site') ? + t('android_site_blocking_header') : + t('android_global_blocking_header'); + } + + render() { + const { + type, + categories, + settings, + siteProps, + callGlobalAction, + } = this.props; + + return ( +
+
+

{this.headerText}

+ +
+ +
+ ); + } +} + +BlockingTab.propTypes = { + type: PropTypes.oneOf([ + 'site', + 'global', + ]).isRequired, + callGlobalAction: PropTypes.func.isRequired, + siteProps: PropTypes.shape({}).isRequired, + categories: PropTypes.arrayOf(PropTypes.shape({})), + settings: PropTypes.shape({}), +}; + +BlockingTab.defaultProps = { + categories: [], + settings: {}, +}; + +export default BlockingTab; diff --git a/app/panel-android/components/content/BlockingTracker.jsx b/app/panel-android/components/content/BlockingTracker.jsx new file mode 100644 index 000000000..501607a36 --- /dev/null +++ b/app/panel-android/components/content/BlockingTracker.jsx @@ -0,0 +1,421 @@ +/** + * Blocking Tracker Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import ClassNames from 'classnames'; +import getSlugFromTrackerId from '../../utils/tracker-info'; + +class BlockingTracker extends React.Component { + get trackerSelectStatus() { + const { type, siteProps, tracker } = this.props; + const { isTrusted, isRestricted } = siteProps; + const { + blocked, + catId = '', + ss_allowed = false, + ss_blocked = false, + warningSmartBlock = false, + } = tracker; + + if (type === 'site') { + if (warningSmartBlock) { + return 'override-sb'; + } + + if (isTrusted) { + return 'trusted'; + } + + if (isRestricted) { + return 'restricted'; + } + + if (ss_allowed) { + return 'trusted'; + } + + if (ss_blocked) { + return 'restricted'; + } + + if (blocked) { + return 'blocked'; + } + + if (catId !== '') { + return catId; + } + } + + if (blocked) { + return 'blocked'; + } + + return ''; + } + + get selectDisabled() { + const { type, siteProps } = this.props; + const { isTrusted, isRestricted, isPaused } = siteProps; + + if (type === 'site') { + return isTrusted || isRestricted || isPaused; + } + + return false; + } + + get selectBlockDisabled() { + const { tracker } = this.props; + const { ss_allowed = false, ss_blocked = false } = tracker; + + return this.selectDisabled || ss_allowed || ss_blocked; + } + + openTrackerInfoLink = (event) => { + event.stopPropagation(); + const { tracker } = this.props; + const slug = (tracker.wtm) ? tracker.wtm : getSlugFromTrackerId(tracker.id); + const tab = window.open(`https://whotracks.me/trackers/${slug}.html`, '_blank'); + tab.focus(); + } + + clickBlock = () => { + const { + type, + tracker, + categoryId, + callGlobalAction, + } = this.props; + const { id, blocked } = tracker; + + if (this.selectBlockDisabled) { + return; + } + + if (type === 'site') { + callGlobalAction({ + actionName: 'trustRestrictBlockSiteTracker', + actionData: { + app_id: id, + cat_id: categoryId, + block: !blocked, + trust: false, + restrict: false, + } + }); + } else if (type === 'global') { + callGlobalAction({ + actionName: 'blockUnblockGlobalTracker', + actionData: { + app_id: id, + cat_id: categoryId, + block: !blocked, + } + }); + } + } + + clickRestrict = () => { + const { tracker, categoryId, callGlobalAction } = this.props; + const { id, blocked, ss_blocked = false } = tracker; + + if (this.selectDisabled) { + return; + } + + callGlobalAction({ + actionName: 'trustRestrictBlockSiteTracker', + actionData: { + app_id: id, + cat_id: categoryId, + restrict: !ss_blocked, + trust: false, + block: blocked, // Keep blocking + } + }); + } + + clickTrust = () => { + const { tracker, categoryId, callGlobalAction } = this.props; + const { id, blocked, ss_allowed = false } = tracker; + + if (this.selectDisabled) { + return; + } + + callGlobalAction({ + actionName: 'trustRestrictBlockSiteTracker', + actionData: { + app_id: id, + cat_id: categoryId, + restrict: false, + trust: !ss_allowed, + block: blocked, // Keep blocking + } + }); + } + + clickAnonymize = () => { + const { tracker, callGlobalAction } = this.props; + + if (this.selectDisabled) { + return; + } + + callGlobalAction({ + actionName: 'anonymizeSiteTracker', + actionData: { + unknownTracker: tracker, + } + }); + } + + renderTrackerModified() { + const { type, tracker } = this.props; + const { cliqzAdCount, cliqzCookieCount, cliqzFingerprintCount } = tracker; + + if (type === 'global') { + return null; + } + + return ( +
+ {cliqzAdCount > 0 && ( + + {`${cliqzAdCount} ${cliqzAdCount === 1 ? t('ad') : t('ads')}`} + + )} + {cliqzCookieCount > 0 && ( + + {`${cliqzCookieCount} ${cliqzCookieCount === 1 ? t('cookie') : t('cookies')}`} + + )} + {cliqzFingerprintCount > 0 && ( + + {`${cliqzFingerprintCount} ${cliqzFingerprintCount === 1 ? t('fingerprint') : t('fingerprints')}`} + + )} +
+ ); + } + + renderTrackerStatus() { + const trackerSelect = this.trackerSelectStatus; + const trackerSelectClassNames = ClassNames({ + OverrideSmartBlock: trackerSelect === 'override-sb', + BlockingSelectButton: trackerSelect.indexOf('override-') === -1, + BlockingSelectButton__blocked: trackerSelect === 'blocked', + BlockingSelectButton__trusted: trackerSelect === 'trusted', + BlockingSelectButton__restricted: trackerSelect === 'restricted', + }); + + return ( +
+ ); + } + + renderUnknownTrackerStatus() { + const { siteProps, tracker } = this.props; + const trackerSelect = this.trackerSelectStatus; + const svgContainerClasses = ClassNames('UnknownSVGContainer', { + whitelisted: tracker.whitelisted && !siteProps.isRestricted, + siteRestricted: siteProps.isRestricted, + }); + const borderClassNames = ClassNames('border', { + protected: trackerSelect === 'antiTracking', + restricted: trackerSelect !== 'antiTracking', + }); + const backgroundClassNames = ClassNames('background', { + protected: trackerSelect === 'antiTracking', + restricted: trackerSelect !== 'antiTracking', + }); + + return ( +
+ + + + + + + + + + + + + + {trackerSelect === 'antiTracking' ? ( + + + + + + ) : ( + + + + + + + )} + + +
+ ); + } + + renderSmartBlockOverflow() { + const { open, tracker } = this.props; + const { warningSmartBlock } = tracker; + const selectGroupClassNames = ClassNames('OverrideText full-height', + 'flex-container align-center-middle', { + 'OverrideText--open': open, + }); + const text = (warningSmartBlock && warningSmartBlock === 'blocked') ? + t('panel_tracker_warning_smartblock_tooltip') : + t('panel_tracker_warning_smartunblock_tooltip'); + + return ( +
+ {text} +
+ ); + } + + renderBlockingOverflow() { + const { + type, + open, + tracker, + settings, + } = this.props; + const { ss_allowed = false, ss_blocked = false, blocked } = tracker; + const { toggle_individual_trackers = false } = settings; + + const selectGroupClassNames = ClassNames('BlockingSelectGroup full-height', + 'flex-container flex-dir-row-reverse', { + 'BlockingSelectGroup--open': open, + 'BlockingSelectGroup--wide': type === 'site' && toggle_individual_trackers, + 'BlockingSelectGroup--disabled': this.selectDisabled, + }); + const selectBlockClassNames = ClassNames('BlockingSelect BlockingSelect__block', + 'full-height flex-child-grow', { + 'BlockingSelect--disabled': this.selectBlockDisabled, + }); + + return ( +
+
+ {blocked ? t('android_unblock') : t('android_block')} +
+ {type === 'site' && toggle_individual_trackers && ( +
+ {ss_blocked ? t('android_unrestrict') : t('android_restrict')} +
+ )} + {type === 'site' && toggle_individual_trackers && ( +
+ {ss_allowed ? t('android_untrust') : t('android_trust')} +
+ )} +
+ ); + } + + renderUnknownOverflow() { + const { + open, + tracker, + } = this.props; + const { whitelisted } = tracker; + + const selectGroupClassNames = ClassNames('BlockingSelectGroup full-height', + 'flex-container flex-dir-row-reverse', { + 'BlockingSelectGroup--open': open, + 'BlockingSelectGroup--disabled': this.selectDisabled, + }); + + return ( +
+
+ {whitelisted ? t('android_anonymize') : t('android_trust')} +
+
+ ); + } + + renderTrackerOverflow() { + const trackerSelect = this.trackerSelectStatus; + if (trackerSelect === 'antiTracking' || trackerSelect === 'adBlock') { + return this.renderUnknownOverflow(); + } + if (trackerSelect === 'override-sb') { + return this.renderSmartBlockOverflow(); + } + + return this.renderBlockingOverflow(); + } + + render() { + const trackerSelect = this.trackerSelectStatus; + const { index, tracker, toggleTrackerSelectOpen } = this.props; + const { name } = tracker; + + return ( +
{ toggleTrackerSelectOpen(index); }}> +
+
+
+
+
{name}
+ {this.renderTrackerModified()} +
+ {(trackerSelect === 'antiTracking' || trackerSelect === 'adBlock') ? this.renderUnknownTrackerStatus() : this.renderTrackerStatus()} + {this.renderTrackerOverflow()} +
+ ); + } +} + +BlockingTracker.propTypes = { + index: PropTypes.number.isRequired, + tracker: PropTypes.shape({ + id: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]).isRequired, + name: PropTypes.string.isRequired, + ss_allowed: PropTypes.bool, + ss_blocked: PropTypes.bool, + blocked: PropTypes.bool.isRequired, + }).isRequired, + categoryId: PropTypes.string.isRequired, + type: PropTypes.oneOf([ + 'site', + 'global', + ]).isRequired, + toggleTrackerSelectOpen: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, + siteProps: PropTypes.shape({ + isTrusted: PropTypes.bool.isRequired, + isRestricted: PropTypes.bool.isRequired, + isPaused: PropTypes.bool.isRequired, + }).isRequired, + settings: PropTypes.shape({}).isRequired, + callGlobalAction: PropTypes.func.isRequired, +}; + +export default BlockingTracker; diff --git a/app/panel-android/components/content/ChartSVG.jsx b/app/panel-android/components/content/ChartSVG.jsx deleted file mode 100644 index ffff1c305..000000000 --- a/app/panel-android/components/content/ChartSVG.jsx +++ /dev/null @@ -1,77 +0,0 @@ -/** - * ChartSVG Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import Path from './Path'; - -export default class ChartSVG extends React.Component { - constructor(props) { - super(props); - this.state = { - nItem: 1, - }; - } - - increaseN = () => { - this.setState((prevState) => { - const { paths } = this.props; - if (prevState.nItem < paths.length) { - return { nItem: prevState.nItem + 1 }; - } - return null; - }); - } - - render() { - const { paths, radius } = this.props; - const { nItem } = this.state; - let computedPaths = paths.slice(0, nItem).map(element => ( - - )); - - if (computedPaths.length === 0) { - // When there is no tracker - const defaultElement = { - start: 0, - end: 360, - category: 'default', - }; - - computedPaths = ( - - ); - } - - return ( - - - {computedPaths} - - - ); - } -} - -ChartSVG.propTypes = { - paths: PropTypes.arrayOf(PropTypes.object), - radius: PropTypes.number.isRequired, -}; - -ChartSVG.defaultProps = { - paths: [], -}; diff --git a/app/panel-android/components/content/DotsMenu.jsx b/app/panel-android/components/content/DotsMenu.jsx index a612ea2ab..ae468053c 100644 --- a/app/panel-android/components/content/DotsMenu.jsx +++ b/app/panel-android/components/content/DotsMenu.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -13,50 +13,52 @@ import React from 'react'; import PropTypes from 'prop-types'; +import ClassNames from 'classnames'; -export default class DotsMenu extends React.Component { +class DotsMenu extends React.Component { constructor(props) { super(props); this.state = { - opening: false, + open: false, + unmounted: false, }; } - componentDidMount() { - window.addEventListener('click', this.handleClick, false); - } - componentWillUnmount() { - window.removeEventListener('click', this.handleClick, false); + this.setState({ unmounted: true }); + window.removeEventListener('click', this.closeDotsMenu); } - /* Close the menu if user clicks anywhere on the window */ - handleClick = (event) => { - const { opening } = this.state; - if (opening && event.target.className.indexOf('dots-menu-btn') === -1) { - this.setState({ - opening: false, - }); + closeDotsMenu = () => { + window.removeEventListener('click', this.closeDotsMenu); + const { unmounted } = this.state; + if (!unmounted) { // Can I remove this and still have no React Warning? + this.setState({ open: false }); } } - /* Toggle menu */ - dotsButtonClicked = () => { - this.setState(prevState => ({ opening: !prevState.opening })); + clickDotsMenu = (event) => { + event.stopPropagation(); + window.addEventListener('click', this.closeDotsMenu); + this.setState(prevState => ({ open: !prevState.open })); } render() { const { actions } = this.props; - const { opening } = this.state; + const { open } = this.state; + const menuContentClassNames = ClassNames('DotsMenu__content', { + DotsMenu__open: open, + }); + return ( -
- + ))} @@ -67,5 +69,11 @@ export default class DotsMenu extends React.Component { } DotsMenu.propTypes = { - actions: PropTypes.arrayOf(PropTypes.object).isRequired, + actions: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + callback: PropTypes.func.isRequired, + })).isRequired, }; + +export default DotsMenu; diff --git a/app/panel-android/components/content/FixedMenu.jsx b/app/panel-android/components/content/FixedMenu.jsx deleted file mode 100644 index 3301bf4ff..000000000 --- a/app/panel-android/components/content/FixedMenu.jsx +++ /dev/null @@ -1,147 +0,0 @@ -/** - * FixedMenu Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import MenuItem from './MenuItem'; - -export default class FixedMenu extends React.Component { - constructor(props) { - super(props); - this.state = { - open: false, - currentMenuItemText: FixedMenu.defaultHeaderText, - }; - } - - static get defaultHeaderText() { - return 'Enhanced Options'; - } - - get cliqzModuleData() { - const { cliqzModuleData } = this.props; - return cliqzModuleData || {}; - } - - get antiTrackingData() { - return this.cliqzModuleData.antitracking || {}; - } - - get adBlockData() { - return this.cliqzModuleData.adblock || {}; - } - - get smartBlockData() { - const { panel } = this.props; - return panel.smartBlock || {}; - } - - getCount = (type) => { - let total = 0; - switch (type) { - case 'enable_anti_tracking': { - const categories = Object.keys(this.antiTrackingData); - categories.forEach((category) => { - const apps = Object.keys(this.antiTrackingData[category]); - apps.forEach((app) => { - if (this.antiTrackingData[category][app] === 'unsafe') { - total++; - } - }); - }); - return total; - } - case 'enable_ad_block': - return (this.adBlockData && this.adBlockData.totalCount) || 0; - case 'enable_smart_block': - Object.keys(this.smartBlockData.blocked || {}).forEach(() => { - total++; - }); - Object.keys(this.smartBlockData.unblocked || {}).forEach(() => { - total++; - }); - return total; - default: - return 0; - } - } - - toggleMenu = () => { - this.setState(prevState => ({ open: !prevState.open })); - } - - updateHeaderText = (text) => { - const textToShow = text || FixedMenu.defaultHeaderText; - - this.setState({ - currentMenuItemText: textToShow, - }); - } - - render() { - const { panel } = this.props; - const { open, currentMenuItemText } = this.state; - return ( -
-
-

{currentMenuItemText}

-
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
- ); - } -} - -FixedMenu.propTypes = { - panel: PropTypes.shape, - cliqzModuleData: PropTypes.shape, -}; - -FixedMenu.defaultProps = { - panel: {}, - cliqzModuleData: {}, -}; diff --git a/app/panel-android/components/content/MenuItem.jsx b/app/panel-android/components/content/MenuItem.jsx deleted file mode 100644 index 9bc4e765e..000000000 --- a/app/panel-android/components/content/MenuItem.jsx +++ /dev/null @@ -1,105 +0,0 @@ -/** - * MenuItem Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class MenuItem extends React.Component { - constructor(props) { - super(props); - - this.state = { - opening: false, - }; - } - - menuItemClicked = () => { - const { updateHeaderText, title } = this.props; - this.setState({ - opening: true, - }); - - updateHeaderText(title); - } - - closeButtonClicked = () => { - const { updateHeaderText } = this.props; - this.setState({ - opening: false, - }); - - updateHeaderText(''); - } - - switcherClicked = () => { - const { active, type } = this.props; - const { callGlobalAction } = this.context; - callGlobalAction({ - actionName: 'cliqzFeatureToggle', - actionData: { - currentState: active, - type, - }, - }); - } - - render() { - const { - type, - numData, - title, - description, - active, - headline, - } = this.props; - const { opening } = this.state; - return ( -
-
- {numData} - {title} -

{description}

-
- -
- {numData} -

{headline}

-

{description}

-
-
- ); - } -} - -MenuItem.propTypes = { - active: PropTypes.bool, - type: PropTypes.string, - title: PropTypes.string, - numData: PropTypes.number, - headline: PropTypes.string, - description: PropTypes.string, -}; - -MenuItem.defaultProps = { - active: false, - type: '', - title: '', - numData: 0, - headline: '', - description: '', -}; - -MenuItem.contextTypes = { - callGlobalAction: PropTypes.func, -}; diff --git a/app/panel-android/components/content/OverviewTab.jsx b/app/panel-android/components/content/OverviewTab.jsx new file mode 100644 index 000000000..07a67b41f --- /dev/null +++ b/app/panel-android/components/content/OverviewTab.jsx @@ -0,0 +1,431 @@ +/** + * Overview Tab Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import ClassNames from 'classnames'; +import PropTypes from 'prop-types'; +import { + NotScanned, + DonutGraph, + GhosteryFeature, + PauseButton, + CliqzFeature +} from '../../../panel/components/BuildingBlocks'; +import globals from '../../../../src/classes/Globals'; + +const { + IS_CLIQZ, + WHITELISTED, BLACKLISTED +} = globals; + +class OverviewTab extends React.Component { + constructor(props) { + super(props); + + this.pauseOptions = [ + { name: t('pause_30_min'), val: 30 }, + { name: t('pause_1_hour'), val: 60 }, + { name: t('pause_24_hours'), val: 1440 }, + ]; + } + + get siteNotScanned() { + const { blocking, summary } = this.props; + const { siteNotScanned, pageUrl } = blocking; + const { categories } = summary; + const searchRegEx = /http|chrome-extension|moz-extension|ms-browser-extension|newtab|chrome:\/\/startpage\//; + + if (siteNotScanned || !categories || pageUrl.search(searchRegEx) === -1) { + return true; + } + return false; + } + + get adBlockBlocked() { + const { panel, cliqzModuleData } = this.props; + const { enable_ad_block } = panel; + const { adBlock } = cliqzModuleData; + + return (enable_ad_block && adBlock.trackerCount) || 0; + } + + get antiTrackUnsafe() { + const { panel, cliqzModuleData } = this.props; + const { enable_anti_tracking } = panel; + const { antiTracking } = cliqzModuleData; + + return (enable_anti_tracking && antiTracking.trackerCount) || 0; + } + + get trackersFound() { + const { summary } = this.props; + const { trackerCounts } = summary; + + return (trackerCounts && (trackerCounts.allowed + trackerCounts.blocked)) || 0; + } + + get smartBlockBlocked() { + const { panel, summary } = this.props; + const { smartBlock } = panel; + const { trackerCounts } = summary; + + let sbBlocked = (smartBlock && smartBlock.blocked && Object.keys(smartBlock.blocked).length) || 0; + if (sbBlocked === trackerCounts.sbBlocked) { + sbBlocked = 0; + } + + return sbBlocked; + } + + get smartBlockAllowed() { + const { panel, summary } = this.props; + const { smartBlock } = panel; + const { trackerCounts } = summary; + + let sbAllowed = (smartBlock && smartBlock.unblocked && Object.keys(smartBlock.unblocked).length) || 0; + if (sbAllowed === trackerCounts.sbAllowed) { + sbAllowed = 0; + } + + return sbAllowed; + } + + get smartBlockAdjust() { + const { panel } = this.props; + const { enable_smart_block } = panel; + + return enable_smart_block && ((this.smartBlockBlocked - this.smartBlockAllowed) || 0); + } + + get trackersBlockedCount() { + const { summary } = this.props; + const { paused_blocking, sitePolicy, trackerCounts } = summary; + + let totalTrackersBlockedCount; + if (paused_blocking || sitePolicy === WHITELISTED) { + totalTrackersBlockedCount = 0; + } else if (sitePolicy === BLACKLISTED) { + totalTrackersBlockedCount = trackerCounts.blocked + trackerCounts.allowed || 0; + } else { + totalTrackersBlockedCount = trackerCounts.blocked + this.smartBlockAdjust || 0; + } + + return totalTrackersBlockedCount; + } + + get requestsModifiedCount() { + return this.adBlockBlocked + this.antiTrackUnsafe; + } + + handleTrustButtonClick = () => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'handleTrustButtonClick', + }); + } + + handleRestrictButtonClick = () => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'handleRestrictButtonClick', + }); + } + + handlePauseButtonClick = (time) => { + const { summary, callGlobalAction } = this.props; + const { paused_blocking } = summary; + + callGlobalAction({ + actionName: 'handlePauseButtonClick', + actionData: { + paused_blocking: (typeof time === 'number' ? true : !paused_blocking), + time: typeof time === 'number' ? time * 60000 : 0, + }, + }); + } + + handleCliqzFeatureClick = ({ feature, status }) => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'cliqzFeatureToggle', + actionData: { + currentState: status, + type: feature, + }, + }); + } + + _renderNavigationLinks() { + const { clickAccount, clickSettings } = this.props; + const accountIcon = ( + + + + + + + + + ); + + const settingsIcon = ( + + + + + + ); + + return ( +
+
+
+ {accountIcon} +
+
+ {settingsIcon} +
+
+
+ ); + } + + _renderDonut() { + const { + blocking, + cliqzModuleData, + summary, + } = this.props; + const { categories } = blocking; + const { adBlock, antiTracking } = cliqzModuleData; + const { sitePolicy, paused_blocking } = summary; + + return ( + + ); + } + + _renderPageHost() { + const { summary } = this.props; + const { pageHost = 'page_host' } = summary; + const pageHostClassNames = ClassNames('OverviewTab__PageHostText', { + invisible: (pageHost.split('.').length < 2), + }); + + return ( + {pageHost} + ); + } + + _renderTotalTrackersBlocked() { + return ( +
+ + {t('trackers_blocked')} + {' '} + + + {this.trackersBlockedCount} + +
+ ); + } + + _renderTotalRequestsModified() { + return ( +
+ + {t('requests_modified')} + {' '} + + + {this.requestsModifiedCount} + +
+ ); + } + + _renderGhosteryFeatures() { + const { summary } = this.props; + const { paused_blocking, paused_blocking_timeout, sitePolicy } = summary; + const disableBlocking = this.siteNotScanned; + + return ( +
+
+ +
+
+ +
+
+ +
+
+ ); + } + + _renderCliqzFeatures() { + const { panel, summary } = this.props; + const { enable_anti_tracking, enable_ad_block, enable_smart_block } = panel; + const { paused_blocking, sitePolicy } = summary; + const disableBlocking = this.siteNotScanned; + + return ( +
+
+ +
+
+ +
+
+ +
+
+ ); + } + + render() { + return ( +
+ {this._renderNavigationLinks()} + + {this.siteNotScanned && ( +
+ +
+ )} + + {!this.siteNotScanned && ( +
+
+ {this._renderDonut()} +
+
+ {this._renderPageHost()} +
+
+ {this._renderTotalTrackersBlocked()} + {this._renderTotalRequestsModified()} +
+
+ )} + +
+ {this._renderGhosteryFeatures()} +
+ +
+ {this._renderCliqzFeatures()} +
+
+ ); + } +} + +OverviewTab.propTypes = { + panel: PropTypes.shape({ + enable_ad_block: PropTypes.bool.isRequired, + enable_anti_tracking: PropTypes.bool.isRequired, + enable_smart_block: PropTypes.bool.isRequired, + smartBlock: PropTypes.shape({ + blocked: PropTypes.shape({}).isRequired, + unblocked: PropTypes.shape({}).isRequired, + }).isRequired, + }).isRequired, + summary: PropTypes.shape({ + categories: PropTypes.array.isRequired, + trackerCounts: PropTypes.shape({ + allowed: PropTypes.number.isRequired, + blocked: PropTypes.number.isRequired, + }).isRequired, + sitePolicy: PropTypes.oneOf([ + false, + WHITELISTED, + BLACKLISTED, + ]).isRequired, + paused_blocking: PropTypes.bool.isRequired, + }).isRequired, + blocking: PropTypes.shape({ + siteNotScanned: PropTypes.bool.isRequired, + pageUrl: PropTypes.string.isRequired, + }).isRequired, + cliqzModuleData: PropTypes.shape({ + adBlock: PropTypes.shape({ + trackerCount: PropTypes.number.isRequired, + }).isRequired, + antiTracking: PropTypes.shape({ + trackerCount: PropTypes.number.isRequired, + }).isRequired, + }).isRequired, + clickAccount: PropTypes.func.isRequired, + clickSettings: PropTypes.func.isRequired, + callGlobalAction: PropTypes.func.isRequired, +}; + +export default OverviewTab; diff --git a/app/panel-android/components/content/Path.jsx b/app/panel-android/components/content/Path.jsx deleted file mode 100644 index 8f37a2d4d..000000000 --- a/app/panel-android/components/content/Path.jsx +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Path Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -const INTERVAL = 1000; // Define the maximum rendering time for this path - -export default class Path extends React.Component { - constructor(props) { - super(props); - this.myRef = React.createRef(); - this.timer = null; - } - - static polarToCartesian(centerX, centerY, radius, angleInDegrees) { - const angleInRadians = (angleInDegrees - 90) * (Math.PI / 180.0); - - return { - x: centerX + (radius * Math.cos(angleInRadians)), - y: centerY + (radius * Math.sin(angleInRadians)) - }; - } - - static describeArc(x, y, radius, startAngle, endAngle) { - const start = Path.polarToCartesian(x, y, radius, startAngle); - const end = Path.polarToCartesian(x, y, radius, endAngle); - - const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1'; - - const d = [ - 'M', start.x, start.y, - 'A', radius, radius, 0, largeArcFlag, 1, end.x, end.y - ].join(' '); - - return d; - } - - componentDidMount() { - const node = this.myRef.current; - node.style.setProperty('--stroke-length', `${node.getTotalLength()}`); - // Check and call props.handler() if the animationEnd event doesn't get fired somehow - this.timer = setInterval(() => { - clearInterval(this.timer); // Run this only once - const { handler } = this.props; - handler(); - }, INTERVAL); - } - - componentWillUnmount() { - clearInterval(this.timer); - } - - onAnimationEndHandler = () => { - clearInterval(this.timer); - const { handler } = this.props; - handler(); - } - - render() { - const { radius, path } = this.props; - const { start, category } = path; - // Fix error for single path - const end = path.end === 360 ? 359.9999 : path.end; - - const d = Path.describeArc(0, 0, radius, start, end); - - return ( - - ); - } -} - -Path.propTypes = { - radius: PropTypes.number.isRequired, - path: PropTypes.shape, - handler: PropTypes.func.isRequired, -}; - -Path.defaultProps = { - path: {}, -}; diff --git a/app/panel-android/components/content/Settings.jsx b/app/panel-android/components/content/Settings.jsx new file mode 100644 index 000000000..38698a3cb --- /dev/null +++ b/app/panel-android/components/content/Settings.jsx @@ -0,0 +1,324 @@ +/** + * Settings Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { ReactSVG } from 'react-svg'; +import TrustAndRestrict from '../../../panel/components/Settings/TrustAndRestrict'; +import GeneralSettings from '../../../panel/components/Settings/GeneralSettings'; +import AdBlocker from '../../../panel/components/Settings/AdBlocker'; +import Notifications from '../../../panel/components/Settings/Notifications'; +import OptIn from '../../../panel/components/Settings/OptIn'; +import ImportExport from '../../../panel/components/Settings/ImportExport'; +import Help from '../../../panel/components/Help'; +import About from '../../../panel/components/About'; +import globals from '../../../../src/classes/Globals'; + +const { IS_CLIQZ } = globals; + +class Settings extends React.Component { + constructor(props) { + super(props); + + this.state = { + view: 'settings-home', + }; + } + + clickBack = () => { + const { clickHome } = this.props; + const { view } = this.state; + + if (view === 'settings-home') { + clickHome(); + } else { + this.setState({ view: 'settings-home' }); + } + } + + updateSitePolicy = ({ type, pageHost }) => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'updateSitePolicy', + actionData: { type, pageHost }, + }); + } + + updateDatabase = () => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'updateDatabase', + }); + } + + toggleCheckbox = (event) => { + const { callGlobalAction } = this.props; + const { name, checked } = event.currentTarget; + + callGlobalAction({ + actionName: 'updateSettingCheckbox', + actionData: { name, checked }, + }); + } + + selectItem = ({ event, value }) => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'selectItem', + actionData: { event, value }, + }); + } + + exportSettings = () => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'exportSettings', + }); + } + + importSettingsDialog = () => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'importSettingsDialog', + }); + } + + importSettingsNative = (importFile) => { + const { callGlobalAction } = this.props; + + callGlobalAction({ + actionName: 'importSettingsNative', + actionData: importFile, + }); + } + + _renderSettingsHeader() { + const { view } = this.state; + + let headerText; + switch (view) { + case 'settings-home': + headerText = t('panel_menu_settings'); + break; + case 'settings-trust-restrict': + headerText = t('settings_trust_and_restrict'); + break; + case 'settings-general': + headerText = t('settings_general_settings'); + break; + case 'settings-adblocker': + headerText = t('settings_adblocker'); + break; + case 'settings-notifications': + headerText = t('settings_notifications'); + break; + case 'settings-opt-in': + headerText = t('settings_opt_in'); + break; + case 'settings-import-export': + headerText = t('settings_import_export'); + break; + case 'settings-help': + headerText = t('panel_menu_help'); + break; + case 'settings-about': + headerText = t('panel_menu_about'); + break; + default: + headerText = ''; + } + + return ( +
+ + {headerText} +
+ ); + } + + _renderSettingsHome() { + return ( +
+
+
{ this.setState({ view: 'settings-trust-restrict' }); }}> + { t('settings_trust_and_restrict') } +
+
{ this.setState({ view: 'settings-general' }); }}> + { t('settings_general_settings') } +
+ {!IS_CLIQZ && ( +
{ this.setState({ view: 'settings-adblocker' }); }}> + { t('settings_adblocker') } +
+ )} +
{ this.setState({ view: 'settings-notifications' }); }}> + { t('settings_notifications') } +
+
{ this.setState({ view: 'settings-opt-in' }); }}> + { t('settings_opt_in') } +
+
{ this.setState({ view: 'settings-import-export' }); }}> + { t('settings_import_export') } +
+
{ this.setState({ view: 'settings-help' }); }}> + { t('panel_menu_help') } +
+
{ this.setState({ view: 'settings-about' }); }}> + { t('panel_menu_about') } +
+
+
+ ); + } + + _renderSettingsTrustRestrict() { + const { summary } = this.props; + const { site_whitelist, site_blacklist } = summary; + const actions = { + updateSitePolicy: this.updateSitePolicy, + }; + + return ( + + ); + } + + _renderSettingsGeneral() { + const { settings } = this.props; + const actions = { + updateDatabase: this.updateDatabase, + }; + + return ( + + ); + } + + _renderSettingsAdBlocker() { + const { settings } = this.props; + const actions = { + selectItem: this.selectItem, + }; + + return ( + + ); + } + + _renderSettingsNotification() { + const { settings } = this.props; + + return ( + + ); + } + + _renderSettingsOptIn() { + const { settings } = this.props; + + return ( + + ); + } + + _renderSettingsImportExport() { + const { summary, settings } = this.props; + const { pageUrl = '' } = summary; + const { + exportResultText = '', + importResultText = '', + actionSuccess = false, + } = settings; + const settingsData = { + pageUrl, + exportResultText, + importResultText, + actionSuccess, + }; + const actions = { + exportSettings: this.exportSettings, + importSettingsDialog: this.importSettingsDialog, + importSettingsNative: this.importSettingsNative, + }; + + return ( +
+
+
+ +
+
+
+ ); + } + + render() { + const { view } = this.state; + + return ( +
+ {this._renderSettingsHeader()} + {view === 'settings-home' && this._renderSettingsHome()} + {view === 'settings-trust-restrict' && this._renderSettingsTrustRestrict()} + {view === 'settings-general' && this._renderSettingsGeneral()} + {view === 'settings-adblocker' && this._renderSettingsAdBlocker()} + {view === 'settings-notifications' && this._renderSettingsNotification()} + {view === 'settings-opt-in' && this._renderSettingsOptIn()} + {view === 'settings-import-export' && this._renderSettingsImportExport()} + {view === 'settings-help' && ()} + {view === 'settings-about' && ()} +
+ ); + } +} + +Settings.propTypes = { + summary: PropTypes.shape({ + site_whitelist: PropTypes.arrayOf(PropTypes.string).isRequired, + site_blacklist: PropTypes.arrayOf(PropTypes.string).isRequired, + }).isRequired, + settings: PropTypes.shape({}).isRequired, + clickHome: PropTypes.func.isRequired, + callGlobalAction: PropTypes.func.isRequired, +}; + +export default Settings; diff --git a/app/panel-android/components/content/Tab.jsx b/app/panel-android/components/content/Tab.jsx index 07ee41c0c..686139544 100644 --- a/app/panel-android/components/content/Tab.jsx +++ b/app/panel-android/components/content/Tab.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -13,8 +13,9 @@ import React from 'react'; import PropTypes from 'prop-types'; +import ClassNames from 'classnames'; -export default class Tab extends React.Component { +class Tab extends React.Component { handleTabClick = (event) => { event.preventDefault(); const { onClick, tabIndex } = this.props; @@ -23,12 +24,16 @@ export default class Tab extends React.Component { render() { const { isActive, tabLabel, linkClassName } = this.props; + const tabClassNames = ClassNames('Tab__navigation_item flex-container align-center-middle', { + 'Tab--active': isActive, + }); + const tabLinkClassNames = ClassNames('Tab__navigation_link', linkClassName, { + 'Tab--active': isActive, + }); + return ( -
  • - +
  • + {tabLabel}
  • @@ -41,14 +46,13 @@ Tab.propTypes = { tabIndex: PropTypes.number, isActive: PropTypes.bool, tabLabel: PropTypes.string.isRequired, - linkClassName: PropTypes.string.isRequired + linkClassName: PropTypes.string.isRequired, }; Tab.defaultProps = { onClick: () => null, tabIndex: -1, -}; - -Tab.defaultProps = { isActive: false, }; + +export default Tab; diff --git a/app/panel-android/components/content/Tabs.jsx b/app/panel-android/components/content/Tabs.jsx index 50c0a52fb..ec4c9de95 100644 --- a/app/panel-android/components/content/Tabs.jsx +++ b/app/panel-android/components/content/Tabs.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,13 +12,14 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; -export default class Tabs extends React.Component { +class Tabs extends React.Component { constructor(props) { super(props); this.state = { - activeTabIndex: 0 + activeTabIndex: 0, }; } @@ -29,17 +30,17 @@ export default class Tabs extends React.Component { } this.setState({ - activeTabIndex: tabIndex + activeTabIndex: tabIndex, }); } - renderTabsNav = () => { + renderTabsNavigation = () => { const { children } = this.props; const { activeTabIndex } = this.state; return React.Children.map(children, (child, index) => React.cloneElement(child, { onClick: this.handleTabClick, tabIndex: index, - isActive: index === activeTabIndex + isActive: index === activeTabIndex, })); } @@ -54,14 +55,33 @@ export default class Tabs extends React.Component { render() { return ( -
    -
      - {this.renderTabsNav()} +
      +
        + {this.renderTabsNavigation()}
      -
      +
      {this.renderActiveTabContent()}
      ); } } + +// ToDo: Validate that Tabs Children is Tab. +// Tried: +// children: PropTypes.oneOfType([ +// PropTypes.shape({ +// type: Tab +// }), +// PropTypes.arrayOf( +// PropTypes.shape({ +// type: Tab +// }) +// ) +// ]).isRequired, +// But failed because of this: https://github.com/vadimdemedes/ink/issues/37 +Tabs.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default Tabs; diff --git a/app/panel-android/components/content/TrackerItem.jsx b/app/panel-android/components/content/TrackerItem.jsx deleted file mode 100644 index 329ad1fde..000000000 --- a/app/panel-android/components/content/TrackerItem.jsx +++ /dev/null @@ -1,194 +0,0 @@ -/** - * TrackerItem Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import getUrlFromTrackerId from '../../utils/tracker-info'; - -export default class TrackerItem extends React.Component { - get trackerSelectStatus() { - const { type, tracker } = this.props; - const { siteProps } = this.context; - // Only for site trackers - if (type === 'site-trackers') { - if (siteProps.isTrusted) { - return 'trusted'; - } - - if (siteProps.isRestricted) { - return 'restricted'; - } - } - - if (tracker.ss_allowed) { - return 'trusted'; - } - - if (tracker.ss_blocked) { - return 'restricted'; - } - - if (tracker.blocked) { - return 'blocked'; - } - - return ''; - } - - get showMenu() { - const { showMenu } = this.props; - return showMenu; - } - - get disabledStatus() { - return ['trusted', 'restricted'].includes(this.trackerSelectStatus) ? 'disabled' : ''; - } - - clickButtonTrust = () => { - const { - tracker, categoryId, index, toggleMenu - } = this.props; - const { callGlobalAction } = this.context; - const ss_allowed = !tracker.ss_allowed; - - callGlobalAction({ - actionName: 'trustRestrictBlockSiteTracker', - actionData: { - app_id: tracker.id, - cat_id: categoryId, - trust: ss_allowed, - restrict: false, - block: tracker.blocked, // Keep blocking - } - }); - toggleMenu(index); // Hide menu - } - - clickButtonRestrict = () => { - const { - tracker, categoryId, index, toggleMenu - } = this.props; - const { callGlobalAction } = this.context; - const ss_blocked = !tracker.ss_blocked; - callGlobalAction({ - actionName: 'trustRestrictBlockSiteTracker', - actionData: { - app_id: tracker.id, - cat_id: categoryId, - restrict: ss_blocked, - trust: false, - block: tracker.blocked, // Keep blocking - } - }); - toggleMenu(index); - } - - clickButtonBlock = (hideMenu = true) => { - // onClick={(e) => { e.stopPropagation(); this.clickButtonBlock(false); }} - const { - tracker, type, categoryId, index, toggleMenu - } = this.props; - const { callGlobalAction } = this.context; - if (this.disabledStatus) { - return; - } - - const blocked = !tracker.blocked; - - if (type === 'site-trackers') { - callGlobalAction({ - actionName: 'trustRestrictBlockSiteTracker', - actionData: { - app_id: tracker.id, - cat_id: categoryId, - block: blocked, - trust: false, - restrict: false, - } - }); - } else { - callGlobalAction({ - actionName: 'blockUnblockGlobalTracker', - actionData: { - app_id: tracker.id, - cat_id: categoryId, - block: blocked, - } - }); - } - - if (hideMenu) { - toggleMenu(index); - } - } - - openTrackerLink = () => { - const { tracker } = this.props; - const url = getUrlFromTrackerId(tracker.id); - const win = window.open(url, '_blank'); - win.focus(); - } - - toggleMenu = () => { - const { index, toggleMenu } = this.props; - toggleMenu(index); - } - - render() { - const { tracker, type } = this.props; - return ( -
    • -
      - - - -
      -
    • - - ); - } -} - -TrackerItem.propTypes = { - toggleMenu: PropTypes.func.isRequired, - index: PropTypes.number.isRequired, - showMenu: PropTypes.bool, - tracker: PropTypes.shape, - categoryId: PropTypes.string, - type: PropTypes.string, -}; - -TrackerItem.defaultProps = { - showMenu: false, - tracker: {}, - categoryId: '', - type: '', -}; - -TrackerItem.contextTypes = { - callGlobalAction: PropTypes.func, - siteProps: PropTypes.shape, -}; diff --git a/app/panel-android/components/content/TrackersChart.jsx b/app/panel-android/components/content/TrackersChart.jsx deleted file mode 100644 index 8781aa20f..000000000 --- a/app/panel-android/components/content/TrackersChart.jsx +++ /dev/null @@ -1,57 +0,0 @@ -/** - * TrackersChart Component - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import ChartSVG from './ChartSVG'; - -class TrackersChart extends React.Component { - constructor(props) { - super(props); - - this.state = { - config: { - radius: 100, - } - }; - } - - render() { - const { num, paths } = this.props; - const { config } = this.state; - return ( -
      - -

      - - {num} - {' '} - - Trackers found -

      -
      - ); - } -} - -TrackersChart.propTypes = { - paths: PropTypes.arrayOf(PropTypes.object), - num: PropTypes.number, -}; - -TrackersChart.defaultProps = { - paths: [], - num: 0, -}; - -export default TrackersChart; diff --git a/app/panel-android/components/content/__tests__/BlockingCategories.jsx b/app/panel-android/components/content/__tests__/BlockingCategories.jsx new file mode 100644 index 000000000..8496a46af --- /dev/null +++ b/app/panel-android/components/content/__tests__/BlockingCategories.jsx @@ -0,0 +1,253 @@ +/** + * BlockingCategories Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import BlockingCategories from '../BlockingCategories'; + +describe('app/panel-android/components/content/BlockingCategories.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('BlockingCategories component as site', () => { + const categories = [ + { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 1, + trackers: [ + { + id: 1, + name: 'Tracker 1', + ss_allowed: false, + ss_blocked: false, + blocked: true, + }, + ], + img_name: 'category-1-image-url', + }, + { + id: 'cat-2', + name: 'Category-2', + num_total: 5, + num_blocked: 3, + trackers: [ + { + id: 2, + name: 'Tracker 2', + ss_allowed: false, + ss_blocked: false, + blocked: true, + }, + { + id: 3, + name: 'Tracker 3', + ss_allowed: false, + ss_blocked: false, + blocked: true, + }, + { + id: 4, + name: 'Tracker 4', + ss_allowed: false, + ss_blocked: false, + blocked: true, + }, + { + id: 5, + name: 'Tracker 5', + ss_allowed: false, + ss_blocked: false, + blocked: false, + }, + { + id: 6, + name: 'Tracker 6', + ss_allowed: false, + ss_blocked: false, + blocked: false, + }, + ], + img_name: 'category-2-image-url', + }, + ]; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingCategories component as global', () => { + const categories = [ + { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 1, + trackers: [ + { + id: '1', + name: 'Tracker 1', + blocked: true, + }, + ], + img_name: 'category-1-image-url', + }, + { + id: 'cat-2', + name: 'Category-2', + num_total: 5, + num_blocked: 3, + trackers: [ + { + id: '2', + name: 'Tracker 2', + blocked: true, + }, + { + id: '3', + name: 'Tracker 3', + blocked: true, + }, + { + id: '4', + name: 'Tracker 4', + blocked: true, + }, + { + id: '5', + name: 'Tracker 5', + blocked: false, + }, + { + id: '6', + name: 'Tracker 6', + blocked: false, + }, + ], + img_name: 'category-2-image-url', + }, + ]; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Functionality tests shallow mounted with Enzyme', () => { + test('BlockingCategories component toggle category clicks work', () => { + const categories = [ + { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 1, + trackers: [ + { + id: '1', + name: 'Tracker 1', + blocked: true, + }, + ], + img_name: 'category-1-image-url', + }, + { + id: 'cat-2', + name: 'Category-2', + num_total: 5, + num_blocked: 3, + trackers: [ + { + id: '2', + name: 'Tracker 2', + blocked: true, + }, + { + id: '3', + name: 'Tracker 3', + blocked: true, + }, + { + id: '4', + name: 'Tracker 4', + blocked: true, + }, + { + id: '5', + name: 'Tracker 5', + blocked: false, + }, + { + id: '6', + name: 'Tracker 6', + blocked: false, + }, + ], + img_name: 'category-2-image-url', + }, + ]; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = shallow( + {}} + /> + ); + const instance = component.instance(); + + expect(component.state('openCategoryIndex')).toBe(-1); + expect(instance.getOpenStatus(0)).toBe(false); + + instance.toggleCategoryOpen(0); + expect(component.state('openCategoryIndex')).toBe(0); + expect(instance.getOpenStatus(0)).toBe(true); + + component.setProps({ type: 'global' }); + expect(component.state('openCategoryIndex')).toBe(-1); + expect(instance.getOpenStatus(0)).toBe(false); + }); + }); +}); diff --git a/app/panel-android/components/content/__tests__/BlockingCategory.jsx b/app/panel-android/components/content/__tests__/BlockingCategory.jsx new file mode 100644 index 000000000..05571129f --- /dev/null +++ b/app/panel-android/components/content/__tests__/BlockingCategory.jsx @@ -0,0 +1,368 @@ +/** + * BlockingCategory Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import BlockingCategory from '../BlockingCategory'; + +describe('app/panel-android/components/content/BlockingCategory.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('BlockingCategory component when sitePolicy Restricted', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 0, + trackers: [], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: false, + isRestricted: true, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + type="site" + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingCategory component when sitePolicy Trusted', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 0, + trackers: [], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: true, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + type="site" + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingCategory component when sitePolicy Trusted & Paused', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 0, + trackers: [], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: true, + isRestricted: false, + isPaused: true, + }; + + const component = renderer.create( + {}} + open={false} + type="site" + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingCategory component as site with all trackers ss_blocked', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 0, + trackers: [ + { + id: '1', + name: 'Tracker 1', + blocked: false, + ss_allowed: false, + ss_blocked: true, + }, + ], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + type="site" + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingCategory component as site with all trackers ss_allowed', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 0, + trackers: [ + { + id: '1', + name: 'Tracker 1', + blocked: false, + ss_allowed: true, + ss_blocked: true, + }, + ], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + type="site" + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingCategory component as global with no trackers blocked', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 0, + trackers: [ + { + id: '1', + name: 'Tracker 1', + blocked: false, + }, + ], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + type="global" + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingCategory component as global with all trackers blocked', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 1, + num_blocked: 1, + trackers: [ + { + id: '1', + name: 'Tracker 1', + blocked: true, + }, + ], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + type="global" + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingCategory component as global with mixed trackers blocked', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 2, + num_blocked: 1, + trackers: [ + { + id: '1', + name: 'Tracker 1', + blocked: false, + }, + { + id: '2', + name: 'Tracker 2', + blocked: true, + }, + ], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + type="global" + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Functionality tests shallow mounted with Enzyme', () => { + test('BlockingCategory component category click works', () => { + const category = { + id: 'cat-1', + name: 'Category-1', + num_total: 2, + num_blocked: 1, + trackers: [ + { + id: '1', + name: 'Tracker 1', + blocked: false, + }, + { + id: '2', + name: 'Tracker 2', + blocked: true, + }, + ], + img_name: 'category-1-image-url', + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const toggleCategoryOpen = jest.fn(); + const callGlobalAction = jest.fn(); + + const component = shallow( + + ); + + expect(component.find('.BlockingCategory__listHeader').length).toBe(0); + component.find('.BlockingCategory__details').simulate('click'); + component.setProps({ open: true }); + expect(toggleCategoryOpen.mock.calls.length).toBe(1); + expect(component.find('.BlockingCategory__listHeader').length).toBe(1); + + expect(callGlobalAction.mock.calls.length).toBe(0); + component.find('.BlockingSelectButton').simulate('click', { stopPropagation: () => {} }); + component.setProps({ type: 'global' }); + component.find('.BlockingSelectButton').simulate('click', { stopPropagation: () => {} }); + + expect(callGlobalAction.mock.calls[0][0].actionData.type).toBe('site'); + expect(callGlobalAction.mock.calls[1][0].actionData.type).toBe('global'); + }); + }); +}); diff --git a/app/panel-android/components/content/__tests__/BlockingTab.jsx b/app/panel-android/components/content/__tests__/BlockingTab.jsx new file mode 100644 index 000000000..3d1fc940e --- /dev/null +++ b/app/panel-android/components/content/__tests__/BlockingTab.jsx @@ -0,0 +1,102 @@ +/** + * BlockingTab Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import BlockingTab from '../BlockingTab'; + +describe('app/panel-android/components/content/BlockingTab.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('BlockingTab component as site with falsy props', () => { + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTab component as global with falsy props', () => { + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTab component as site with tracker falsy props', () => { + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTab component as global with tracker truthy props', () => { + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/panel-android/components/content/__tests__/BlockingTracker.jsx b/app/panel-android/components/content/__tests__/BlockingTracker.jsx new file mode 100644 index 000000000..97bb43659 --- /dev/null +++ b/app/panel-android/components/content/__tests__/BlockingTracker.jsx @@ -0,0 +1,282 @@ +/** + * BlockingTracker Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import BlockingTracker from '../BlockingTracker'; + +describe('app/panel-android/components/content/BlockingTracker.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('BlockingTracker component with falsy props', () => { + const tracker = { + id: 1, + name: 'Tracker 1', + ss_allowed: false, + ss_blocked: false, + blocked: false, + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + settings={{}} + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTracker component when tracker blocked', () => { + const tracker = { + id: 1, + name: 'Tracker 1', + ss_allowed: false, + ss_blocked: false, + blocked: true, + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + settings={{ toggle_individual_trackers: false }} + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTracker component when tracker allowed', () => { + const tracker = { + id: 1, + name: 'Tracker 1', + ss_allowed: true, + ss_blocked: false, + blocked: false, + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + settings={{ toggle_individual_trackers: true }} + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTracker component when tracker restricted', () => { + const tracker = { + id: 1, + name: 'Tracker 1', + ss_allowed: false, + ss_blocked: true, + blocked: false, + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + settings={{ toggle_individual_trackers: true }} + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTracker component when site Trusted', () => { + const tracker = { + id: 1, + name: 'Tracker 1', + ss_allowed: false, + ss_blocked: false, + blocked: false, + }; + const siteProps = { + isTrusted: true, + isRestricted: false, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + settings={{ toggle_individual_trackers: true }} + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTracker component when site Restricted', () => { + const tracker = { + id: 1, + name: 'Tracker 1', + ss_allowed: false, + ss_blocked: false, + blocked: false, + }; + const siteProps = { + isTrusted: false, + isRestricted: true, + isPaused: false, + }; + + const component = renderer.create( + {}} + open={false} + settings={{ toggle_individual_trackers: true }} + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('BlockingTracker component when site Paused', () => { + const tracker = { + id: 1, + name: 'Tracker 1', + ss_allowed: false, + ss_blocked: false, + blocked: false, + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: true, + }; + + const component = renderer.create( + {}} + open={false} + settings={{ toggle_individual_trackers: true }} + siteProps={siteProps} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Functionality tests shallow mounted with Enzyme', () => { + test('BlockingTracker component clicks work', () => { + const tracker = { + id: 1, + name: 'Tracker 1', + ss_allowed: false, + ss_blocked: false, + blocked: false, + }; + const siteProps = { + isTrusted: false, + isRestricted: false, + isPaused: false, + }; + + const toggleTrackerSelectOpen = jest.fn(); + const callGlobalAction = jest.fn(); + + const component = shallow( + + ); + + expect(component.find('.BlockingSelectGroup.BlockingSelectGroup--open').length).toBe(0); + component.find('.BlockingTracker').simulate('click'); + component.setProps({ open: true }); + expect(toggleTrackerSelectOpen.mock.calls.length).toBe(1); + expect(component.find('.BlockingSelectGroup.BlockingSelectGroup--open').length).toBe(1); + + expect(callGlobalAction.mock.calls.length).toBe(0); + component.find('.BlockingSelect__block').simulate('click'); + component.setProps({ type: 'site' }); + component.find('.BlockingSelect__block').simulate('click'); + component.find('.BlockingSelect__restrict').simulate('click'); + component.find('.BlockingSelect__trust').simulate('click'); + expect(callGlobalAction.mock.calls[0][0].actionName).toBe('blockUnblockGlobalTracker'); + expect(callGlobalAction.mock.calls[1][0].actionName).toBe('trustRestrictBlockSiteTracker'); + expect(callGlobalAction.mock.calls[2][0].actionData.restrict).toBe(true); + expect(callGlobalAction.mock.calls[3][0].actionData.trust).toBe(true); + }); + }); +}); diff --git a/app/panel-android/components/content/__tests__/DotsMenu.jsx b/app/panel-android/components/content/__tests__/DotsMenu.jsx new file mode 100644 index 000000000..da4545ab3 --- /dev/null +++ b/app/panel-android/components/content/__tests__/DotsMenu.jsx @@ -0,0 +1,104 @@ +/** + * DotsMenu Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import DotsMenu from '../DotsMenu'; + +describe('app/panel-android/components/content/DotsMenu.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('DotsMenu component with 0 actions', () => { + const actions = []; + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('DotsMenu component with 3 actions', () => { + const actions = [ + { + id: 'action-1', + name: 'Action One', + callback: () => {}, + }, + { + id: 'action-2', + name: 'Action Two', + callback: () => {}, + }, + { + id: 'action-3', + name: 'Action Three', + callback: () => {}, + } + ]; + + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Functionality tests mounted with Enzyme', () => { + test('DotsMenu component with 3 actions happy path', () => { + const actions = [ + { + id: 'action-1', + name: 'Action One', + callback: jest.fn(), + }, + { + id: 'action-2', + name: 'Action Two', + callback: jest.fn(), + }, + { + id: 'action-3', + name: 'Action Three', + callback: jest.fn(), + } + ]; + + const component = mount( + + ); + expect(component.find('.DotsMenu').length).toBe(1); + expect(component.find('.DotsMenu__button').length).toBe(1); + expect(component.find('.DotsMenu__content').length).toBe(1); + expect(component.find('.DotsMenu__content.DotsMenu__open').length).toBe(0); + expect(component.find('.DotsMenu__item').length).toBe(3); + + component.setState({ open: true }); + expect(component.find('.DotsMenu__content.DotsMenu__open').length).toBe(1); + expect(component.find('.DotsMenu__item').length).toBe(3); + expect(actions[0].callback.mock.calls.length).toBe(0); + expect(actions[1].callback.mock.calls.length).toBe(0); + expect(actions[2].callback.mock.calls.length).toBe(0); + component.find('.DotsMenu__item').at(0).simulate('click'); + expect(actions[0].callback.mock.calls.length).toBe(1); + component.find('.DotsMenu__item').at(1).simulate('click'); + expect(actions[1].callback.mock.calls.length).toBe(1); + component.find('.DotsMenu__item').at(2).simulate('click'); + expect(actions[0].callback.mock.calls.length).toBe(1); + expect(actions[1].callback.mock.calls.length).toBe(1); + expect(actions[2].callback.mock.calls.length).toBe(1); + + component.setState({ open: false }); + expect(component.find('.DotsMenu__content.DotsMenu__open').length).toBe(0); + expect(component.find('.DotsMenu__item').length).toBe(3); + }); + }); +}); diff --git a/app/panel-android/components/content/__tests__/OverviewTab.jsx b/app/panel-android/components/content/__tests__/OverviewTab.jsx new file mode 100644 index 000000000..4fec3f938 --- /dev/null +++ b/app/panel-android/components/content/__tests__/OverviewTab.jsx @@ -0,0 +1,155 @@ +/** + * OverviewTab Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import OverviewTab from '../OverviewTab'; + +jest.mock('../../../../panel/components/Tooltip'); + +describe('app/panel-android/components/content/OverviewTab.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('OverviewTab component with falsy props and SiteNotScanned', () => { + const panel = { + enable_ad_block: false, + enable_anti_tracking: false, + enable_smart_block: false, + smartBlock: { blocked: {}, unblocked: {} }, + }; + const summary = { + categories: [], + trackerCounts: { + allowed: 0, + blocked: 0, + }, + sitePolicy: false, + paused_blocking: false, + }; + const blocking = { + siteNotScanned: true, + pageUrl: '', + }; + const cliqzModuleData = { + adBlock: { trackerCount: 0 }, + antiTracking: { trackerCount: 0 }, + }; + + const component = renderer.create( + {}} + clickSettings={() => {}} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('OverviewTab component with truthy props and no SiteNotScanned', () => { + const panel = { + enable_ad_block: true, + enable_anti_tracking: true, + enable_smart_block: true, + smartBlock: { blocked: { 1: true }, unblocked: { 2: true, 3: true } }, + }; + const summary = { + categories: ['ads', 'trackers'], + trackerCounts: { + allowed: 3, + blocked: 5, + }, + sitePolicy: false, + paused_blocking: true, + }; + const blocking = { + siteNotScanned: false, + pageUrl: 'http://example.com', + }; + const cliqzModuleData = { + adBlock: { trackerCount: 8 }, + antiTracking: { trackerCount: 13 }, + }; + + const component = renderer.create( + {}} + clickSettings={() => {}} + callGlobalAction={() => {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Functionality tests mounted with Enzyme', () => { + test('OverviewTab component clicks work', () => { + const panel = { + enable_ad_block: false, + enable_anti_tracking: false, + enable_smart_block: false, + smartBlock: { blocked: {}, unblocked: {} }, + }; + const summary = { + categories: [], + trackerCounts: { + allowed: 0, + blocked: 0, + }, + sitePolicy: false, + paused_blocking: false, + }; + const blocking = { + siteNotScanned: true, + pageUrl: '', + }; + const cliqzModuleData = { + adBlock: { trackerCount: 0 }, + antiTracking: { trackerCount: 0 }, + }; + + const clickAccount = jest.fn(); + const clickSettings = jest.fn(); + + const component = mount( + {}} + /> + ); + expect(clickAccount.mock.calls.length).toBe(0); + expect(clickSettings.mock.calls.length).toBe(0); + expect(component.find('.OverviewTab__NavigationLink').length).toBe(2); + + component.find('.OverviewTab__NavigationLink').at(0).simulate('click'); + expect(clickAccount.mock.calls.length).toBe(1); + expect(clickSettings.mock.calls.length).toBe(0); + + component.find('.OverviewTab__NavigationLink').at(1).simulate('click'); + expect(clickAccount.mock.calls.length).toBe(1); + expect(clickSettings.mock.calls.length).toBe(1); + }); + }); +}); diff --git a/app/panel-android/components/content/__tests__/Tabs.jsx b/app/panel-android/components/content/__tests__/Tabs.jsx new file mode 100644 index 000000000..30ed48514 --- /dev/null +++ b/app/panel-android/components/content/__tests__/Tabs.jsx @@ -0,0 +1,92 @@ +/** + * Tabs Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import Tabs from '../Tabs'; +import Tab from '../Tab'; + +describe('app/panel-android/components/content/Tabs.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('Tabs component with 3 Tab children components', () => { + const component = renderer.create( + + +
      Tab 1 Content
      +
      + + +
      Tab 2 Content
      +
      + + +
      Tab 3 Content Part I
      +
      Tab 3 Content Part II
      +
      Tab 3 Content Part III
      +
      +
      + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Functionality tests mounted with Enzyme', () => { + test('Tabs component with 3 Tab children components happy path', () => { + const component = mount( + + +
      Tab 1 Content
      +
      + + +
      Tab 2 Content
      +
      + + +
      Tab 3 Content Part I
      +
      Tab 3 Content Part II
      +
      Tab 3 Content Part III
      +
      +
      + ); + expect(component.find('.Tabs__component').length).toBe(1); + expect(component.find('.Tabs__navigation').length).toBe(1); + expect(component.find('.Tab__navigation_item').length).toBe(3); + expect(component.find('.Tab__navigation_item.tab-1-class').length).toBe(0); + expect(component.find('.Tab__navigation_item.Tab--active').length).toBe(1); + expect(component.find('.Tab__navigation_link').length).toBe(3); + expect(component.find('.Tab__navigation_link.Tab--active').length).toBe(1); + expect(component.find('.Tab__navigation_link.Tab--active.tab-1-class').length).toBe(1); + expect(component.find('.Tab__navigation_link.Tab--active.tab-2-class').length).toBe(0); + expect(component.find('.Tab__navigation_link.Tab--active.tab-3-class').length).toBe(0); + expect(component.find('.Tabs__active_content').length).toBe(1); + expect(component.find('.Tabs__active_content .tab-1-content').length).toBe(1); + expect(component.find('.Tabs__active_content .tab-2-content').length).toBe(0); + + component.setState({ activeTabIndex: 1 }); + expect(component.find('.Tab__navigation_link.Tab--active.tab-1-class').length).toBe(0); + expect(component.find('.Tab__navigation_link.Tab--active.tab-2-class').length).toBe(1); + expect(component.find('.Tab__navigation_link.Tab--active.tab-3-class').length).toBe(0); + expect(component.find('.Tabs__active_content .tab-1-content').length).toBe(0); + expect(component.find('.Tabs__active_content .tab-2-content').length).toBe(1); + + component.setState({ activeTabIndex: 2 }); + expect(component.find('.Tab__navigation_link.Tab--active.tab-1-class').length).toBe(0); + expect(component.find('.Tab__navigation_link.Tab--active.tab-2-class').length).toBe(0); + expect(component.find('.Tab__navigation_link.Tab--active.tab-3-class').length).toBe(1); + expect(component.find('.Tabs__active_content .tab-1-content').length).toBe(0); + expect(component.find('.Tabs__active_content .tab-2-content').length).toBe(0); + }); + }); +}); diff --git a/app/panel-android/components/content/__tests__/__snapshots__/BlockingCategories.jsx.snap b/app/panel-android/components/content/__tests__/__snapshots__/BlockingCategories.jsx.snap new file mode 100644 index 000000000..3e2a53b13 --- /dev/null +++ b/app/panel-android/components/content/__tests__/__snapshots__/BlockingCategories.jsx.snap @@ -0,0 +1,243 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel-android/components/content/BlockingCategories.jsx Snapshot tests with react-test-renderer BlockingCategories component as global 1`] = ` +
      +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + + + 1 blocking_category_blocked + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +

      + Category-2 +

      +
      + + 5 blocking_category_trackers + + + 3 blocking_category_blocked + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`app/panel-android/components/content/BlockingCategories.jsx Snapshot tests with react-test-renderer BlockingCategories component as site 1`] = ` +
      +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + + + 1 blocking_category_blocked + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +

      + Category-2 +

      +
      + + 5 blocking_category_trackers + + + 3 blocking_category_blocked + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; diff --git a/app/panel-android/components/content/__tests__/__snapshots__/BlockingCategory.jsx.snap b/app/panel-android/components/content/__tests__/__snapshots__/BlockingCategory.jsx.snap new file mode 100644 index 000000000..b46e865f0 --- /dev/null +++ b/app/panel-android/components/content/__tests__/__snapshots__/BlockingCategory.jsx.snap @@ -0,0 +1,451 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel-android/components/content/BlockingCategory.jsx Snapshot tests with react-test-renderer BlockingCategory component as global with all trackers blocked 1`] = ` +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + + + 1 blocking_category_blocked + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`app/panel-android/components/content/BlockingCategory.jsx Snapshot tests with react-test-renderer BlockingCategory component as global with mixed trackers blocked 1`] = ` +
      +
      + +
      +

      + Category-1 +

      +
      + + 2 blocking_category_trackers + + + 1 blocking_category_blocked + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`app/panel-android/components/content/BlockingCategory.jsx Snapshot tests with react-test-renderer BlockingCategory component as global with no trackers blocked 1`] = ` +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`app/panel-android/components/content/BlockingCategory.jsx Snapshot tests with react-test-renderer BlockingCategory component as site with all trackers ss_allowed 1`] = ` +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`app/panel-android/components/content/BlockingCategory.jsx Snapshot tests with react-test-renderer BlockingCategory component as site with all trackers ss_blocked 1`] = ` +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`app/panel-android/components/content/BlockingCategory.jsx Snapshot tests with react-test-renderer BlockingCategory component when sitePolicy Restricted 1`] = ` +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`app/panel-android/components/content/BlockingCategory.jsx Snapshot tests with react-test-renderer BlockingCategory component when sitePolicy Trusted & Paused 1`] = ` +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; + +exports[`app/panel-android/components/content/BlockingCategory.jsx Snapshot tests with react-test-renderer BlockingCategory component when sitePolicy Trusted 1`] = ` +
      +
      + +
      +

      + Category-1 +

      +
      + + 1 blocking_category_tracker + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +`; diff --git a/app/panel-android/components/content/__tests__/__snapshots__/BlockingTab.jsx.snap b/app/panel-android/components/content/__tests__/__snapshots__/BlockingTab.jsx.snap new file mode 100644 index 000000000..4d2206593 --- /dev/null +++ b/app/panel-android/components/content/__tests__/__snapshots__/BlockingTab.jsx.snap @@ -0,0 +1,396 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel-android/components/content/BlockingTab.jsx Snapshot tests with react-test-renderer BlockingTab component as global with falsy props 1`] = ` +
      +
      +

      + android_global_blocking_header +

      +
      + + +
    • + +
    • +
    • + +
    • +
    +
    +
    +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTab.jsx Snapshot tests with react-test-renderer BlockingTab component as global with tracker truthy props 1`] = ` +
    +
    +

    + android_global_blocking_header +

    +
    + + +
  • + +
  • +
  • + +
  • + +
    +
    +
    +
    +
    +
    + +
    +

    + Test1 +

    +
    + + 1 blocking_category_tracker + + + 1 blocking_category_blocked + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    + Test2 +

    +
    + + 5 blocking_category_trackers + + + 3 blocking_category_blocked + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTab.jsx Snapshot tests with react-test-renderer BlockingTab component as site with falsy props 1`] = ` +
    +
    +

    + android_site_blocking_header +

    +
    + + +
  • + +
  • + +
    +
    +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTab.jsx Snapshot tests with react-test-renderer BlockingTab component as site with tracker falsy props 1`] = ` +
    +
    +

    + android_site_blocking_header +

    +
    + + +
  • + +
  • + +
    +
    +
    +
    +
    +
    + +
    +

    + Test1 +

    +
    + + 1 blocking_category_tracker + + + 1 blocking_category_blocked + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +`; diff --git a/app/panel-android/components/content/__tests__/__snapshots__/BlockingTracker.jsx.snap b/app/panel-android/components/content/__tests__/__snapshots__/BlockingTracker.jsx.snap new file mode 100644 index 000000000..9b60d4d84 --- /dev/null +++ b/app/panel-android/components/content/__tests__/__snapshots__/BlockingTracker.jsx.snap @@ -0,0 +1,320 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel-android/components/content/BlockingTracker.jsx Snapshot tests with react-test-renderer BlockingTracker component when site Paused 1`] = ` +
    +
    +
    +
    +
    +
    + Tracker 1 +
    +
    +
    +
    +
    +
    + android_block +
    +
    + android_restrict +
    +
    + android_trust +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTracker.jsx Snapshot tests with react-test-renderer BlockingTracker component when site Restricted 1`] = ` +
    +
    +
    +
    +
    +
    + Tracker 1 +
    +
    +
    +
    +
    +
    + android_block +
    +
    + android_restrict +
    +
    + android_trust +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTracker.jsx Snapshot tests with react-test-renderer BlockingTracker component when site Trusted 1`] = ` +
    +
    +
    +
    +
    +
    + Tracker 1 +
    +
    +
    +
    +
    +
    + android_block +
    +
    + android_restrict +
    +
    + android_trust +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTracker.jsx Snapshot tests with react-test-renderer BlockingTracker component when tracker allowed 1`] = ` +
    +
    +
    +
    +
    +
    + Tracker 1 +
    +
    +
    +
    +
    +
    + android_block +
    +
    + android_restrict +
    +
    + android_untrust +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTracker.jsx Snapshot tests with react-test-renderer BlockingTracker component when tracker blocked 1`] = ` +
    +
    +
    +
    +
    +
    + Tracker 1 +
    +
    +
    +
    +
    +
    + android_unblock +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTracker.jsx Snapshot tests with react-test-renderer BlockingTracker component when tracker restricted 1`] = ` +
    +
    +
    +
    +
    +
    + Tracker 1 +
    +
    +
    +
    +
    +
    + android_block +
    +
    + android_unrestrict +
    +
    + android_trust +
    +
    +
    +`; + +exports[`app/panel-android/components/content/BlockingTracker.jsx Snapshot tests with react-test-renderer BlockingTracker component with falsy props 1`] = ` +
    +
    +
    +
    +
    +
    + Tracker 1 +
    +
    +
    +
    +
    +
    + android_block +
    +
    +
    +`; diff --git a/app/panel-android/components/content/__tests__/__snapshots__/DotsMenu.jsx.snap b/app/panel-android/components/content/__tests__/__snapshots__/DotsMenu.jsx.snap new file mode 100644 index 000000000..330edae49 --- /dev/null +++ b/app/panel-android/components/content/__tests__/__snapshots__/DotsMenu.jsx.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel-android/components/content/DotsMenu.jsx Snapshot tests with react-test-renderer DotsMenu component with 0 actions 1`] = ` +
    +
    +`; + +exports[`app/panel-android/components/content/DotsMenu.jsx Snapshot tests with react-test-renderer DotsMenu component with 3 actions 1`] = ` +
    + + +
  • + +
  • +
  • + +
  • + +
    +
    +`; diff --git a/app/panel-android/components/content/__tests__/__snapshots__/OverviewTab.jsx.snap b/app/panel-android/components/content/__tests__/__snapshots__/OverviewTab.jsx.snap new file mode 100644 index 000000000..d345de9e4 --- /dev/null +++ b/app/panel-android/components/content/__tests__/__snapshots__/OverviewTab.jsx.snap @@ -0,0 +1,509 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel-android/components/content/OverviewTab.jsx Snapshot tests with react-test-renderer OverviewTab component with falsy props and SiteNotScanned 1`] = ` +
    +
    +
    +
    + + + + + + + + +
    +
    + + + + + +
    +
    +
    +
    +
    +
    + summary_page_not_scanned +
    +
    + summary_description_not_scanned_1 +
    +
    + summary_description_not_scanned_2 +
    +
    +
    +
    +
    +
    +
    + + + summary_trust_site + + +
    +
    +
    +
    + + + summary_restrict_site + + +
    +
    +
    +
    +
    +
    + + + summary_pause_ghostery + + +
    +
    + + summary_show_menu + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + off +
    +
    +
    + enhanced_anti_tracking +
    +
    +
    +
    +
    +
    + off +
    +
    +
    + enhanced_ad_blocking +
    +
    +
    +
    +
    +
    + off +
    +
    +
    + smart_blocking +
    +
    +
    +
    +
    +
    +`; + +exports[`app/panel-android/components/content/OverviewTab.jsx Snapshot tests with react-test-renderer OverviewTab component with truthy props and no SiteNotScanned 1`] = ` +
    +
    +
    +
    + + + + + + + + +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + 29 +
    +
    +
    +
    +
    + + page_host + +
    +
    +
    + + trackers_blocked + + + + 0 + +
    +
    + + requests_modified + + + + 21 + +
    +
    +
    +
    +
    +
    +
    + + + summary_trust_site + + +
    +
    +
    +
    + + + summary_restrict_site + + +
    +
    +
    +
    +
    +
    + + + summary_resume_ghostery + + +
    +
    + + summary_show_menu + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + on +
    +
    +
    + enhanced_anti_tracking +
    +
    +
    +
    +
    +
    + on +
    +
    +
    + enhanced_ad_blocking +
    +
    +
    +
    +
    +
    + on +
    +
    +
    + smart_blocking +
    +
    +
    +
    +
    +
    +`; diff --git a/app/panel-android/components/content/__tests__/__snapshots__/Tabs.jsx.snap b/app/panel-android/components/content/__tests__/__snapshots__/Tabs.jsx.snap new file mode 100644 index 000000000..4033ce6f3 --- /dev/null +++ b/app/panel-android/components/content/__tests__/__snapshots__/Tabs.jsx.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel-android/components/content/Tabs.jsx Snapshot tests with react-test-renderer Tabs component with 3 Tab children components 1`] = ` +
    + +
    +
    + Tab 1 Content +
    +
    +
    +`; diff --git a/app/panel-android/index.jsx b/app/panel-android/index.jsx index 48861700b..90422a32f 100644 --- a/app/panel-android/index.jsx +++ b/app/panel-android/index.jsx @@ -4,21 +4,23 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0 */ + /** * @namespace PanelAndroidClasses */ + import React from 'react'; import ReactDOM from 'react-dom'; -import Panel from './components/Panel'; +import PanelAndroid from './components/PanelAndroid'; ReactDOM.render( ( - + ), document.getElementById('ghostery-content'), ); diff --git a/app/panel-android/utils/chart.js b/app/panel-android/utils/chart.js deleted file mode 100644 index 18b314b98..000000000 --- a/app/panel-android/utils/chart.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Chart Utilities - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ -/** - * @namespace PanelAndroidUtils - */ - -export default function fromTrackersToChartData(trackers) { - if (trackers.length < 1) { - return { - sum: 0, - arcs: [], - }; - } - - const arcs = []; - let startAngle = 0; - - const sum = trackers.map(tracker => tracker.numTotal).reduce((a, b) => a + b, 0); - - for (let i = 0; i < trackers.length; i += 1) { - const endAngle = startAngle + (trackers[i].numTotal * (360 / sum)); - - arcs.push({ - start: startAngle, - end: endAngle, - category: trackers[i].id, - }); - - startAngle = endAngle; - } - - return { - sum, - arcs, - }; -} diff --git a/app/panel-android/utils/tracker-info.js b/app/panel-android/utils/tracker-info.js index 9749513fc..df8e1a3a2 100644 --- a/app/panel-android/utils/tracker-info.js +++ b/app/panel-android/utils/tracker-info.js @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -16,10 +16,13 @@ import { apps } from '../../../cliqz/core/tracker_db_v2.json'; -// Link to whotracks.me website -export default function getUrlFromTrackerId(id) { - const trackerName = apps[id].name; +/** + * Look up WhoTracksMe url slug + * @param {Int} id Ghostery tracker ID + * @return {String} WTM slug + */ +export default function getSlugFromTrackerId(id) { + const trackerName = apps[id] && apps[id].name; const trackerWtm = (Object.values(apps).find(app => app.wtm && app.name === trackerName) || {}).wtm; - const slug = trackerWtm || '../tracker-not-found'; - return `https://whotracks.me/trackers/${slug}.html`; + return trackerWtm || '../tracker-not-found'; } diff --git a/app/panel/components/BuildingBlocks/CliqzFeature.jsx b/app/panel/components/BuildingBlocks/CliqzFeature.jsx index 91e01e40d..3abc03dc9 100644 --- a/app/panel/components/BuildingBlocks/CliqzFeature.jsx +++ b/app/panel/components/BuildingBlocks/CliqzFeature.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; import ClassNames from 'classnames'; import Tooltip from '../Tooltip'; @@ -162,4 +163,31 @@ class CliqzFeature extends React.Component { } } +CliqzFeature.propTypes = { + clickButton: PropTypes.func.isRequired, + type: PropTypes.oneOf([ + 'anti_track', + 'ad_block', + 'smart_block', + ]).isRequired, + active: PropTypes.bool, + cliqzInactive: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.number, + ]).isRequired, + isSmaller: PropTypes.bool.isRequired, + isCondensed: PropTypes.bool, + isTooltipHeader: PropTypes.bool, + isTooltipBody: PropTypes.bool, + tooltipPosition: PropTypes.string, +}; + +CliqzFeature.defaultProps = { + active: true, + isCondensed: false, + isTooltipHeader: false, + isTooltipBody: false, + tooltipPosition: '', +}; + export default CliqzFeature; diff --git a/app/panel/components/BuildingBlocks/DonutGraph.jsx b/app/panel/components/BuildingBlocks/DonutGraph.jsx index d48b9a0df..9cab95c7b 100644 --- a/app/panel/components/BuildingBlocks/DonutGraph.jsx +++ b/app/panel/components/BuildingBlocks/DonutGraph.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -13,6 +13,7 @@ import { throttle } from 'underscore'; import React from 'react'; +import PropTypes from 'prop-types'; import ClassNames from 'classnames'; import { arc, @@ -407,8 +408,24 @@ class DonutGraph extends React.Component { } } +DonutGraph.propTypes = { + categories: PropTypes.arrayOf(PropTypes.object), + adBlock: PropTypes.shape({}), + antiTracking: PropTypes.shape({}), + renderRedscale: PropTypes.bool.isRequired, + renderGreyscale: PropTypes.bool.isRequired, + totalCount: PropTypes.number.isRequired, + ghosteryFeatureSelect: PropTypes.oneOf([false, 1, 2]).isRequired, + isSmall: PropTypes.bool, + clickDonut: PropTypes.func, +}; + DonutGraph.defaultProps = { categories: [], + adBlock: { unknownTrackerCount: 0 }, + antiTracking: { unknownTrackerCount: 0 }, + clickDonut: () => {}, + isSmall: false, }; export default DonutGraph; diff --git a/app/panel/components/BuildingBlocks/GhosteryFeature.jsx b/app/panel/components/BuildingBlocks/GhosteryFeature.jsx index b4980cf54..d66e943bd 100644 --- a/app/panel/components/BuildingBlocks/GhosteryFeature.jsx +++ b/app/panel/components/BuildingBlocks/GhosteryFeature.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; import ClassNames from 'classnames'; import Tooltip from '../Tooltip'; import globals from '../../../../src/classes/Globals'; @@ -130,4 +131,19 @@ class GhosteryFeature extends React.Component { } } +GhosteryFeature.propTypes = { + handleClick: PropTypes.func.isRequired, + type: PropTypes.oneOf(['trust', 'restrict']).isRequired, + sitePolicy: PropTypes.oneOf([false, 1, 2]), + blockingPausedOrDisabled: PropTypes.bool.isRequired, + showText: PropTypes.bool.isRequired, + tooltipPosition: PropTypes.string.isRequired, + short: PropTypes.bool.isRequired, + narrow: PropTypes.bool.isRequired, +}; + +GhosteryFeature.defaultProps = { + sitePolicy: false, +}; + export default GhosteryFeature; diff --git a/app/panel/components/BuildingBlocks/NotScanned.jsx b/app/panel/components/BuildingBlocks/NotScanned.jsx index 0d1e36b64..74eb6dd34 100644 --- a/app/panel/components/BuildingBlocks/NotScanned.jsx +++ b/app/panel/components/BuildingBlocks/NotScanned.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; import ClassNames from 'classnames'; /** @@ -39,4 +40,12 @@ const NotScanned = ({ isSmall }) => { ); }; +NotScanned.propTypes = { + isSmall: PropTypes.bool, +}; + +NotScanned.defaultProps = { + isSmall: false, +}; + export default NotScanned; diff --git a/app/panel/components/BuildingBlocks/PauseButton.jsx b/app/panel/components/BuildingBlocks/PauseButton.jsx index 217588c87..d7ab0e287 100644 --- a/app/panel/components/BuildingBlocks/PauseButton.jsx +++ b/app/panel/components/BuildingBlocks/PauseButton.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; import ClassNames from 'classnames'; import Tooltip from '../Tooltip'; @@ -197,4 +198,22 @@ class PauseButton extends React.Component { } } +PauseButton.propTypes = { + isPaused: PropTypes.bool, + isPausedTimeout: PropTypes.number, + clickPause: PropTypes.func.isRequired, + dropdownItems: PropTypes.arrayOf(PropTypes.shape({ + val: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + name_condensed: PropTypes.string, + })).isRequired, + isCentered: PropTypes.bool.isRequired, + isCondensed: PropTypes.bool.isRequired, +}; + +PauseButton.defaultProps = { + isPaused: false, + isPausedTimeout: 0, +}; + export default PauseButton; diff --git a/app/panel/components/BuildingBlocks/RadioButtonGroup.jsx b/app/panel/components/BuildingBlocks/RadioButtonGroup.jsx index 376544392..2722ceddc 100644 --- a/app/panel/components/BuildingBlocks/RadioButtonGroup.jsx +++ b/app/panel/components/BuildingBlocks/RadioButtonGroup.jsx @@ -20,8 +20,12 @@ import RadioButton from './RadioButton'; * @memberof PanelBuildingBlocks */ const RadioButtonGroup = ({ indexClicked, handleItemClick, labels }) => { - const labelsEl = labels.map(label => ( -
    + const labelsEl = labels.map((label, index) => ( +
    handleItemClick(index)} + > {t(label)}
    )); diff --git a/app/panel/components/BuildingBlocks/__tests__/CliqzFeature.jsx b/app/panel/components/BuildingBlocks/__tests__/CliqzFeature.jsx new file mode 100644 index 000000000..cba12eac1 --- /dev/null +++ b/app/panel/components/BuildingBlocks/__tests__/CliqzFeature.jsx @@ -0,0 +1,171 @@ +/** + * Cliqz Feature Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import CliqzFeature from '../CliqzFeature'; + +// Fake the translation function to only return the translation key +global.t = function(str) { + return str; +}; + +// Fake the Tooltip implementation +jest.mock('../../Tooltip'); + +describe('app/panel/components/CliqzFeature.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('CliqzFeature is rendered correctly with falsy props', () => { + const component = renderer.create( +
    + {}} + type="anti_track" + active={false} + cliqzInactive={false} + isSmaller={false} + /> + {}} + type="ad_block" + active={false} + cliqzInactive={false} + isSmaller={false} + isCondensed={false} + isTooltipHeader={false} + isTooltipBody={false} + tooltipPosition="" + /> + {}} + type="smart_block" + active={false} + cliqzInactive={false} + isSmaller={false} + isCondensed={false} + isTooltipHeader={false} + isTooltipBody={false} + tooltipPosition="" + /> +
    + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('CliqzFeature is rendered correctly with some truthy props', () => { + const component = renderer.create( +
    + {}} + type="anti_track" + active + cliqzInactive={false} + isSmaller={false} + /> + {}} + type="ad_block" + active + cliqzInactive + isSmaller={false} + isCondensed={false} + isTooltipHeader={false} + isTooltipBody={false} + tooltipPosition="" + /> + {}} + type="smart_block" + active + cliqzInactive + isSmaller={false} + isCondensed + isTooltipHeader + isTooltipBody + tooltipPosition="top" + /> +
    + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('CliqzFeature is rendered correctly with all truthy props', () => { + const component = renderer.create( +
    + {}} + type="anti_track" + active + cliqzInactive + isSmaller + isCondensed + isTooltipHeader + isTooltipBody + tooltipPosition="right" + /> + {}} + type="ad_block" + active + cliqzInactive + isSmaller + isCondensed + isTooltipHeader + isTooltipBody + tooltipPosition="top" + /> + {}} + type="smart_block" + active + cliqzInactive + isSmaller + isCondensed + isTooltipHeader + isTooltipBody + tooltipPosition="top" + /> +
    + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('CliqzFeature handles clicks correctly', () => { + const clickButton = jest.fn(); + const component = shallow( + + ); + expect(clickButton.mock.calls.length).toBe(0); + component.find('.CliqzFeature').simulate('click'); + expect(clickButton.mock.calls.length).toBe(0); + + component.setProps({ cliqzInactive: false }); + component.find('.CliqzFeature').simulate('click'); + + component.setProps({ active: false }); + component.find('.CliqzFeature').simulate('click'); + expect(clickButton.mock.calls.length).toBe(2); + expect(clickButton.mock.calls[0][0].status).toBe(true); + expect(clickButton.mock.calls[1][0].status).toBe(false); + }); + }); +}); diff --git a/app/panel/components/BuildingBlocks/__tests__/DonutGraph.jsx b/app/panel/components/BuildingBlocks/__tests__/DonutGraph.jsx new file mode 100644 index 000000000..29d3d9311 --- /dev/null +++ b/app/panel/components/BuildingBlocks/__tests__/DonutGraph.jsx @@ -0,0 +1,137 @@ +/** + * Donut Graph Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import DonutGraph from '../DonutGraph'; + +// Fake the translation function to only return the translation key +global.t = function(str) { + return str; +}; + +// Fake the Tooltip implementation +jest.mock('../../Tooltip'); + +describe('app/panel/components/DonutGraph.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('DonutGraph is rendered correctly when props are falsy', () => { + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('DonutGraph is rendered correctly when some props are truthy', () => { + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('DonutGraph is rendered correctly when all props are truthy', () => { + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('DonutGraph handles clicks correctly', () => { + const clickDonut = jest.fn(); + const component = shallow( + + ); + expect(clickDonut.mock.calls.length).toBe(0); + component.find('.DonutGraph__textCountContainer').simulate('click'); + expect(clickDonut.mock.calls.length).toBe(1); + expect(clickDonut.mock.calls[0][0].type).toBe('trackers'); + expect(clickDonut.mock.calls[0][0].name).toBe('all'); + }); + }); +}); diff --git a/app/panel/components/BuildingBlocks/__tests__/GhosteryFeature.jsx b/app/panel/components/BuildingBlocks/__tests__/GhosteryFeature.jsx new file mode 100644 index 000000000..e8516f147 --- /dev/null +++ b/app/panel/components/BuildingBlocks/__tests__/GhosteryFeature.jsx @@ -0,0 +1,195 @@ +/** + * Ghostery Feature Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import GhosteryFeature from '../GhosteryFeature'; + +// Fake the translation function to only return the translation key +global.t = function(str) { + return str; +}; + +// Fake the Tooltip implementation +jest.mock('../../Tooltip'); + +describe('app/panel/components/GhosteryFeature.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('GhosteryFeature is rendered correctly props are falsy', () => { + const component = renderer.create( +
    + {}} + type="trust" + sitePolicy={false} + blockingPausedOrDisabled={false} + showText={false} + tooltipPosition="" + short={false} + narrow={false} + /> + {}} + type="restrict" + sitePolicy={false} + blockingPausedOrDisabled={false} + showText={false} + tooltipPosition="" + short={false} + narrow={false} + /> +
    + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('GhosteryFeature is rendered correctly some props are truthy', () => { + const component = renderer.create( +
    + {}} + type="trust" + sitePolicy={2} + blockingPausedOrDisabled={false} + showText + tooltipPosition="right" + short + narrow={false} + /> + {}} + type="restrict" + sitePolicy={2} + blockingPausedOrDisabled={false} + showText + tooltipPosition="right" + short + narrow={false} + /> +
    + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('GhosteryFeature is rendered correctly props are truthy', () => { + const component = renderer.create( +
    + {}} + type="trust" + sitePolicy={1} + blockingPausedOrDisabled + showText + tooltipPosition="top" + short + narrow + /> + {}} + type="restrict" + sitePolicy={1} + blockingPausedOrDisabled + showText + tooltipPosition="top" + short + narrow + /> +
    + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('GhosteryFeature has the correct class names', () => { + const component = shallow( + {}} + type="restrict" + sitePolicy={false} + blockingPausedOrDisabled={false} + showText={false} + tooltipPosition="" + short={false} + narrow={false} + /> + ); + expect(component.find('.GhosteryFeatureButton.restrict').length).toBe(1); + expect(component.find('.GhosteryFeatureButton.trust').length).toBe(0); + expect(component.find('.GhosteryFeatureButton.not-clickable').length).toBe(0); + expect(component.find('.GhosteryFeatureButton.clickable').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--normal').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--short').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--narrow').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--inactive').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--active').length).toBe(0); + + component.setProps({ narrow: true, sitePolicy: 1 }); + expect(component.find('.GhosteryFeatureButton--normal').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--short').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--narrow').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--inactive').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--active').length).toBe(1); + + component.setProps({ short: true, type: 'trust' }); + expect(component.find('.GhosteryFeatureButton.restrict').length).toBe(0); + expect(component.find('.GhosteryFeatureButton.trust').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--normal').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--short').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--narrow').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--inactive').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--active').length).toBe(0); + + component.setProps({ narrow: false, sitePolicy: 2 }); + expect(component.find('.GhosteryFeatureButton--normal').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--short').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--narrow').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--inactive').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--active').length).toBe(1); + + component.setProps({ short: false, blockingPausedOrDisabled: true }); + expect(component.find('.GhosteryFeatureButton.not-clickable').length).toBe(1); + expect(component.find('.GhosteryFeatureButton.clickable').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--normal').length).toBe(1); + expect(component.find('.GhosteryFeatureButton--short').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--narrow').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--inactive').length).toBe(0); + expect(component.find('.GhosteryFeatureButton--active').length).toBe(1); + }); + + test('GhosteryFeature handles clicks correctly', () => { + const handleClick = jest.fn(); + const component = shallow( + + ); + + expect(handleClick.mock.calls.length).toBe(0); + component.find('.GhosteryFeatureButton').simulate('click'); + expect(handleClick.mock.calls.length).toBe(0); + + component.setProps({ blockingPausedOrDisabled: false }); + component.find('.GhosteryFeatureButton').simulate('click'); + expect(handleClick.mock.calls.length).toBe(1); + }); + }); +}); diff --git a/app/panel/components/BuildingBlocks/__tests__/NotScanned.jsx b/app/panel/components/BuildingBlocks/__tests__/NotScanned.jsx new file mode 100644 index 000000000..ac445df7b --- /dev/null +++ b/app/panel/components/BuildingBlocks/__tests__/NotScanned.jsx @@ -0,0 +1,39 @@ +/** + * Not Scanned Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import NotScanned from '../NotScanned'; + +// Fake the translation function to only return the translation key +global.t = function(str) { + return str; +}; + +describe('app/panel/components/NotScanned.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('NotScanned is rendered correctly when no props are passed', () => { + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('NotScanned is rendered correctly when small', () => { + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/panel/components/__tests__/PauseButton.jsx b/app/panel/components/BuildingBlocks/__tests__/PauseButton.jsx similarity index 82% rename from app/panel/components/__tests__/PauseButton.jsx rename to app/panel/components/BuildingBlocks/__tests__/PauseButton.jsx index 4d89ba7b0..37ba604ee 100644 --- a/app/panel/components/__tests__/PauseButton.jsx +++ b/app/panel/components/BuildingBlocks/__tests__/PauseButton.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -14,7 +14,7 @@ import React from 'react'; import renderer from 'react-test-renderer'; import { shallow } from 'enzyme'; -import PauseButton from '../BuildingBlocks/PauseButton'; +import PauseButton from '../PauseButton'; // Fake the translation function to only return the translation key global.t = function(str) { @@ -22,7 +22,7 @@ global.t = function(str) { }; // Fake the Tooltip implementation -jest.mock('../Tooltip'); +jest.mock('../../Tooltip'); describe('app/panel/components/BuildingBlocks/PauseButton.jsx', () => { describe('Snapshot tests with react-test-renderer', () => { @@ -170,5 +170,41 @@ describe('app/panel/components/BuildingBlocks/PauseButton.jsx', () => { expect(component.find('.button.button-pause.smaller').length).toBe(0); expect(component.find('.button.button-pause.smallest').length).toBe(1); }); + + test('the pause button correctly handles clicks', () => { + const clickPause = jest.fn(); + const dropdownItems = [ + { name: t('pause_30_min'), name_condensed: t('pause_30_min_condensed'), val: 30 }, + { name: t('pause_1_hour'), name_condensed: t('pause_1_hour_condensed'), val: 60 }, + { name: t('pause_24_hours'), name_condensed: t('pause_24_hours_condensed'), val: 1440 }, + ]; + const component = shallow( + + ); + expect(clickPause.mock.calls.length).toBe(0); + component.find('.button-pause').simulate('click'); + + component.setState({ showDropdown: true }); + component.find('.dropdown').childAt(0).simulate('click'); + + component.setState({ showDropdown: true }); + component.find('.dropdown').childAt(1).simulate('click'); + + component.setState({ showDropdown: true }); + component.find('.dropdown').childAt(2).simulate('click'); + + expect(clickPause.mock.calls.length).toBe(4); + expect(clickPause.mock.calls[0][0]).toBeFalsy(); + expect(clickPause.mock.calls[1][0]).toBe(30); + expect(clickPause.mock.calls[2][0]).toBe(60); + expect(clickPause.mock.calls[3][0]).toBe(1440); + }); }); }); diff --git a/app/panel/components/BuildingBlocks/__tests__/__snapshots__/CliqzFeature.jsx.snap b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/CliqzFeature.jsx.snap new file mode 100644 index 000000000..16ba9dffd --- /dev/null +++ b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/CliqzFeature.jsx.snap @@ -0,0 +1,178 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/CliqzFeature.jsx Snapshot tests with react-test-renderer CliqzFeature is rendered correctly with all truthy props 1`] = ` +
    +
    +
    + on +
    +
    +
    + enhanced_anti_tracking +
    +
    +
    +
    + on +
    +
    +
    + enhanced_ad_blocking +
    +
    +
    +
    + on +
    +
    +
    + smart_blocking +
    +
    +
    +`; + +exports[`app/panel/components/CliqzFeature.jsx Snapshot tests with react-test-renderer CliqzFeature is rendered correctly with falsy props 1`] = ` +
    +
    +
    + off +
    +
    +
    + enhanced_anti_tracking +
    +
    +
    +
    + off +
    +
    +
    + enhanced_ad_blocking +
    +
    +
    +
    + off +
    +
    +
    + smart_blocking +
    +
    +
    +`; + +exports[`app/panel/components/CliqzFeature.jsx Snapshot tests with react-test-renderer CliqzFeature is rendered correctly with some truthy props 1`] = ` +
    +
    +
    + on +
    +
    +
    + enhanced_anti_tracking +
    +
    +
    +
    + on +
    +
    +
    + enhanced_ad_blocking +
    +
    +
    +
    + on +
    +
    +
    + smart_blocking +
    +
    +
    +`; diff --git a/app/panel/components/BuildingBlocks/__tests__/__snapshots__/DonutGraph.jsx.snap b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/DonutGraph.jsx.snap new file mode 100644 index 000000000..b960d1f0e --- /dev/null +++ b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/DonutGraph.jsx.snap @@ -0,0 +1,132 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/DonutGraph.jsx Snapshot tests with react-test-renderer DonutGraph is rendered correctly when all props are truthy 1`] = ` +
    +
    + + category-1 + + + category-2 + + + category-3 + + + category-4 + + + unknown + +
    +
    +
    +
    + 38 +
    +
    +
    +`; + +exports[`app/panel/components/DonutGraph.jsx Snapshot tests with react-test-renderer DonutGraph is rendered correctly when props are falsy 1`] = ` +
    +
    +
    +
    +
    + 0 +
    +
    +
    +`; + +exports[`app/panel/components/DonutGraph.jsx Snapshot tests with react-test-renderer DonutGraph is rendered correctly when some props are truthy 1`] = ` +
    +
    + + category-1 + + + category-2 + + + category-3 + + + category-4 + + + unknown + +
    +
    +
    +
    + 8 +
    +
    +
    +`; diff --git a/app/panel/components/BuildingBlocks/__tests__/__snapshots__/GhosteryFeature.jsx.snap b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/GhosteryFeature.jsx.snap new file mode 100644 index 000000000..b1c00c3ea --- /dev/null +++ b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/GhosteryFeature.jsx.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/GhosteryFeature.jsx Snapshot tests with react-test-renderer GhosteryFeature is rendered correctly props are falsy 1`] = ` +
    +
    + + + + + +
    +
    + + + + + +
    +
    +`; + +exports[`app/panel/components/GhosteryFeature.jsx Snapshot tests with react-test-renderer GhosteryFeature is rendered correctly props are truthy 1`] = ` +
    +
    + + + summary_trust_site + + +
    +
    + + + summary_restrict_site_active + + +
    +
    +`; + +exports[`app/panel/components/GhosteryFeature.jsx Snapshot tests with react-test-renderer GhosteryFeature is rendered correctly some props are truthy 1`] = ` +
    +
    + + + summary_trust_site_active + + +
    +
    + + + summary_restrict_site + + +
    +
    +`; diff --git a/app/panel/components/BuildingBlocks/__tests__/__snapshots__/NotScanned.jsx.snap b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/NotScanned.jsx.snap new file mode 100644 index 000000000..eebea9ac8 --- /dev/null +++ b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/NotScanned.jsx.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/NotScanned.jsx Snapshot tests with react-test-renderer NotScanned is rendered correctly when no props are passed 1`] = ` +
    +
    + summary_page_not_scanned +
    +
    + summary_description_not_scanned_1 +
    +
    + summary_description_not_scanned_2 +
    +
    +`; + +exports[`app/panel/components/NotScanned.jsx Snapshot tests with react-test-renderer NotScanned is rendered correctly when small 1`] = ` +
    +
    + summary_page_not_scanned +
    +
    + summary_description_not_scanned_1 +
    +
    + summary_description_not_scanned_2 +
    +
    +`; diff --git a/app/panel/components/__tests__/__snapshots__/PauseButton.jsx.snap b/app/panel/components/BuildingBlocks/__tests__/__snapshots__/PauseButton.jsx.snap similarity index 100% rename from app/panel/components/__tests__/__snapshots__/PauseButton.jsx.snap rename to app/panel/components/BuildingBlocks/__tests__/__snapshots__/PauseButton.jsx.snap diff --git a/app/panel/components/Header.jsx b/app/panel/components/Header.jsx index ee3770b4f..f20bb7ac5 100644 --- a/app/panel/components/Header.jsx +++ b/app/panel/components/Header.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -213,9 +213,11 @@ class Header extends React.Component { ); const tabs = ( -
    - {simpleTab} - {detailedTab} +
    +
    + {simpleTab} + {detailedTab} +
    ); diff --git a/app/panel/components/Panel.jsx b/app/panel/components/Panel.jsx index 7aae2e9fb..01ca9aab6 100644 --- a/app/panel/components/Panel.jsx +++ b/app/panel/components/Panel.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -19,6 +19,7 @@ import ThemeContext from '../contexts/ThemeContext'; import DynamicUIPortContext from '../contexts/DynamicUIPortContext'; import { sendMessage } from '../utils/msg'; import { setTheme } from '../utils/utils'; +import { log } from '../../../src/utils/common'; const INSIGHTS = 'insights'; const PLUS = 'plus'; @@ -51,7 +52,9 @@ class Panel extends React.Component { const { body } = msg; - if (body.panel) { + if (body.error) { + log(`Error: ${body.error}`); + } else if (body.panel) { this._initializeData(body); } else if (this._dynamicUIDataInitialized) { actions.updatePanelData(body); diff --git a/app/panel/components/Settings/Account.jsx b/app/panel/components/Settings/Account.jsx index 15273c354..52d4e2e20 100644 --- a/app/panel/components/Settings/Account.jsx +++ b/app/panel/components/Settings/Account.jsx @@ -14,6 +14,7 @@ * @namespace SettingsComponents */ import React from 'react'; +import ImportExport from './ImportExport'; import { sendMessage } from '../../utils/msg'; import globals from '../../../../src/classes/Globals'; /** @@ -44,43 +45,12 @@ class Account extends React.Component { window.close(); } - /** - * Trigger action to export settings in JSON format and save it to a file. - */ - clickExportSettings = () => { - const { actions, settingsData } = this.props; - actions.exportSettings(settingsData.pageUrl); - } - - /** - * Trigger custom Import dialog or a native Open File dialog depending on browser. - */ - clickImportSettings = () => { - const { actions, settingsData } = this.props; - const browserName = globals.BROWSER_INFO.name; - if (browserName === 'firefox') { - // show ghostery dialog window for import - actions.importSettingsDialog(settingsData.pageUrl); - } else { - // for chrome and opera, use the native File Dialog - this.selectedFile.click(); - } - } - - /** - * Parse settings file imported via native browser window. Called via input#select-file onChange. - */ - validateImportFile = () => { - const { actions } = this.props; - actions.importSettingsNative(this.selectedFile.files[0]); - } - /** * Render Account subview. * @return {ReactComponent} ReactComponent instance */ render() { - const { settingsData } = this.props; + const { settingsData, actions } = this.props; const { email, firstName, lastName } = settingsData.user ? settingsData.user : {}; const accountName = (firstName || lastName) ? (firstName ? (`${firstName} ${lastName}`) : lastName) : ''; return ( @@ -116,17 +86,10 @@ class Account extends React.Component {

    { t('settings_edit_account') }

    -
    -

    { t('settings_export_header') }

    -

    { t('settings_export_text') }

    -

    { settingsData.exportResultText }

    -
    -

    { t('settings_import_header') }

    -

    { t('settings_import_text') }

    -

    { t('settings_import_warning') }

    -

    { settingsData.importResultText }

    - { this.selectedFile = input; }} type="file" id="select-file" name="select-file" onChange={this.validateImportFile} /> -
    +
    diff --git a/app/panel/components/Settings/AdBlocker.jsx b/app/panel/components/Settings/AdBlocker.jsx index 90e44a6c4..2a5466602 100644 --- a/app/panel/components/Settings/AdBlocker.jsx +++ b/app/panel/components/Settings/AdBlocker.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -49,13 +49,14 @@ const AdBlocker = (props) => { }; AdBlocker.propTypes = { - settingsData: PropTypes.shape({ - cliqz_adb_mode: PropTypes.number, - }), actions: PropTypes.shape({ selectItem: PropTypes.func.isRequired, }).isRequired, + settingsData: PropTypes.shape({ + cliqz_adb_mode: PropTypes.number, + }), }; + AdBlocker.defaultProps = { settingsData: { cliqz_adb_mode: 0, diff --git a/app/panel/components/Settings/GeneralSettings.jsx b/app/panel/components/Settings/GeneralSettings.jsx index 1381b34e2..02e5704b0 100644 --- a/app/panel/components/Settings/GeneralSettings.jsx +++ b/app/panel/components/Settings/GeneralSettings.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; import moment from 'moment/min/moment-with-locales.min'; /** * @class Implement General Settings subview. The view opens from the @@ -88,6 +89,7 @@ class GeneralSettings extends React.Component { render() { const { settingsData, toggleCheckbox } = this.props; const { dbLastUpdated } = this.state; + return (
    @@ -179,4 +181,23 @@ class GeneralSettings extends React.Component { } } +GeneralSettings.propTypes = { + actions: PropTypes.shape({ + updateDatabase: PropTypes.func.isRequired, + }).isRequired, + toggleCheckbox: PropTypes.func.isRequired, + settingsData: PropTypes.shape({ + language: PropTypes.string.isRequired, + bugs_last_checked: PropTypes.number.isRequired, + enable_autoupdate: PropTypes.bool.isRequired, + dbUpdateText: PropTypes.string, + show_tracker_urls: PropTypes.bool.isRequired, + enable_click2play: PropTypes.bool.isRequired, + enable_click2play_social: PropTypes.bool.isRequired, + toggle_individual_trackers: PropTypes.bool.isRequired, + ignore_first_party: PropTypes.bool.isRequired, + block_by_default: PropTypes.bool.isRequired, + }).isRequired, +}; + export default GeneralSettings; diff --git a/app/panel/components/Settings/ImportExport.jsx b/app/panel/components/Settings/ImportExport.jsx new file mode 100644 index 000000000..f781200b0 --- /dev/null +++ b/app/panel/components/Settings/ImportExport.jsx @@ -0,0 +1,86 @@ +/** + * Account Settings Import/Export Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import globals from '../../../../src/classes/Globals'; + +class ImportExport extends React.Component { + /** + * Trigger action to export settings in JSON format and save it to a file. + */ + clickExportSettings = () => { + const { actions, settingsData } = this.props; + actions.exportSettings(settingsData.pageUrl); + } + + /** + * Trigger custom Import dialog or a native Open File dialog depending on browser. + */ + clickImportSettings = () => { + const { actions, settingsData } = this.props; + const browserName = globals.BROWSER_INFO.name; + if (browserName === 'firefox') { + // show ghostery dialog window for import + actions.importSettingsDialog(settingsData.pageUrl); + } else { + // for chrome and opera, use the native File Dialog + this.selectedFile.click(); + } + } + + /** + * Parse settings file imported via native browser window. Called via input#select-file onChange. + */ + validateImportFile = () => { + const { actions } = this.props; + actions.importSettingsNative(this.selectedFile.files[0]); + } + + /** + * Render Account subview. + * @return {ReactComponent} ReactComponent instance + */ + render() { + const { settingsData } = this.props; + return ( +
    +

    { t('settings_export_header') }

    +

    { t('settings_export_text') }

    +

    { settingsData.exportResultText }

    +
    +

    { t('settings_import_header') }

    +

    { t('settings_import_text') }

    +

    { t('settings_import_warning') }

    +

    { settingsData.importResultText }

    + { this.selectedFile = input; }} type="file" id="select-file" name="select-file" onChange={this.validateImportFile} /> +
    + ); + } +} + +ImportExport.propTypes = { + settingsData: PropTypes.shape({ + pageUrl: PropTypes.string.isRequired, + exportResultText: PropTypes.string.isRequired, + importResultText: PropTypes.string.isRequired, + actionSuccess: PropTypes.bool.isRequired, + }).isRequired, + actions: PropTypes.shape({ + exportSettings: PropTypes.func.isRequired, + importSettingsDialog: PropTypes.func.isRequired, + importSettingsNative: PropTypes.func.isRequired, + }).isRequired, +}; + +export default ImportExport; diff --git a/app/panel/components/Settings/Notifications.jsx b/app/panel/components/Settings/Notifications.jsx index b8047f727..9efbef082 100644 --- a/app/panel/components/Settings/Notifications.jsx +++ b/app/panel/components/Settings/Notifications.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; /** * @class Implement Notification subview as a React component. * The view opens from the left-side menu of the main @@ -74,4 +75,17 @@ const Notifications = ({ settingsData, toggleCheckbox }) => (
    ); +Notifications.propTypes = { + toggleCheckbox: PropTypes.func.isRequired, + settingsData: PropTypes.shape({ + show_cmp: PropTypes.bool.isRequired, + notify_upgrade_updates: PropTypes.bool.isRequired, + notify_promotions: PropTypes.bool.isRequired, + notify_library_updates: PropTypes.bool.isRequired, + reload_banner_status: PropTypes.bool, + trackers_banner_status: PropTypes.bool, + show_badge: PropTypes.bool.isRequired, + }).isRequired, +}; + export default Notifications; diff --git a/app/panel/components/Settings/OptIn.jsx b/app/panel/components/Settings/OptIn.jsx index 5f7fbd80a..f74262c0b 100644 --- a/app/panel/components/Settings/OptIn.jsx +++ b/app/panel/components/Settings/OptIn.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,9 +12,11 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; import globals from '../../../../src/classes/Globals'; -const { IS_CLIQZ } = globals; +const { IS_CLIQZ, BROWSER_INFO } = globals; +const IS_ANDROID = (BROWSER_INFO.os === 'android'); /** * @class Implement Opt In subview as a React component. @@ -55,7 +57,7 @@ const OptIn = ({ settingsData, toggleCheckbox }) => (
    )} - {!IS_CLIQZ && ( + {!IS_CLIQZ && !IS_ANDROID && (
    @@ -73,4 +75,13 @@ const OptIn = ({ settingsData, toggleCheckbox }) => (
    ); +OptIn.propTypes = { + toggleCheckbox: PropTypes.func.isRequired, + settingsData: PropTypes.shape({ + enable_metrics: PropTypes.bool.isRequired, + enable_human_web: PropTypes.bool.isRequired, + enable_offers: PropTypes.bool.isRequired, + }).isRequired, +}; + export default OptIn; diff --git a/app/panel/components/Settings/TrustAndRestrict.jsx b/app/panel/components/Settings/TrustAndRestrict.jsx index 620fc3faf..03ed86e6b 100644 --- a/app/panel/components/Settings/TrustAndRestrict.jsx +++ b/app/panel/components/Settings/TrustAndRestrict.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,6 +12,7 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; import Sites from './Sites'; /** * @class Implement Trust and Restrict subview presenting the lists @@ -191,12 +192,14 @@ class TrustAndRestrict extends React.Component {

    { t('settings_trusted_restricted_sites') }

    -
    -
    - {t('settings_trusted_sites')} -
    -
    - {t('settings_restricted_sites')} +
    +
    +
    + {t('settings_trusted_sites')} +
    +
    + {t('settings_restricted_sites')} +
    @@ -234,4 +237,12 @@ class TrustAndRestrict extends React.Component { } } +TrustAndRestrict.propTypes = { + actions: PropTypes.shape({ + updateSitePolicy: PropTypes.func.isRequired, + }).isRequired, + site_whitelist: PropTypes.arrayOf(PropTypes.string).isRequired, + site_blacklist: PropTypes.arrayOf(PropTypes.string).isRequired, +}; + export default TrustAndRestrict; diff --git a/app/panel/components/Settings/__tests__/AdBlocker.jsx b/app/panel/components/Settings/__tests__/AdBlocker.jsx new file mode 100644 index 000000000..8ef332c2e --- /dev/null +++ b/app/panel/components/Settings/__tests__/AdBlocker.jsx @@ -0,0 +1,95 @@ +/** + * AdBlocker Settings Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import AdBlocker from '../AdBlocker'; + +describe('app/panel/Settings/AdBlocker.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('AdBlocker is rendered correctly with AdOnly checked', () => { + const settingsData = { + cliqz_adb_mode: 0, + }; + const actions = { + selectItem: () => {}, + }; + + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('AdBlocker is rendered correctly with Ads & Trackers checked', () => { + const settingsData = { + cliqz_adb_mode: 1, + }; + const actions = { + selectItem: () => {}, + }; + + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('AdBlocker is rendered correctly with Ads, Trackers, & Annoyances checked', () => { + const settingsData = { + cliqz_adb_mode: 2, + }; + const actions = { + selectItem: () => {}, + }; + + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('AdBlocker functions correctly', () => { + const settingsData = { + cliqz_adb_mode: 0, + }; + const actions = { + selectItem: jest.fn(), + }; + + const component = mount( + + ); + + expect(actions.selectItem.mock.calls.length).toBe(0); + component.find('.RadioButtonGroup__label').at(0).simulate('click'); + component.find('.RadioButton__outerCircle').at(2).simulate('click'); + expect(actions.selectItem.mock.calls.length).toBe(2); + }); + }); +}); diff --git a/app/panel/components/Settings/__tests__/GeneralSettings.jsx b/app/panel/components/Settings/__tests__/GeneralSettings.jsx new file mode 100644 index 000000000..d67ab814e --- /dev/null +++ b/app/panel/components/Settings/__tests__/GeneralSettings.jsx @@ -0,0 +1,113 @@ +/** + * GeneralSettings Settings Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import GeneralSettings from '../GeneralSettings'; + +jest.spyOn(GeneralSettings, 'getDbLastUpdated').mockImplementation(settingsData => settingsData.bugs_last_checked); + +describe('app/panel/Settings/GeneralSettings.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('GeneralSettings is rendered correctly with falsy props', () => { + const settingsData = { + language: 'en', + bugs_last_checked: 0, + enable_autoupdate: false, + show_tracker_urls: false, + enable_click2play: false, + enable_click2play_social: false, + toggle_individual_trackers: false, + ignore_first_party: false, + block_by_default: false, + }; + const actions = { + updateDatabase: () => {}, + }; + + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('GeneralSettings is rendered correctly with truthy props', () => { + const settingsData = { + language: 'en', + bugs_last_checked: 1000000, + enable_autoupdate: true, + dbUpdateText: 'database-updated-text', + show_tracker_urls: true, + enable_click2play: true, + enable_click2play_social: true, + toggle_individual_trackers: true, + ignore_first_party: true, + block_by_default: true, + }; + const actions = { + updateDatabase: () => {}, + }; + + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('GeneralSettings functions correctly', () => { + const settingsData = { + language: 'en', + bugs_last_checked: 0, + enable_autoupdate: false, + show_tracker_urls: false, + enable_click2play: false, + enable_click2play_social: false, + toggle_individual_trackers: false, + ignore_first_party: false, + block_by_default: false, + }; + const actions = { + updateDatabase: jest.fn(), + }; + const toggleCheckbox = jest.fn(); + + const component = mount( + + ); + + expect(actions.updateDatabase.mock.calls.length).toBe(0); + component.find('#update-now-span').simulate('click'); + expect(actions.updateDatabase.mock.calls.length).toBe(1); + + expect(toggleCheckbox.mock.calls.length).toBe(0); + component.find('input[type="checkbox"]').at(0).simulate('click'); + component.find('#settings-allow-trackers').simulate('click'); + expect(toggleCheckbox.mock.calls.length).toBe(2); + }); + }); +}); diff --git a/app/panel/components/Settings/__tests__/ImportExport.jsx b/app/panel/components/Settings/__tests__/ImportExport.jsx new file mode 100644 index 000000000..a09516e4e --- /dev/null +++ b/app/panel/components/Settings/__tests__/ImportExport.jsx @@ -0,0 +1,148 @@ +/** + * Import Export Settings Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import ImportExport from '../ImportExport'; + +jest.mock('../../../../../src/classes/Globals', () => ({ + BROWSER_INFO: { + name: 'firefox', + } +})); + +describe('app/panel/Settings/ImportExport.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('ImportExport is rendered correctly with baseline props', () => { + const settingsData = { + pageUrl: '', + exportResultText: '', + importResultText: '', + actionSuccess: false, + }; + const actions = { + exportSettings: () => {}, + importSettingsDialog: () => {}, + importSettingsNative: () => {}, + }; + + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('ImportExport is rendered correctly with happy-path props', () => { + const settingsData = { + pageUrl: 'https://example.com', + exportResultText: 'Your settings have been successfully exported to your downloads folder on August 8, 2020 6:08 PM', + importResultText: 'Your settings have been successfully imported on September 12, 2020 6:08 PM', + actionSuccess: true, + }; + const actions = { + exportSettings: () => {}, + importSettingsDialog: () => {}, + importSettingsNative: () => {}, + }; + + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('ImportExport is rendered correctly with unhappy-path props', () => { + const settingsData = { + pageUrl: 'chrome://extensions', + exportResultText: 'Ghostery cannot export settings when the current page is one of the reserved browser pages. Please navigate to a different page and try again.', + importResultText: 'That is the incorrect file type. Please choose a .ghost file and try again.', + actionSuccess: false, + }; + const actions = { + exportSettings: () => {}, + importSettingsDialog: () => {}, + importSettingsNative: () => {}, + }; + + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('ImportExport functions correctly', () => { + const settingsData = { + pageUrl: '', + exportResultText: '', + importResultText: '', + actionSuccess: false, + }; + const actions = { + exportSettings: jest.fn(), + importSettingsDialog: jest.fn(), + importSettingsNative: jest.fn(), + }; + const component = mount( + + ); + + expect(actions.exportSettings.mock.calls.length).toBe(0); + expect(actions.importSettingsDialog.mock.calls.length).toBe(0); + expect(actions.importSettingsNative.mock.calls.length).toBe(0); + expect(component.find('.export-result').text()).toBe(''); + expect(component.find('.import-result').text()).toBe(''); + + component.find('.export').simulate('click'); + component.setProps({ + settingsData: { + pageUrl: '', + exportResultText: 'export-result-text', + importResultText: '', + actionSuccess: true, + } + }); + expect(actions.exportSettings.mock.calls.length).toBe(1); + expect(component.find('.export-result').text()).toBe('export-result-text'); + + component.find('.import').simulate('click'); + component.setProps({ + settingsData: { + pageUrl: '', + exportResultText: '', + importResultText: 'import-result-text', + actionSuccess: true, + } + }); + expect(actions.importSettingsDialog.mock.calls.length).toBe(1); + expect(component.find('.import-result').text()).toBe('import-result-text'); + + component.find('#select-file').simulate('change'); + expect(actions.importSettingsNative.mock.calls.length).toBe(1); + }); + }); +}); diff --git a/app/panel/components/Settings/__tests__/Notifications.jsx b/app/panel/components/Settings/__tests__/Notifications.jsx new file mode 100644 index 000000000..32ea8e74e --- /dev/null +++ b/app/panel/components/Settings/__tests__/Notifications.jsx @@ -0,0 +1,93 @@ +/** + * Notifications Settings Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import Notifications from '../Notifications'; + +describe('app/panel/Settings/Notifications.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('Notifications is rendered correctly with falsy props', () => { + const settingsData = { + show_cmp: false, + notify_upgrade_updates: false, + notify_promotions: false, + notify_library_updates: false, + reload_banner_status: false, + trackers_banner_status: false, + show_badge: false, + }; + + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('Notifications is rendered correctly with truthy props', () => { + const settingsData = { + show_cmp: true, + notify_upgrade_updates: true, + notify_promotions: true, + notify_library_updates: true, + reload_banner_status: true, + trackers_banner_status: true, + show_badge: true, + }; + + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('Notifications functions correctly', () => { + const settingsData = { + show_cmp: false, + notify_upgrade_updates: false, + notify_promotions: false, + notify_library_updates: false, + reload_banner_status: false, + trackers_banner_status: false, + show_badge: false, + }; + const toggleCheckbox = jest.fn(); + + const component = mount( + + ); + + expect(toggleCheckbox.mock.calls.length).toBe(0); + component.find('#settings-announcements').simulate('click'); + component.find('#settings-new-features').simulate('click'); + component.find('#settings-new-promotions').simulate('click'); + component.find('#settings-new-trackers').simulate('click'); + component.find('#settings-show-reload-banner').simulate('click'); + component.find('#settings-show-trackers-banner').simulate('click'); + component.find('#settings-show-count-badge').simulate('click'); + expect(toggleCheckbox.mock.calls.length).toBe(7); + }); + }); +}); diff --git a/app/panel/components/Settings/__tests__/OptIn.jsx b/app/panel/components/Settings/__tests__/OptIn.jsx new file mode 100644 index 000000000..083c43a4e --- /dev/null +++ b/app/panel/components/Settings/__tests__/OptIn.jsx @@ -0,0 +1,77 @@ +/** + * OptIn Settings Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import OptIn from '../OptIn'; + +describe('app/panel/Settings/OptIn.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('OptIn is rendered correctly with falsy props', () => { + const settingsData = { + enable_metrics: false, + enable_human_web: false, + enable_offers: false, + }; + + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('OptIn is rendered correctly with truthy props', () => { + const settingsData = { + enable_metrics: true, + enable_human_web: true, + enable_offers: true, + }; + + const component = renderer.create( + {}} + /> + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('OptIn functions correctly', () => { + const settingsData = { + enable_metrics: true, + enable_human_web: true, + enable_offers: true, + }; + const toggleCheckbox = jest.fn(); + + const component = mount( + + ); + + expect(toggleCheckbox.mock.calls.length).toBe(0); + component.find('#settings-share-usage').simulate('click'); + component.find('#settings-share-human-web').simulate('click'); + component.find('#settings-allow-offers').simulate('click'); + expect(toggleCheckbox.mock.calls.length).toBe(3); + }); + }); +}); diff --git a/app/panel/components/Settings/__tests__/TrustAndRestrict.jsx b/app/panel/components/Settings/__tests__/TrustAndRestrict.jsx index eb70ac5bd..823ae3848 100644 --- a/app/panel/components/Settings/__tests__/TrustAndRestrict.jsx +++ b/app/panel/components/Settings/__tests__/TrustAndRestrict.jsx @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -20,7 +20,11 @@ describe('app/panel/components/Settings/TrustAndRestrict', () => { describe('Snapshot test with react-test-renderer', () => { test('Testing TrustAndRestrict is rendering', () => { const wrapper = renderer.create( - + {} }} + site_whitelist={[]} + site_blacklist={[]} + /> ).toJSON(); expect(wrapper).toMatchSnapshot(); }); diff --git a/app/panel/components/Settings/__tests__/__snapshots__/AdBlocker.jsx.snap b/app/panel/components/Settings/__tests__/__snapshots__/AdBlocker.jsx.snap new file mode 100644 index 000000000..72a961de8 --- /dev/null +++ b/app/panel/components/Settings/__tests__/__snapshots__/AdBlocker.jsx.snap @@ -0,0 +1,280 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/Settings/AdBlocker.jsx Snapshot tests with react-test-renderer AdBlocker is rendered correctly with AdOnly checked 1`] = ` +
    +
    +
    +

    + settings_adblocker +

    +
    + settings_adblocker_lists +
    +
    +
    +
    + settings_adblocker_list_1 +
    +
    + settings_adblocker_list_2 +
    +
    + settings_adblocker_list_3 +
    +
    +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`app/panel/Settings/AdBlocker.jsx Snapshot tests with react-test-renderer AdBlocker is rendered correctly with Ads & Trackers checked 1`] = ` +
    +
    +
    +

    + settings_adblocker +

    +
    + settings_adblocker_lists +
    +
    +
    +
    + settings_adblocker_list_1 +
    +
    + settings_adblocker_list_2 +
    +
    + settings_adblocker_list_3 +
    +
    +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`app/panel/Settings/AdBlocker.jsx Snapshot tests with react-test-renderer AdBlocker is rendered correctly with Ads, Trackers, & Annoyances checked 1`] = ` +
    +
    +
    +

    + settings_adblocker +

    +
    + settings_adblocker_lists +
    +
    +
    +
    + settings_adblocker_list_1 +
    +
    + settings_adblocker_list_2 +
    +
    + settings_adblocker_list_3 +
    +
    +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +`; diff --git a/app/panel/components/Settings/__tests__/__snapshots__/GeneralSettings.jsx.snap b/app/panel/components/Settings/__tests__/__snapshots__/GeneralSettings.jsx.snap new file mode 100644 index 000000000..22e43b4f7 --- /dev/null +++ b/app/panel/components/Settings/__tests__/__snapshots__/GeneralSettings.jsx.snap @@ -0,0 +1,462 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/Settings/GeneralSettings.jsx Snapshot tests with react-test-renderer GeneralSettings is rendered correctly with falsy props 1`] = ` +
    +
    +
    +

    + settings_trackers +

    +
    +
    + + +
    + + settings_last_update + + + + + + + + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +

    + settings_highlight_trackers +

    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +

    + settings_blocking +

    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +`; + +exports[`app/panel/Settings/GeneralSettings.jsx Snapshot tests with react-test-renderer GeneralSettings is rendered correctly with truthy props 1`] = ` +
    +
    +
    +

    + settings_trackers +

    +
    +
    + + +
    + + settings_last_update + + + + 1000000 + + + + database-updated-text + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +

    + settings_highlight_trackers +

    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +

    + settings_blocking +

    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +`; diff --git a/app/panel/components/Settings/__tests__/__snapshots__/ImportExport.jsx.snap b/app/panel/components/Settings/__tests__/__snapshots__/ImportExport.jsx.snap new file mode 100644 index 000000000..fae790743 --- /dev/null +++ b/app/panel/components/Settings/__tests__/__snapshots__/ImportExport.jsx.snap @@ -0,0 +1,184 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/Settings/ImportExport.jsx Snapshot tests with react-test-renderer ImportExport is rendered correctly with baseline props 1`] = ` +
    +

    + settings_export_header +

    +

    + settings_export_text +

    +

    + +

    +
    +

    + settings_import_header +

    +

    + settings_import_text +

    +

    + settings_import_warning +

    +

    + +

    + +
    +`; + +exports[`app/panel/Settings/ImportExport.jsx Snapshot tests with react-test-renderer ImportExport is rendered correctly with happy-path props 1`] = ` +
    +

    + settings_export_header +

    +

    + settings_export_text +

    +

    + Your settings have been successfully exported to your downloads folder on August 8, 2020 6:08 PM +

    +
    +

    + settings_import_header +

    +

    + settings_import_text +

    +

    + settings_import_warning +

    +

    + Your settings have been successfully imported on September 12, 2020 6:08 PM +

    + +
    +`; + +exports[`app/panel/Settings/ImportExport.jsx Snapshot tests with react-test-renderer ImportExport is rendered correctly with unhappy-path props 1`] = ` +
    +

    + settings_export_header +

    +

    + settings_export_text +

    +

    + Ghostery cannot export settings when the current page is one of the reserved browser pages. Please navigate to a different page and try again. +

    +
    +

    + settings_import_header +

    +

    + settings_import_text +

    +

    + settings_import_warning +

    +

    + That is the incorrect file type. Please choose a .ghost file and try again. +

    + +
    +`; diff --git a/app/panel/components/Settings/__tests__/__snapshots__/Notifications.jsx.snap b/app/panel/components/Settings/__tests__/__snapshots__/Notifications.jsx.snap new file mode 100644 index 000000000..8ffa3a822 --- /dev/null +++ b/app/panel/components/Settings/__tests__/__snapshots__/Notifications.jsx.snap @@ -0,0 +1,355 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/Settings/Notifications.jsx Snapshot tests with react-test-renderer Notifications is rendered correctly with falsy props 1`] = ` +
    +
    +
    +

    + settings_notifications +

    +
    + settings_notify_me +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +`; + +exports[`app/panel/Settings/Notifications.jsx Snapshot tests with react-test-renderer Notifications is rendered correctly with truthy props 1`] = ` +
    +
    +
    +

    + settings_notifications +

    +
    + settings_notify_me +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +`; diff --git a/app/panel/components/Settings/__tests__/__snapshots__/OptIn.jsx.snap b/app/panel/components/Settings/__tests__/__snapshots__/OptIn.jsx.snap new file mode 100644 index 000000000..3953f909d --- /dev/null +++ b/app/panel/components/Settings/__tests__/__snapshots__/OptIn.jsx.snap @@ -0,0 +1,235 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/Settings/OptIn.jsx Snapshot tests with react-test-renderer OptIn is rendered correctly with falsy props 1`] = ` +
    +
    +
    +

    + settings_support_ghostery +

    +
    + settings_support_ghostery_by + : +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    +
    +`; + +exports[`app/panel/Settings/OptIn.jsx Snapshot tests with react-test-renderer OptIn is rendered correctly with truthy props 1`] = ` +
    +
    +
    +

    + settings_support_ghostery +

    +
    + settings_support_ghostery_by + : +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    +
    +`; diff --git a/app/panel/components/Settings/__tests__/__snapshots__/TrustAndRestrict.jsx.snap b/app/panel/components/Settings/__tests__/__snapshots__/TrustAndRestrict.jsx.snap index 0403041cf..96eddffb5 100644 --- a/app/panel/components/Settings/__tests__/__snapshots__/TrustAndRestrict.jsx.snap +++ b/app/panel/components/Settings/__tests__/__snapshots__/TrustAndRestrict.jsx.snap @@ -16,25 +16,29 @@ exports[`app/panel/components/Settings/TrustAndRestrict Snapshot test with react
    - - settings_trusted_sites - -
    -
    - - settings_restricted_sites - +
    + + settings_trusted_sites + +
    +
    + + settings_restricted_sites + +
    { + describe('Snapshot tests with react-test-renderer', () => { + test('About panel is rendered correctly', () => { + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/panel/components/__tests__/Help.jsx b/app/panel/components/__tests__/Help.jsx new file mode 100644 index 000000000..bfd93a890 --- /dev/null +++ b/app/panel/components/__tests__/Help.jsx @@ -0,0 +1,32 @@ +/** + * Help Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2019 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import Help from '../Help'; + +jest.mock('../../utils/msg', () => ({ + openSupportPage: () => {}, + openHubPage: () => {}, +})); + +describe('app/panel/components/Help.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('Help panel is rendered correctly', () => { + const component = renderer.create( + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/panel/components/__tests__/__snapshots__/About.jsx.snap b/app/panel/components/__tests__/__snapshots__/About.jsx.snap new file mode 100644 index 000000000..7d943a683 --- /dev/null +++ b/app/panel/components/__tests__/__snapshots__/About.jsx.snap @@ -0,0 +1,74 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/About.jsx Snapshot tests with react-test-renderer About panel is rendered correctly 1`] = ` + +`; diff --git a/app/panel/components/__tests__/__snapshots__/Help.jsx.snap b/app/panel/components/__tests__/__snapshots__/Help.jsx.snap new file mode 100644 index 000000000..6043581f6 --- /dev/null +++ b/app/panel/components/__tests__/__snapshots__/Help.jsx.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/Help.jsx Snapshot tests with react-test-renderer Help panel is rendered correctly 1`] = ` +
    +
    +
    +

    + panel_help_panel_header +

    + +
    +

    + panel_help_questions_header +

    + + panel_help_faq + + + panel_help_feedback + + + support + +
    +
    +

    + panel_help_contact_header +

    + + info@ghostery.com + +
    +
    +
    +
    +`; diff --git a/app/panel/utils/msg.js b/app/panel/utils/msg.js index c966af4f1..8582ab152 100644 --- a/app/panel/utils/msg.js +++ b/app/panel/utils/msg.js @@ -150,3 +150,13 @@ export function openHubPage(e) { sendMessage('openHubPage'); window.close(); } + +/** + * Send a message to open Account-Web if signed in or the Account Hub page if not + * This should be used for messages that don't require a callback. + * @memberOf PanelUtils + */ +export function openAccountPageAndroid(e) { + e.preventDefault(); + sendMessage('openAccountAndroid'); +} diff --git a/app/scss/android/_blocking_tab.scss b/app/scss/android/_blocking_tab.scss new file mode 100644 index 000000000..d876f5b93 --- /dev/null +++ b/app/scss/android/_blocking_tab.scss @@ -0,0 +1,381 @@ +/** + * Blocking Tabs Component Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +.BlockingHeader { + .BlockingHeader__text { + position: relative; + + h2 { + font-weight: 500; + padding: 12px 0 25px 10px; + font-size: 24px; + } + } +} + +.BlockingCategories { + .BlockingCategory { + &:not(:last-child) { + border-bottom: 1px solid #E0E0E0; + } + } +} + +.BlockingCategory { + color: $tundora; + font-weight: 500; + + + .BlockingCategory--noPointer { + cursor: text; + } + + .BlockingCategory--uppercase { + text-transform: uppercase; + } + + &.BlockingCategory__unknown:before { + content: ""; + position: relative; + top: -1px; + display: block; + width: 100%; + height: 5px; + border-top: 1px solid $ghosty-blue; + border-bottom: 1px solid $ghosty-blue; + } + + .BlockingCategory__details { + min-height: 80px; + padding: 15px 12px; + cursor: pointer; + } + + .BlockingCategory__image { + padding: 1px 12px 0 0; + width: 47px; + height: 43px; + } + + .BlockingCategory__name { + font-size: 18px; + } + + .BlockingCategory__numTrackers { + text-transform: uppercase; + } + + .BlockingCategory__numBlocked { + text-transform: uppercase; + margin-left: 5px; + color: $button-block; + } + + .BlockingCategory__buttons { + padding: 0 5px; + } + + .BlockingCategory__toggle { + height: 20px; + width: 20px; + background-image: url('/app/images/panel-android/down.svg'); + background-repeat: no-repeat; + background-position: center; + @include prefix('transition', 'background-image 300ms ease'); + + &.BlockingCategory--open { + background-image: url('/app/images/panel-android/up.svg'); + } + } + + .BlockingCategory__listHeader { + color: $tundora; + font-size: 12px; + font-weight: 500; + margin: 0 10px; + padding-bottom: 5px; + border-bottom: 1px solid #E0E0E0; + } + + .BlockingCategory__list { + overflow: hidden; + @include prefix('transition', 'height 300ms ease'); + } + + .BlockingCategory__tracker { + cursor: pointer; + padding: 0 10px; + + &:not(:last-child) .BlockingCategory__trackerBottom { + border-bottom: 1px solid #E0E0E0; + } + } +} + +.BlockingTracker { + position: relative; + + .BlockingTracker--noPointer { + cursor: text; + } + + .BlockingTracker__info { + height: 28px; + width: 28px; + background-image: url('/app/images/panel-android/icon-information-blue.svg'); + background-repeat: no-repeat; + background-position: center; + background-size: 18px; + cursor: pointer; + } + + .BlockingTracker__name { + padding: 0 6px; + font-size: 14px; + font-style: italic; + font-weight: 400; + } + + .BlockingSelectButton { + margin-right: 7px; + } + + .BlockingSelectGroup { + position: absolute; + overflow: hidden; + word-break: break-all; + right: 0px; + width: 0px; + margin-right: -10px; + text-align: center; + @include prefix('transition', 'width 300ms ease'); + + &.BlockingSelectGroup--open.BlockingSelectGroup--wide { + width: 180px; + } + + &.BlockingSelectGroup--open:not(.BlockingSelectGroup--wide) { + width: 60px; + } + + &.BlockingSelectGroup--disabled { + .BlockingSelect__block, + .BlockingSelect__restrict, + .BlockingSelect__trust, + .BlockingSelect__anonymize { + background-color: #C6C6C6; + } + } + + .BlockingSelect { + flex-basis: 0; + padding-top: 30px; + color: $white; + font-size: 11px; + background-repeat: no-repeat; + background-position: center 10px; + } + + .BlockingSelect__block { + background-color: $button-block; + background-image: selectBlocked($white); + + &.BlockingSelect--disabled { + background-color: #C6C6C6; + } + } + + .BlockingSelect__restrict { + background-color: $button-restrict; + background-image: buildIconRestrict($white); + } + + .BlockingSelect__trust { + background-color: $button-trust; + background-image: buildIconTrust($white); + } + + .BlockingSelect__anonymize { + background-color: $ghosty-blue; + background-image: buildIconTrust($white); + } + } + + .OverrideSmartBlock { + position: relative; + cursor: pointer; + height: 25px; + width: 25px; + margin-right: 5px; + background-image: buildIconSmartBlocking($ghosty-blue); + background-size: 40px 40px; + background-position: center; + } + + .OverrideText { + position: absolute; + overflow: hidden; + word-break: keep-all; + right: 0px; + width: 0px; + margin-right: -10px; + color: $white; + background-color: $ghosty-blue; + font-size: 13px; + text-align: center; + @include prefix('transition', 'width 300ms ease'); + + &.OverrideText--open { + width: 250px; + } + + span { + min-width: 250px; + } + } + + .RequestModified { + color: $ghosty-blue; + text-transform: capitalize; + font-size: 11px; + font-style: normal; + padding-right: 5px; + + &:before { + content: ""; + position: relative; + display: inline-block; + top: 2px; + height: 13px; + width: 13px; + margin-right: 2px; + background-size: 21px 21px; + background-position: center; + background-repeat: no-repeat; + + } + &.RequestModified--ad:before { + background-image: buildIconAdBlocking($ghosty-blue); + } + &.RequestModified--cookie:before, + &.RequestModified--fingerprint:before { + background-image: buildIconAntiTracking($ghosty-blue); + } + } +} + +.BlockingSelectButton { + position: relative; + cursor: pointer; + height: 20px; + width: 20px; + border: 1px solid #cccccc; + background-color: #eeeeee; + + &.BlockingSelectButton__mixed::before { + content: " "; + position: absolute; + top: 8px; + left: 2px; + height: 2px; + width: 14px; + background-color: #a4a4a4; + } + + &.BlockingSelectButton__blocked, + &.BlockingSelectButton__trusted, + &.BlockingSelectButton__restricted { + border: none; + background-position: center; + background-repeat: no-repeat; + + &::before { + content: " "; + position: absolute; + top: 1px; + left: 1px; + width: 18px; + height: 18px; + border: 1px solid $white; + } + } + + &.BlockingSelectButton__blocked { + background-color: $button-block; + background-image: selectBlocked($white); + } + + &.BlockingSelectButton__trusted { + background-color: $button-trust; + background-image: buildIconTrust($white); + } + + &.BlockingSelectButton__restricted { + background-color: $button-restrict; + background-image: buildIconRestrict($white); + } +} + +.UnknownSVGContainer { + position: relative; + margin-right: 7px; + display: flex; + align-items: center; + justify-content: space-between; + &:not(.whitelisted) { + .cliqz-tracker-trust { + visibility: hidden; + cursor: pointer; + .border { stroke: #d8d8d8; } + .background { fill: #f7f7f7; } + .trust-circle { stroke: #9B9B9B; } + } + .cliqz-tracker-trust > g > path:nth-child(1) { + stroke: #d8d8d8; + } + .cliqz-tracker-trust > g > path:nth-child(2) { + fill: #f7f7f7; + } + .cliqz-tracker-scrub > g > .border { + fill: #FFF; + stroke: #00AEF0; + } + .cliqz-tracker-scrub > g > .background { + fill: #00AEF0; + stroke: #FFF; + } + .cliqz-tracker-scrub { + pointer-events: none; + .background.protected { + fill: #00AEF0; + } + .background.restricted { + fill: #00AEF0; + } + } + } + &.whitelisted { + flex-direction: row-reverse; + .cliqz-tracker-trust { + pointer-events: none; + } + .cliqz-tracker-scrub { + visibility: hidden; + pointer-events: auto; + cursor: pointer; + .border { stroke: #d8d8d8; } + .background { fill: #f7f7f7; } + .shield { stroke: #9B9B9B; } + } + } +} diff --git a/app/scss/android/_dots_menu.scss b/app/scss/android/_dots_menu.scss new file mode 100644 index 000000000..e51c2e407 --- /dev/null +++ b/app/scss/android/_dots_menu.scss @@ -0,0 +1,45 @@ +/** + * Dots Menu Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +.DotsMenu { + position: absolute; + top: 20px; + right: 5px; + z-index: 2; + text-align: right; + + .DotsMenu__button { + background-image: url(../../app/images/panel-android/kebab-menu-black.svg); + background-size: 4px 16px; + background-position: center; + background-repeat: no-repeat; + height: 16px; + width: 34px; + } + + .DotsMenu__content { + background-color: $white; + border-radius: 2px; + display: none; + @include prefix('box-shadow', '3px 2px 10px rgba(0,0,0,0.25)'); + + &.DotsMenu__open { display: block; } + ul { list-style: none; } + } + + .DotsMenu__item { + padding: 10px 20px; + width: 100%; + text-align: left; + } +} diff --git a/app/scss/android/_landscape.scss b/app/scss/android/_landscape.scss new file mode 100644 index 000000000..10533dfe4 --- /dev/null +++ b/app/scss/android/_landscape.scss @@ -0,0 +1,16 @@ +/** + * Landscape Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +@media screen and (orientation: landscape) { + //ToDo +} diff --git a/app/scss/android/_mixins.scss b/app/scss/android/_mixins.scss new file mode 100644 index 000000000..ee11a347f --- /dev/null +++ b/app/scss/android/_mixins.scss @@ -0,0 +1,180 @@ +/** + * Mixins + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +// Used in Tabs, Dots Menu, and Accordions +@mixin prefix($name, $value) { + @each $vendor in ('-webkit-', '-moz-', '-ms-', '-o-', '') { + #{$vendor}#{$name}: #{$value}; + } +} + +.s-search-box { + position: relative; + width: auto; + padding: 0; + background-color: $whisper; + .s-search-icon { + display: inline-block; + position: absolute; + width: 14.5px; + height: 14.5px; + top: 7px; + left: 7px; + &::before { + content: url('../../app/images/panel/search.svg'); + } + } + input { + margin: 0; + padding: 0; + font-family: $body-font-family; + font-style: normal; + font-weight: normal; + color: #333333; + font-size: 11px; + line-height: 15px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + padding-left: 28px; + height: 29px; + border: 1px solid #E7ECEE; + border-radius: 3px; + } +} + +%pointer { + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + &.disabled { + cursor: not-allowed; + } +} + +@keyframes g-tooltip-animation { + from {opacity: 0;} + to {opacity: 1;} +} + +%g-tooltip-animation { + animation-name: g-tooltip-animation; + animation-duration: 0.2s; +} + +%g-tooltip-body { + @extend %g-tooltip-animation; + pointer-events:none; + font-family: "Open Sans", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; + font-size: 12px; + font-weight: 400; + -webkit-font-smoothing: antialiased; + text-align: center; + white-space: nowrap; + background: #333; + background: rgba(0,0,0,.8); + border-radius: 1px; + color: #fff; + padding: 3px 5px; + position: absolute; + z-index: 10; + font-size: 12px; + -webkit-box-shadow: 0px 2px 2px -1px rgba(#020202, 0.75); + -moz-box-shadow: 0px 2px 2px -1px rgba(#020202, 0.75); + box-shadow: 0px 2px 2px -1px rgba(#020202, 0.75); +} + +%g-tip-body { + @extend %g-tooltip-animation; + border-style: solid; + border-color: #333 transparent; + content: ""; + position: absolute; + z-index: 11; +} + +%g-tooltip { + display: inline; + position: relative; + + &:hover:before { + @extend %g-tooltip-body; + content: attr(data-g-tooltip); + } + &:hover:after { + @extend %g-tip-body; + } +} + +%g-tooltip-up-left{ + @extend %g-tooltip; + &:hover:before { + right: -15px; + bottom: 26px; + } + &:hover:after { + border-width: 6px 6px 0 6px; + left: 20%; + bottom: 20px; + } +} + +%g-tooltip-down-left { + @extend %g-tooltip; + &:hover:before { + @extend %g-tooltip:hover:before; + right: -100%; + top: 20px; + } + &:hover:after { + @extend %g-tooltip:hover:after; + border-width: 0 6px 6px 6px; + left: 20%; + top: 14px; + } +} + +%g-tooltip-down-right { + @extend %g-tooltip; + &:hover:before { + @extend %g-tooltip:hover:before; + left: -100%; + top: 20px; + } + &:hover:after { + @extend %g-tooltip:hover:after; + border-width: 0 6px 6px 6px; + left: 20%; + top: 14px; + } +} + +// Function helper with color variables +@function url-friendly-colour($colour) { + @return '%23' + str-slice('#{$colour}', 2, -1); +} + +// Used in Tabs +@keyframes tab--background-animation { + from { background-color: $ghosty-blue--lighter; } + to { background-color: $ghosty-blue; } +} + +// Panel Android SVGs +// Used in BlockingCategory & BlockingTracker +@function selectBlocked($colour) { + @return url('data:image/svg+xml;charset%3dUS-ASCII,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2050%2050%22%20xmlns%3D%22http://www.w3.org/2000/svg%22%3E%3Cg%20stroke%3D%22#{url-friendly-colour($colour)}%22%20stroke-width%3D%224%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M9%209%2041%2041%22%20/%3E%3Cpath%20d%3D%22M41%209%209%2041%22%20/%3E%3C/g%3E%3C/svg%3E'); +} diff --git a/app/scss/android/content/_normalize.scss b/app/scss/android/_normalize.scss similarity index 79% rename from app/scss/android/content/_normalize.scss rename to app/scss/android/_normalize.scss index 7d4f0e775..6b0095bd6 100644 --- a/app/scss/android/content/_normalize.scss +++ b/app/scss/android/_normalize.scss @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -26,9 +26,9 @@ button { html, body { @include prefix('user-select', 'none'); - font-family: Roboto, "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-family: "Open Sans", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; } -body, .accordion { +body { background-color:rgba(255, 255, 255, 0.7); } diff --git a/app/scss/android/_overview.scss b/app/scss/android/_overview.scss deleted file mode 100644 index f66745248..000000000 --- a/app/scss/android/_overview.scss +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Overview Component Sass - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -.overview { - padding-top: 20px; - padding-bottom: 50px; - text-align: center; - - .buttons-wrapper { - padding-top: 65px; - - .button { - min-width: 180px; - background-color: $white; - border: 1px solid #ccc; - - & > span { - padding-left: 20px; - background-repeat: no-repeat; - background-position: left center; - color: $tundora; - } - - &.trust-site-btn { - - & > span { - background-image: buildIconTrust($button-dark-grey); - } - - &.changed:not(.paused) { - background-color: #9ecc42; - border-color: #9ecc42; - - & > span { - color: $white; - background-image: buildIconTrust($button-white); - } - } - } - - &.restrict-site-btn { - - & > span { - background-image: buildIconRestrict($button-dark-grey); - } - - &.changed:not(.paused) { - background-color: #e74055; - border-color: #e74055; - - & > span { - color: $white; - background-image: buildIconRestrict($button-white); - } - } - } - - &.pause-resume-btn { - - & > span { - background-image: url(../../app/images/panel-android/pause.svg); - - &:last-child { - display: none; - } - } - - &.changed { - background-color: $cliqz-blue; - border-color: $cliqz-blue; - - & > span { - background-image: url(../../app/images/panel-android/play.svg); - - &:first-child { - display: none; - } - - &:last-child { - display: inline-block; - color: $white; - } - } - } - } - } - } - - @media screen and (orientation: landscape) { - .chart-wrapper { - display: none; - } - } -} diff --git a/app/scss/android/_overview_tab.scss b/app/scss/android/_overview_tab.scss new file mode 100644 index 000000000..16ccb95f5 --- /dev/null +++ b/app/scss/android/_overview_tab.scss @@ -0,0 +1,136 @@ +/** + * Overview Tab Component Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +.OverviewTab { + padding-bottom: 35px; + text-align: center; + + .OverviewTab__NavigationLinks { + position: absolute; + + .OverviewTab__NavigationLink { + cursor: pointer; + margin: 0 10px; + } + } + + .OverviewTab__NotScannedContainer { + margin-top: 26px; + } + + .OverviewTab__DonutGraphContainer { + margin: 26px auto; + height: 120px; + width: 120px; + + .DonutGraph__tooltipContainer, + .DonutGraph__textCount span { + display: none; + } + } + + .OverviewTab__PageHostContainer { + margin-top: 20px; + margin-bottom: 10px; + + .OverviewTab__PageHostText { + font-size: 11px; + font-weight: 600; + color: $tundora; + } + } + + .OverviewTab__PageStatsContainer { + margin-bottom: 21px; + + .OverviewTab__PageStat { + line-height: 21px; + font-size: 14px; + font-weight: 600; + } + .OverviewTab__PageStat--red { + color: $red; + } + .OverviewTab__PageStat--blue { + color: $ghosty-blue; + } + } + + .OverviewTab__GhosteryFeaturesContainer { + .GhosteryFeatureButton__text, + .pause-button-text { + font-weight: 600; + } + + .OverviewTab__GhosteryFeature--ExtraMargins { + margin: 12px 28px; + } + + .GhosteryFeatureButton--inactive.clickable { + &:hover { color: $tundora; } + &.trust:hover { background-color: $white; } + &.restrict:hover { background-color: $white; } + } + .GhosteryFeatureButton--active { + &.trust:hover { box-shadow: inset 0px 1px 7px 2px #85b329; } + &.restrict:hover { box-shadow: inset 0px 1px 7px 2px #ce273c; } + } + + .sub-component.pause-button .button { + &.active:hover { + border-color: #0093bd; + background-color: $cliqz-feature--blue; + box-shadow: inset 0px 1px 7px 2px #0093bd; + } + &:not(.active):hover { background-color: $white; } + } + + .tooltip-content { + display: none; + } + } + + .OverviewTab__CliqzFeaturesContainer { + margin: 12px 0; + + .OverviewTab__CliqzFeature { + width: 55px; + display: inline-block; + margin: 0 10px; + } + + .CliqzFeature--active.clickable { + &:hover { + .CliqzFeature__status { color: $cliqz-feature--blue; } + .CliqzFeature__icon--anti-track { background-image: buildIconAntiTracking($cliqz-feature--blue); } + .CliqzFeature__icon--ad-block { background-image: buildIconAdBlocking($cliqz-feature--blue); } + .CliqzFeature__icon--smart-block { background-image: buildIconSmartBlocking($cliqz-feature--blue); } + .CliqzFeature__feature-name { color: $cliqz-feature--blue; } + } + } + + .CliqzFeature--inactive.clickable { + &:hover { + .CliqzFeature__status { color: $cliqz-feature--gray; } + .CliqzFeature__icon--anti-track { background-image: buildIconAntiTracking($cliqz-feature--gray); } + .CliqzFeature__icon--ad-block { background-image: buildIconAdBlocking($cliqz-feature--gray); } + .CliqzFeature__icon--smart-block { background-image: buildIconSmartBlocking($cliqz-feature--gray); } + .CliqzFeature__feature-name { color: $cliqz-feature--gray; } + } + } + + .tooltip-content { + display: none; + } + } +} diff --git a/app/scss/android/_settings.scss b/app/scss/android/_settings.scss new file mode 100644 index 000000000..f13ffe9fb --- /dev/null +++ b/app/scss/android/_settings.scss @@ -0,0 +1,159 @@ +/** + * Settings Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +.Settings#content-settings { + position: relative; + height: 100%; + width: 100%; + + .SettingsHeader { + height: $tab--nav-height; + line-height: $tab--nav-height; + background-color: $ghosty-blue; + text-align: center; + position: relative; + @include prefix('box-shadow', '3px 2px 10px rgba(0,0,0,0.15)'); + + .SettingsHeader__icon { + position: absolute; + left: 0; + height: 14px; + width: 10px; + padding: 8px 10px; + margin: 10px; + line-height: 0; + cursor: pointer; + } + + .SettingsHeader__text { + color: $white; + font-size: 16px; + font-weight: 500; + } + } + + .Settings__link { + margin: 16px 10px; + color: #4A4A4A; + font-size: 16px; + font-weight: 700; + } + + .s-trust-restrict-panel { + left: 0; + width: 100%; + border-left: none; + background-color: #ffffff !important; + + .s-trust-restrict-menu .s-pane-title { + border-left: 1px solid #CCCCCC !important; + + &:last-of-type { + border-left: none !important; + } + + &:not(.s-active-pane) { + background-color: #E5E5E5 !important; + } + } + + .s-site, .s-site:hover { + background-color: $white; + + .s-site-x-container { + @extend %pointer; + background: url('../../app/images/panel/icon-trust-restrict-x.svg') no-repeat center center; + background-size: 12px 12px; + height: 32px; + width: 32px; + } + } + + .s-site-name { + color: #4a4a4a; + font-size: 13px; + line-height: 35px; + } + + .s-site-description span { + font-size: 13px; + } + + .s-sites-input-box input { + height: 35px; + font-size: 13px; + } + + .s-sites-input-icon { + height: 20px; + width: 20px; + + &::before { + height: 20px; + width: 20px; + background-position: center; + background-size: 18px 18px; + background-repeat: no-repeat; + } + } + } + + .s-tabs-panel { + left: 0; + width: 100%; + border-left: none; + + h3 { + font-size: 16px; + padding-top: 10px; + + &.s-special { + max-width: 100%; + } + } + + h5 { + font-size: 14px; + max-width: 100%; + } + + p { + font-size: 14px; + } + + .s-option-group { + margin-right: 0; + + .s-checkbox-label { + margin-left: 24px; + max-width: 100%; + + span { + font-size: 14px; + line-height: 16px; + } + } + + .s-square-checkbox input[type="checkbox"] + label { + margin-left: 24px; + font-size: 14px; + line-height: 16px; + + &::before { + height: 14px; + width: 14px; + } + } + } + } +} diff --git a/app/scss/android/_tabs.scss b/app/scss/android/_tabs.scss new file mode 100644 index 000000000..533f1c3fd --- /dev/null +++ b/app/scss/android/_tabs.scss @@ -0,0 +1,48 @@ +/** + * Tabs Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +.Tabs__component { + .Tabs__navigation { + list-style: none; + flex-wrap: wrap; + padding: 0; + margin: 0; + background-color: $ghosty-blue; + @include prefix('box-shadow', '3px 2px 10px rgba(0,0,0,0.15)'); + } + .Tab__navigation_item { + flex-grow: 1; + flex-basis: 0; + text-align: center; + min-height: $tab--nav-height; + cursor: pointer; + } + .Tab__navigation_item.Tab--active { + border-bottom: 2px solid $white; + @include prefix('animation', 'tab--background-animation 800ms cubic-bezier(0.4, 0, 1, 1) alternate 1'); + } + .Tab__navigation_link { + padding: 0 10px 2px; + color: rgba($white, 0.8); + font-size: 12px; + font-weight: 500; + text-transform: uppercase; + @include prefix('transition', 'color 300ms ease-in'); + } + .Tab__navigation_link.Tab--active { + padding-bottom: 0; + color: $white; + } + + .Tabs__active_content {} +} diff --git a/app/scss/android/_variables.scss b/app/scss/android/_variables.scss new file mode 100644 index 000000000..3663eb73f --- /dev/null +++ b/app/scss/android/_variables.scss @@ -0,0 +1,33 @@ +/** + * Sass Variables + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +// Copied for partials/_settings.scss +$body-font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; +$mercury: #E5E5E5; +$whisper: #F7F8FB; + +// CSS Properties +$tab--nav-height: 50px; + +// Colors +$ghosty-blue: #00AEF0; +$ghosty-blue--lighter: #28C4FF; +$cliqz-feature--blue: #1dafed; +$cliqz-feature--gray: #c8c7c2; +$white: #FFFFFF; +$tundora: #4A4A4A; +$button-trust: #9FCA4C; +$button-restrict: #BC4A4B; +$button-block: #E44258; +$atlantis: #9ECC42; +$red: #E74055; diff --git a/app/scss/android/content/_accordions.scss b/app/scss/android/content/_accordions.scss deleted file mode 100644 index 467ad2703..000000000 --- a/app/scss/android/content/_accordions.scss +++ /dev/null @@ -1,303 +0,0 @@ -/** - * Accordion Sass - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -.accordions { - .accordion { - position: relative; - - .accordionSelect { - position: absolute; - top: 18px; - right: 19px; - border: 1px solid #ccc; - width: 20px; - height: 20px; - background-color: #eee; - background-repeat: no-repeat; - background-position: center; - z-index: 1; - - &.blocked, &.trusted, &.restricted { - border: none; - - &:before { - content: " "; - position: absolute; - top: 1px; - right: 1px; - width: 18px; - height: 18px; - border: 1px solid $white; - } - } - - &.trusted { - background-color: $button-trust; - background-image: buildIconTrust($button-white); - } - - &.restricted { - background-color: $button-restrict; - background-image: buildIconRestrict($button-white); - } - - &.blocked { - background-color: $button-block; - background-image: buildIconBlock($button-white); - } - - &.mixed { - - &:before { - content: " "; - position: absolute; - top: 8px; - right: 1px; - height: 0; - width: 16px; - border: 1px solid #ccc; - } - } - } - - .accordionTitle { - position: relative; - display: block; - cursor: pointer; - background-repeat: no-repeat; - background-size: 35px; - background-position: 12px 16px; - padding: 15px 0 15px 59px; - color: #4A4A4A; - font-weight: 500; - - & > h2 { - font-size: 18px; - } - - & > p { - text-transform: uppercase; - - & > span { - - &.blocked-trackers { - margin-left: 5px; - color: $button-block; - } - } - } - - &:after { - content: " "; - position: absolute; - right: 9px; - bottom: 0; - width: 40px; - height: 40px; - background-image: url(../../app/images/panel-android/down.svg); - background-repeat: no-repeat; - background-position: center; - @include prefix('transition', 'background-image 300ms ease-in'); - } - - &.active { - &:after { - background-image: url(../../app/images/panel-android/up.svg); - } - } - } - - .accordionContent { - overflow: hidden; - @include prefix('transition', 'height 300ms ease-in'); - height: var(--trackers-length, 0px); - - & > p { - margin: 0 10px; - height: 32px; - line-height: 32px; - font-weight: 500; - border-bottom: 1px solid #E0E0E0; - - & > span { - &:last-child { - float: right; - padding-right: 4px; - } - } - } - - .trackers-list { - list-style: none; - - .tracker { - position: relative; - margin-left: 4px; - @include prefix('transition', 'margin 300ms ease-in'); - height: $tracker-height; - - .info { - display: inline-block; - width: 40px; - height: 100%; - float: left; - background-image: url(../../app/images/panel-android/icon-information-blue.svg); - background-repeat: no-repeat; - background-position: center; - background-size: 18px; - } - - .trackerName { - font-style: italic; - - & > span:first-child { - line-height: $tracker-height; - font-size: 14px; - } - } - - .trackerSelect { - position: absolute; - top: 15px; - right: 19px; - border: 1px solid #ccc; - width: 20px; - height: 20px; - background-repeat: no-repeat; - background-position: center; - - &:before { - content: " "; - position: absolute; - top: 1px; - right: 1px; - width: 18px; - height: 18px; - } - } - - .menu { - position: absolute; - top: 0; - right: calc(-3 * #{$tracker-menu-item-width}); - width: calc(3 * #{$tracker-menu-item-width}); - @include prefix('transition', 'right 300ms ease-in'); - height: 100%; - z-index: 1; - overflow: hidden; - - .trackerOption { - padding-top: 20px; - height: 100%; - width: $tracker-menu-item-width; - color: $white; - background-repeat: no-repeat; - background-position: center 10px; - font-size: 11px; - - &.trust { - background-color: $button-trust; - background-image: buildIconTrust($button-white); - } - - &.restrict { - background-color: $button-restrict; - background-image: buildIconRestrict($button-white); - } - - &.block { - background-color: $button-block; - background-image: buildIconBlock($button-white); - - &.disabled { - background-color: #C6C6C6; - pointer-events: none; - } - } - } - - &.global-trackers { - right: -$tracker-menu-item-width; - width: $tracker-menu-item-width; - - .trackerOption { - - &.trust { - display: none; - } - - &.restrict { - display: none; - } - } - } - } - - &.show-menu { - margin-left: -10px; - - .menu { - right: 0; - } - } - - &.blocked, &.trusted, &.restricted { - .trackerSelect:before { - border: 1px solid $white; - } - } - - &.blocked { - - .trackerName { - text-decoration: line-through; - color: #C7C7CD; - } - - .trackerSelect { - border: none; - background-image: buildIconBlock($button-white); - background-color: $button-block; - } - } - - &.trusted { - - .trackerSelect { - border: none; - background-image: buildIconTrust($button-white); - background-color: $button-trust; - } - } - - &.restricted { - - .trackerSelect { - border: none; - background-image: buildIconRestrict($button-white); - background-color: $button-restrict; - } - } - } - - & > li:not(:last-child) .tracker { - border-bottom: 1px solid #eee; - } - } - } - - &:not(:last-child) { - border-bottom: 1px solid #E0E0E0; - } - } -} diff --git a/app/scss/android/content/_dots-menu.scss b/app/scss/android/content/_dots-menu.scss deleted file mode 100644 index aed9cb0bf..000000000 --- a/app/scss/android/content/_dots-menu.scss +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Dots Menu Sass - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -.header { - position: relative; - - & > h2 { - font-weight: 500; - padding: 12px 0 25px 10px; - font-size: 24px; - } - - .dots-menu { - position: absolute; - top: 20px; - right: 5px; - z-index: 2; - text-align: right; - - .dots-menu-btn { - background-image: url(../../app/images/panel-android/kebab-menu-black.svg); - background-size: 4px 16px; - background-position: center; - background-repeat: no-repeat; - height: 16px; - width: 34px; - } - - .dots-menu-content { - background-color: $white; - border-radius: 2px; - display: none; - @include prefix('box-shadow', '3px 2px 10px rgba(0,0,0,0.25)'); - - & > ul { - list-style: none; - - & > li { - - .dots-menu-item { - padding: 10px 20px; - width: 100%; - text-align: left; - } - } - } - - &.opening { - display: block; - } - } - } -} diff --git a/app/scss/android/content/_fixed-menu.scss b/app/scss/android/content/_fixed-menu.scss deleted file mode 100644 index 575b97591..000000000 --- a/app/scss/android/content/_fixed-menu.scss +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Fixed Menu Sass - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -.fixed-menu { - position: fixed; - bottom: 0; - right: 0; - width: 100%; - top: calc(100% - #{$fixed-menu-header-height}); - @include prefix('transition', 'top 250ms cubic-bezier(0.55, 0.09, 0.68, 0.53)'); - @include prefix('box-shadow', '0px -3px 4px rgba(0,0,0,0.15)'); - z-index: 1; //TODO: @mai check this - - .menuHeader { - height: $fixed-menu-header-height; - background-color: $white; - text-align: center; - color: $cliqz-blue; - - & > p { - position: relative; - padding-top: 12px; - font-size: 16px; - font-weight: 500; - - &:before { - content: " "; - height: 0; - width: 40px; - left: 0; - right: 0; - margin: 0 auto; - border: 2px solid $cliqz-blue; - border-radius: 5px; - position: absolute; - top: 5px; - } - } - } - - .menuContent { - position: relative; - margin: 0; - list-style: none; - height: calc(100% - #{$fixed-menu-header-height}); - background-color: $white; - - .menuItem { - height: calc(100% / 3); - width: 100%; - display: table; - - .menuItemWrapper { - height: 100%; - display: table-cell; - vertical-align: middle; - - .anti_tracking, .ad_block, .smart_block { - background-repeat: no-repeat; - background-position: 20px center; - color: $cliqz-blue; - } - - .anti_tracking { - background-image: buildAntiTrackIcon($button-blue); - } - - .ad_block { - background-image: buildAdBlockIcon($button-blue); - } - - .smart_block { - background-image: buildSmartBlockIcon($button-blue); - } - - .menuItemOverview { - width: calc(100% - 47px); - color: $cliqz-blue; - float: left; - - .anti_tracking, .ad_block, .smart_block { - font-size: 23px; - font-weight: 500; - padding-left: 60px; - padding-right: 10px; - background-size: contain; - } - - .smart_block { - background-position-x: 27px; - background-size: 17px; - } - - .title { - padding-right: 30px; - background-image: url(../../app/images/panel-android/icon-information-grey.svg); - background-repeat: no-repeat; - background-position: right center; - background-size: contain; - font-size: 15px; - font-weight: 500; - } - - .description { - display: none; - } - } - - .switcher { - display: inline-block; - margin-top: 14px; - width: 32px; - height: 16px; - border-radius: 8px; - background-color: #82D6F5; - position: relative; - @include prefix('transition', 'background-color 200ms ease-in'); - - &:after { - content: ' '; - position: absolute; - top: -2px; - left: 50%; - width: 20px; - height: 20px; - border-radius: 50%; - background-color: $cliqz-blue; - @include prefix('transition', 'left 200ms ease-in'); - } - - &:not(.active) { - background-color: #bbb; - - &:after { - background-color: #aaa; - left: 0; - } - } - } - - .menuItemContent { - position: absolute; - top: 0; - left: 100%; - bottom: 0; - width: 100%; - @include prefix('transition', 'left 300ms ease-in'); - z-index: 1; - text-align: center; - background-color: $white; - padding-top: 5px; - - .anti_tracking, .ad_block, .smart_block { - font-size: 40px; - padding-left: 37px; - background-size: 30px; - background-position: 5px center; - } - - .smart_block { - background-position-x: 12px; - background-size: 23px; - } - - .headline { - font-weight: bold; - color: $cliqz-blue; - font-size: 16px; - } - - .description { - padding: 10px 20px 0; - text-align: left; - } - - .close { - position: absolute; - top: 0; - left: 0; - width: 40px; - height: 40px; - background-repeat: no-repeat; - background-image: url(../../app/images/panel-android/back.svg); - background-position: center; - } - - &.opening { - left: 0; - } - } - } - - &:not(:last-child) { - border-bottom: 1px solid #eee; - } - } - } - - &.opened { - top: 55%; - } -} diff --git a/app/scss/android/content/_landscape.scss b/app/scss/android/content/_landscape.scss deleted file mode 100644 index 9cd12f2c7..000000000 --- a/app/scss/android/content/_landscape.scss +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Landscape Sass - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -@media screen and (orientation: landscape) { - .tabs-wrapper { - width: 50%; - float: right; - - ul.tabs-nav li.tab-item a.tab-link.custom-link { - font-size: 1.5vw; - } - - .buttons-wrapper { - padding-top: 10px; - } - } - - .fixed-menu { - width: 50%; - - .menuItemOverview { - pointer-events: none; - background-color: $white; - - .title { - background: none; - } - - .description { - display: block; - margin-right: -45px; - padding-left: 20px; - color: #4A4A4A; - } - } - - &.opened { - top: $tab-header-height; - overflow-y: auto; - overflow-x: hidden; - } - } - - & > div > .chart-wrapper { - display: block; - border-right: 1px solid #eee; - padding-top: 0; - - .trackers-chart { - margin: 5px 0; - } - } -} diff --git a/app/scss/android/content/_mixins.scss b/app/scss/android/content/_mixins.scss deleted file mode 100644 index 5e47a0265..000000000 --- a/app/scss/android/content/_mixins.scss +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Mixins - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -@mixin prefix($name, $value) { - @each $vendor in ('-webkit-', '-moz-', '-ms-', '-o-', '') { - #{$vendor}#{$name}: #{$value}; - } -} - -@keyframes dash { - from { - stroke-dashoffset: var(--stroke-length, 0); - } - to { - stroke-dashoffset: 0; - } -} - -@keyframes tab-animation { - from { - background-color: #28C4FF; - } - to { - background-color: #00AEF0; - } -} - -@function buildIconBlock($stroke-color) { - @return url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Csvg%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2020%2020%22%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%0A%20%20%20%20%3Ctitle%3EBlock%3C%2Ftitle%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C%2Fdesc%3E%0A%20%20%20%20%3Cdefs%3E%3C%2Fdefs%3E%0A%20%20%20%20%3Cg%20id%3D%22Block%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%20%20%20%20%3Cpolygon%20id%3D%22Shape%22%20fill%3D%22%23#{$stroke-color}%22%20points%3D%2217%204.4%2015.6%203%2010%208.6%204.4%203%203%204.4%208.6%2010%203%2015.6%204.4%2017%2010%2011.4%2015.6%2017%2017%2015.6%2011.4%2010%22%3E%3C%2Fpolygon%3E%0A%20%20%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E'); -} - -@function buildIconTrust($stroke-color) { - @return url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Csvg%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2047.1%20%2845422%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Ctitle%3EOval%3C/title%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cg%20id%3D%22Page-1%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%20%20%20%20%3Cg%20id%3D%22Card-view---list-expanded-MAX---HOVER%22%20transform%3D%22translate%28-687.000000%2C%20-498.000000%29%22%20stroke-width%3D%222%22%20stroke%3D%22%23#{$stroke-color}%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Ccircle%20id%3D%22Oval%22%20cx%3D%22693.875%22%20cy%3D%22504.875%22%20r%3D%225.875%22%3E%3C/circle%3E%0A%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%3C/g%3E%0A%3C/svg%3E'); -} - -@function buildIconRestrict($stroke-color) { - @return url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Csvg%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2014%2014%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2047.1%20%2845422%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Ctitle%3Eicon-%20restrict%20site%3C/title%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cg%20id%3D%22Page-1%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%20%20%20%20%3Cg%20id%3D%22Card-view---list-expanded-MAX---HOVER%22%20transform%3D%22translate%28-687.000000%2C%20-536.000000%29%22%20stroke%3D%22%23#{$stroke-color}%22%20stroke-width%3D%222%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cg%20id%3D%22icon--restrict-site%22%20transform%3D%22translate%28688.000000%2C%20537.000000%29%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M1.95833333%2C1.95833333%20L9.79166667%2C9.79166667%22%20id%3D%22Line%22%20stroke-linecap%3D%22square%22%3E%3C/path%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Ccircle%20id%3D%22Oval%22%20cx%3D%225.75260417%22%20cy%3D%225.75260417%22%20r%3D%225.75260417%22%3E%3C/circle%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%3C/g%3E%0A%3C/svg%3E'); -} - -@function buildIconPause($stroke-color) { - @return url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Csvg%20width%3D%2210px%22%20height%3D%2213px%22%20viewBox%3D%220%200%2010%2013%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2047.1%20%2845422%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Ctitle%3EShape%3C/title%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cg%20id%3D%22Page-1%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%20%20%20%20%3Cg%20id%3D%22Card-view---list-expanded-MAX---HOVER%22%20transform%3D%22translate%28-684.000000%2C%20-575.000000%29%22%20fill%3D%22%23#{$stroke-color}%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cg%20id%3D%22Group-2%22%20transform%3D%22translate%28684.000000%2C%20575.000000%29%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cg%20id%3D%22pause%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M0%2C12.173913%20L2.60869565%2C12.173913%20L2.60869565%2C0%20L0%2C0%20L0%2C12.173913%20L0%2C12.173913%20Z%20M6.95652174%2C0%20L6.95652174%2C12.173913%20L9.56521739%2C12.173913%20L9.56521739%2C0%20L6.95652174%2C0%20L6.95652174%2C0%20Z%22%20id%3D%22Shape%22%3E%3C/path%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%3C/g%3E%0A%3C/svg%3E'); -} - -@function buildIconResume($stroke-color) { - @return url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%20version%3D%221.1%22%20x%3D%220px%22%20y%3D%220px%22%20viewBox%3D%220%200%20100%20100%22%20enable-background%3D%22new%200%200%20100%20100%22%20xml%3Aspace%3D%22preserve%22%20fill%3D%22%23fff%22%3E%3Cpath%20d%3D%22M72%2C48.3l-42-24c-1.313-0.875-3%2C0.295-3%2C1.7v48c0%2C1.533%2C1.767%2C2.523%2C3%2C1.7l42-24C73.589%2C50.639%2C73.047%2C48.824%2C72%2C48.3z%20%20%20M31%2C70.6V29.4L67%2C50L31%2C70.6z%22%3E%3C/path%3E%3C/svg%3E%0A'); -} - -// cliqz drawer buttons -@function buildAntiTrackIcon($stroke-color) { - @return url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Csvg%20width%3D%2222px%22%20height%3D%2222px%22%20viewBox%3D%220%200%2022%2022%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2047.1%20%2845422%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Ctitle%3EPage%201%20Copy%202%3C/title%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cg%20id%3D%22Page-1%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%20%20%20%20%3Cg%20id%3D%22Simple-View-Panel---hover---adblocing%22%20transform%3D%22translate%28-1082.000000%2C%20-690.000000%29%22%20stroke%3D%22%23#{$stroke-color}%22%20stroke-width%3D%222%22%20fill%3D%22none%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M1093.2127%2C691.032068%20C1093.08497%2C690.989311%201092.91503%2C690.989311%201092.7873%2C691.032068%20L1083.63836%2C693.457515%20C1083.25545%2C693.542485%201083%2C693.882908%201083%2C694.265816%20C1083.04276%2C700.776614%201086.53196%2C706.81899%201092.53185%2C710.861584%20C1092.65958%2C710.946554%201092.82979%2C710.989311%201093%2C710.989311%20C1093.17021%2C710.989311%201093.34042%2C710.946554%201093.46815%2C710.861584%20C1099.46804%2C706.81899%201102.95724%2C700.776614%201103%2C694.265816%20C1103%2C693.882908%201102.74455%2C693.542485%201102.36164%2C693.457515%20L1093.2127%2C691.032068%20Z%22%20id%3D%22Page-1-Copy-2%22%3E%3C/path%3E%0A%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%3C/g%3E%0A%3C/svg%3E'); -} - -@function buildAdBlockIcon($stroke-color) { - @return url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Csvg%20width%3D%2224px%22%20height%3D%2224px%22%20viewBox%3D%220%200%2024%2024%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2047.1%20%2845422%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Ctitle%3EFill%201%3C/title%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cg%20id%3D%22Page-1%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%20%20%20%20%3Cg%20id%3D%22Simple-View-Panel---hover---adblocing%22%20transform%3D%22translate%28-1166.000000%2C%20-735.000000%29%22%20stroke%3D%22%23#{$stroke-color}%22%20stroke-width%3D%220.5%22%20fill%3D%22%23#{$stroke-color}%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M1171.71317%2C754.301569%20C1172.4562%2C755.045891%201173.19537%2C755.790213%201173.94227%2C756.528096%20C1173.99635%2C756.582182%201174.10195%2C756.610513%201174.18565%2C756.610513%20C1176.73025%2C756.614376%201179.27356%2C756.615664%201181.81816%2C756.609225%20C1181.91088%2C756.609225%201182.02806%2C756.56029%201182.09374%2C756.495903%20C1183.89659%2C754.700773%201185.69429%2C752.901779%201187.4907%2C751.098923%20C1187.55638%2C751.03196%201187.60918%2C750.91735%201187.60918%2C750.824631%20C1187.6169%2C748.272302%201187.61561%2C745.72126%201187.61304%2C743.16893%20C1187.61175%2C743.099391%201187.5963%2C743.007961%201187.55252%2C742.961601%20C1186.80562%2C742.203114%201186.05357%2C741.451065%201185.31054%2C740.704168%20C1180.77122%2C745.242215%201176.24734%2C749.766097%201171.71317%2C754.301569%20M1170.68168%2C753.279092%20C1175.21843%2C748.74362%201179.74488%2C744.215875%201184.27648%2C739.684266%20C1183.54761%2C738.955397%201182.80844%2C738.212362%201182.06154%2C737.474479%20C1182.00617%2C737.420393%201181.90186%2C737.389487%201181.82073%2C737.389487%20C1179.27614%2C737.384336%201176.73154%2C737.384336%201174.18823%2C737.390775%20C1174.09422%2C737.390775%201173.97575%2C737.435846%201173.91007%2C737.501522%20C1172.10207%2C739.301803%201170.29664%2C741.104659%201168.49637%2C742.912667%20C1168.43971%2C742.970616%201168.39077%2C743.068485%201168.39077%2C743.147038%20C1168.38562%2C745.714821%201168.38562%2C748.281316%201168.38948%2C750.847811%20C1168.38948%2C750.91735%201168.41524%2C751.003629%201168.46289%2C751.049988%20C1169.20463%2C751.802037%201169.95281%2C752.548935%201170.68168%2C753.279092%20M1188.99608%2C747.014165%20C1188.99608%2C748.386912%201188.99093%2C749.75837%201188.99995%2C751.129829%20C1189.00252%2C751.4273%201188.90981%2C751.655233%201188.6999%2C751.865137%20C1186.75025%2C753.805783%201184.80574%2C755.75158%201182.86381%2C757.699953%20C1182.66163%2C757.903418%201182.44143%2C758%201182.1504%2C758%20C1179.38559%2C757.993561%201176.6195%2C757.993561%201173.85213%2C758%20C1173.56367%2C758%201173.34346%2C757.905994%201173.13871%2C757.702529%20C1171.19292%2C755.749005%201169.24198%2C753.798057%201167.28717%2C751.850972%20C1167.08885%2C751.653945%201167%2C751.437602%201167.00129%2C751.156872%20C1167.00644%2C748.390775%201167.00644%2C745.624678%201167%2C742.857293%20C1167%2C742.568836%201167.08757%2C742.347343%201167.29232%2C742.14259%20C1169.24713%2C740.195505%201171.19807%2C738.245844%201173.14515%2C736.291033%20C1173.34218%2C736.094006%201173.55594%2C736%201173.83667%2C736%20C1176.61049%2C736.005151%201179.38431%2C736.005151%201182.15812%2C736%20C1182.44014%2C736%201182.6552%2C736.090143%201182.85093%2C736.288457%20C1184.80445%2C738.24842%201186.76184%2C740.203231%201188.72051%2C742.156755%20C1188.91109%2C742.347343%201189.00124%2C742.558534%201188.99995%2C742.830251%20C1188.99222%2C744.224889%201188.99608%2C745.619527%201188.99608%2C747.014165%22%20id%3D%22Fill-1%22%3E%3C/path%3E%0A%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%3C/g%3E%0A%3C/svg%3E'); -} - -@function buildSmartBlockIcon($stroke-color) { - @return url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Csvg%20width%3D%2218px%22%20height%3D%2228px%22%20viewBox%3D%220%200%2018%2028%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2047.1%20%2845422%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Ctitle%3EGroup%2011%3C/title%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cg%20id%3D%22Page-1%22%20stroke%3D%22none%22%20stroke-width%3D%221%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%20%20%20%20%3Cg%20id%3D%22Simple-View-Panel---hover---adblocing%22%20transform%3D%22translate%28-1105.000000%2C%20-711.000000%29%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cg%20id%3D%22Group-11%22%20transform%3D%22translate%281105.000000%2C%20711.000000%29%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cg%20id%3D%22icon--smart-blocking-light-bulb%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M15.9766215%2C9.23905085%20C15.8801207%2C10.9165559%2015.2802076%2C12.3962344%2014.3232416%2C13.7536786%20C13.8922048%2C14.3648501%2013.456343%2C14.9711966%2013.038173%2C15.5936266%20C12.4414766%2C16.4814337%2011.9637978%2C17.4255329%2011.7804463%2C18.4918664%20C11.7498877%2C18.6671761%2011.6389118%2C18.6543094%2011.5166775%2C18.6543094%20C10.6755125%2C18.652701%209.83434744%2C18.652701%208.99157404%2C18.652701%20C8.16005907%2C18.652701%207.32854409%2C18.647876%206.49542077%2C18.6559177%20C6.31367765%2C18.6575261%206.24934381%2C18.606059%206.21235184%2C18.4178825%20C6.01452526%2C17.3869326%205.55453826%2C16.4637419%204.9707086%2C15.60006%20C4.47372963%2C14.8666541%203.95584216%2C14.146115%203.45725484%2C13.4127092%20C2.5501476%2C12.0826069%202.07568548%2C10.6109701%202.01135163%2C9.00101562%20C1.8987674%2C6.18158477%203.4974635%2C3.72242346%206.19626838%2C2.58049768%20C9.65421266%2C1.118511%2013.7426286%2C2.58532272%2015.3622332%2C5.87439065%20C15.8833374%2C6.93589913%2016.044172%2C8.0633498%2015.9766215%2C9.23905085%20L15.9766215%2C9.23905085%20Z%20M6.35227796%2C21.3000388%20L11.6566037%2C21.3000388%20L11.6566037%2C20.6695671%20L6.35227796%2C20.6695671%20L6.35227796%2C21.3000388%20Z%20M11.1113743%2C23.9811519%20L6.89589897%2C23.9811519%20C6.38766158%2C23.9811519%206.2943775%2C23.8733927%206.36031969%2C23.3281634%20L11.6405202%2C23.3281634%20C11.7145041%2C23.865351%2011.6180033%2C23.9811519%2011.1113743%2C23.9811519%20L11.1113743%2C23.9811519%20Z%20M17.3501491%2C5.40475357%20C15.9010292%2C2.2363116%2013.3839675%2C0.476780878%209.96462348%2C0.0698692952%20C7.05190855%2C-0.277533479%204.51876332%2C0.679432497%202.45364683%2C2.74294064%20C0.148886755%2C5.04930906%20-0.40438433%2C7.86713156%200.26468768%2C10.9841065%20C0.615307147%2C12.6133611%201.41304685%2C14.0303141%202.36840448%2C15.3732832%20C2.80587464%2C15.9908881%203.23691142%2C16.6165348%203.63899796%2C17.2566565%20C4.01052593%2C17.8501363%204.2742947%2C18.4982998%204.30002824%2C19.2027554%20C4.33058682%2C20.0165786%204.30806997%2C20.8304018%204.30806997%2C21.6442249%20L4.34023689%2C21.6442249%20C4.34023689%2C22.3197303%204.33862855%2C22.9952357%204.34184524%2C23.6707411%20C4.34827862%2C24.8753924%205.29398618%2C25.8918672%206.49220408%2C25.9706761%20C6.649822%2C25.9819346%206.70289743%2C26.0269683%206.74149774%2C26.1845862%20C6.99722478%2C27.242878%207.92684887%2C27.9746755%208.99157404%2C27.9762838%20C10.0772077%2C27.9778922%2011.0003984%2C27.2541364%2011.2657755%2C26.1781528%20C11.2995508%2C26.0382267%2011.3381511%2C25.9835429%2011.490944%2C25.9722845%20C12.7325872%2C25.8790004%2013.6622113%2C24.8753924%2013.665428%2C23.6353575%20C13.665428%2C22.3776308%2013.6750781%2C21.1199041%2013.6622113%2C19.860569%20C13.6509529%2C18.9196865%2013.869688%2C18.0447462%2014.3682753%2C17.2502232%20C14.7703619%2C16.6101014%2015.1997903%2C15.9828464%2015.6420855%2C15.3700665%20C16.9046372%2C13.6137525%2017.7972694%2C11.7175123%2017.9645374%2C9.52694482%20C18.0739049%2C8.10355845%2017.9468456%2C6.71073066%2017.3501491%2C5.40475357%20L17.3501491%2C5.40475357%20Z%22%20id%3D%22Page-1%22%20fill%3D%22%23#{$stroke-color}%22%3E%3C/path%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M9.09571339%2C7.21443826%20C9.03823634%2C7.19518725%208.96176366%2C7.19518725%208.90428661%2C7.21443826%20L4.78726273%2C8.3064732%20C4.61495411%2C8.34472999%204.5%2C8.49800235%204.5%2C8.67040311%20C4.51924072%2C11.6018291%206.08938152%2C14.3223522%208.7893325%2C16.1424922%20C8.84680955%2C16.180749%208.92340478%2C16.2%209%2C16.2%20C9.07659522%2C16.2%209.15319045%2C16.180749%209.2106675%2C16.1424922%20C11.9106185%2C14.3223522%2013.4807593%2C11.6018291%2013.5%2C8.67040311%20C13.5%2C8.49800235%2013.3850459%2C8.34472999%2013.2127373%2C8.3064732%20L9.09571339%2C7.21443826%20Z%22%20id%3D%22Page-1-Copy-2%22%20stroke%3D%22%23#{$stroke-color}%22%20stroke-width%3D%221.5%22%3E%3C/path%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%20%20%20%20%3C/g%3E%0A%20%20%20%20%3C/g%3E%0A%3C/svg%3E'); -} diff --git a/app/scss/android/content/_tabs.scss b/app/scss/android/content/_tabs.scss deleted file mode 100644 index 9c36fc993..000000000 --- a/app/scss/android/content/_tabs.scss +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Tabs Sass - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ -#ghostery-header .header-tab-group { - margin-top: 20px; - z-index: 1; -} - -.tabs-wrapper { - ul.tabs-nav { - margin: 0px; - padding: 0px; - list-style: none; - display: flex; - flex-wrap: wrap; - background-color: $cliqz-blue; - @include prefix('box-shadow', '3px 2px 10px rgba(0,0,0,0.15)'); - - li.tab-item { - display: inline-block; - flex-grow: 1; - - a.tab-link.custom-link { - display: block; - padding: 0 10px; - text-align: left; - cursor: pointer; - text-transform: uppercase; - color: #fff; - font-size: 12px; - font-weight: 500; - opacity: 0.8; - height: $tab-header-height; - line-height: $tab-header-height; - @include prefix('transition', 'opacity 300ms ease-in'); - - &.active { - @include prefix('animation', 'tab-animation 800ms cubic-bezier(0.4, 0, 1, 1) alternate 1'); - border-bottom: 2px solid #fff; - opacity: 1; - } - } - } - } - - .tabs-active-content { - - } -} diff --git a/app/scss/android/content/_trackers-chart.scss b/app/scss/android/content/_trackers-chart.scss deleted file mode 100644 index 68a943b78..000000000 --- a/app/scss/android/content/_trackers-chart.scss +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Tracker Chart Sass - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -.chart-wrapper { - .trackers-chart { - display: inline-block; - margin-top: 5%; - margin-bottom: 20px; - width: 190px; - max-width: 100%; - position: relative; - - #circle { - & > g { - fill: none; - stroke-width: 25; - @include prefix('transform', 'translate(100px, 100px)'); - - .path { - stroke-dashoffset: 0; - stroke-dasharray: var(--stroke-length, 0); - @include prefix('animation', 'dash calc(var(--stroke-length, 0) / 2 * 1ms) linear alternate 1'); - - &[data-category='advertising'] { - stroke: #cb55cd; - } - - &[data-category='audio_video_player'] { - stroke: #ef671e; - } - - &[data-category='comments'] { - stroke: #43b7c5; - } - - &[data-category='customer_interaction'] { - stroke: #fdc257; - } - - &[data-category='essential'] { - stroke: #fc9734; - } - - &[data-category='pornvertising'] { - stroke: #ecafc2; - } - - &[data-category='site_analytics'] { - stroke: #87d7ef; - } - - &[data-category='social_media'] { - stroke: #388ee8; - } - - &[data-category='default'] { - stroke: #e8e8e8; - } - } - } - } - - .trackers-num { - position: absolute; - top: 48%; - left: 50%; - @include prefix('transform', 'translate(-50%, -50%)'); - text-align: center; - - & > span { - &:first-child { - font-size: 40px; - font-weight: 400; - line-height: 51px; - } - - &:last-child { - font-size: 17px; - line-height: 17px; - display: inline-block; - } - } - } - } - - .trackers-blocked-num { - font-size: 20px; - .number { - color: #e74055; - } - } - - &.paused { - @include prefix('filter', 'grayscale(100%)'); - } -} diff --git a/app/scss/android/content/_variables.scss b/app/scss/android/content/_variables.scss deleted file mode 100644 index 4ff9351b3..000000000 --- a/app/scss/android/content/_variables.scss +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Sass Variables - * - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2019 Ghostery, Inc. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -$cliqz-blue: #00AEF0; -$tracker-menu-item-width: 60px; -$tracker-height: 50px; -$fixed-menu-item-height: 50px; -$fixed-menu-header-height: 50px; -$tab-header-height: 50px; - -$white: #FFFFFF; -$tundora: #4A4A4A; - -$button-white: ffffff; -$button-blue: 1DAFED; -$button-dark-grey: 4A4A4A; - -/* Control buttons */ -$button-trust: #9FCA4C; -$button-restrict: #BC4A4B; -$button-block: #E44258; diff --git a/app/scss/hub.scss b/app/scss/hub.scss index 60394aad7..3cd030117 100644 --- a/app/scss/hub.scss +++ b/app/scss/hub.scss @@ -31,6 +31,7 @@ html, body, #root { .App__mainContent { margin-left: 54px; } + .android-relative {position: relative;} } // Foundation Overrides diff --git a/app/scss/panel.scss b/app/scss/panel.scss index b0cb28133..88bffcdff 100644 --- a/app/scss/panel.scss +++ b/app/scss/panel.scss @@ -7,7 +7,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -32,7 +32,7 @@ html body { // Function helper with color variables @function url-friendly-colour($colour) { - @return '%23' + str-slice('#{$colour}', 2, -1) + @return '%23' + str-slice('#{$colour}', 2, -1); } // Foundation Helpers diff --git a/app/scss/panel_android.scss b/app/scss/panel_android.scss index 316bb1cb8..c302b9665 100644 --- a/app/scss/panel_android.scss +++ b/app/scss/panel_android.scss @@ -4,36 +4,54 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0 */ -@import 'android/content/variables'; -@import 'android/content/mixins'; -@import 'android/content/normalize'; -@import 'android/content/tabs'; -@import 'android/content/accordions'; -@import 'android/content/dots-menu'; -@import 'android/content/trackers-chart'; -@import 'android/content/fixed-menu'; +// Android Specific Styles +@import './android/_variables'; +@import './android/_mixins'; +@import './android/_normalize'; // ToDo: Update -#ghostery-content { - @import 'android/overview'; +// Panel Shared Styles +@import './partials/_svgs'; - & > div > .chart-wrapper { - display: none; - position: fixed; - top: 0; - bottom: 0; - left: 0; - width: 50%; - text-align: center; - padding-top: 5%; - } +// Android Specific Component Styles +@import './android/_settings'; +@import './android/_tabs'; +@import './android/_overview_tab'; +@import './android/_blocking_tab'; +@import './android/_dots_menu'; + +// Panel Shared Component Styles +@import './partials/_settings'; +@import './partials/_help'; +@import './partials/_radio_button'; +@import './partials/_not_scanned'; +@import './partials/_donut_graph'; +@import './partials/_ghostery_feature'; +@import './partials/_pause_button'; +@import './partials/_cliqz_feature'; + +// Import shared helpers +@import 'shared_helper_classes'; - /* Landscape mode */ - @import 'android/content/landscape'; +// NeedsReload Header +.NeedsReload { + height: 0; + color: #4a4a4a; + font-weight: 500; + font-style: italic; + background-color: #ffae00; + overflow: none; + opacity: 0; + @include prefix('transition', 'all 300ms ease'); + + &.NeedsReload--show { + height: 25px; + opacity: 1; + } } diff --git a/app/scss/partials/_header.scss b/app/scss/partials/_header.scss index 47684e16e..413cc98ea 100644 --- a/app/scss/partials/_header.scss +++ b/app/scss/partials/_header.scss @@ -4,21 +4,25 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0 */ #ghostery-header { - .header-tab-group { + .header-tab-group-container { position: absolute; height: 36px; - width: 280px; - left: calc(50% - 140px); + width: 580px; + pointer-events: none; + } + .header-tab-group { + height: 36px; } .header-tab { cursor: pointer; + pointer-events: all; height: 20px; width: 140px; text-align: center; diff --git a/app/scss/partials/_pause_button.scss b/app/scss/partials/_pause_button.scss index f2308d5ca..5f546a924 100644 --- a/app/scss/partials/_pause_button.scss +++ b/app/scss/partials/_pause_button.scss @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -21,6 +21,7 @@ transition: background-image 0.25s ease-out, background-color 0.25s ease-out, border-color 0.25s ease-out, + box-shadow 0.25s ease-out, color 0.25s ease-out; } .button.active { diff --git a/app/scss/partials/_settings.scss b/app/scss/partials/_settings.scss index 96981e5aa..c9452812a 100644 --- a/app/scss/partials/_settings.scss +++ b/app/scss/partials/_settings.scss @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -237,6 +237,8 @@ margin-right: 15px; left: -20px; top: 1px; + height: 12px; + width: 12px; } } &:checked { diff --git a/app/scss/partials/_subscribe.scss b/app/scss/partials/_subscribe.scss index c771b102d..59a440ca5 100644 --- a/app/scss/partials/_subscribe.scss +++ b/app/scss/partials/_subscribe.scss @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -12,9 +12,6 @@ */ .content-subscription { - position: absolute; - height: 479px; - width: 100%; .badge { display: block; margin: 35px auto 35px auto; diff --git a/app/scss/partials/_summary.scss b/app/scss/partials/_summary.scss index 79462279f..b5d87bacf 100644 --- a/app/scss/partials/_summary.scss +++ b/app/scss/partials/_summary.scss @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -60,7 +60,7 @@ .SummaryPageStat.page-load-time-fast .SummaryPageStat__value { color: #9ecc42; } .Summary--simple { - width: 100%; + width: 580px; .Summary__pageHostContainer { margin-bottom: 36px; diff --git a/app/shared-components/ForgotPassword/ForgotPassword.jsx b/app/shared-components/ForgotPassword/ForgotPassword.jsx index 058cef3fb..f13d8c7b1 100644 --- a/app/shared-components/ForgotPassword/ForgotPassword.jsx +++ b/app/shared-components/ForgotPassword/ForgotPassword.jsx @@ -114,51 +114,53 @@ class ForgotPassword extends React.Component { return (
    -
    - {panel && ( -

    - {t('forgot_password_message')} -

    - )} - {hub && ( -

    - {t('forgot_password_message')} -

    - )} -
    - -

    - {t('invalid_email_forgot')} -

    -

    - {t('error_email_forgot')} -

    -
    -
    -
    - {panel && ( - - {t('button_cancel')} - - )} - {hub && ( -
    - {t('button_cancel')} -
    - )} +
    + + {panel && ( +

    + {t('forgot_password_message')} +

    + )} + {hub && ( +

    + {t('forgot_password_message')} +

    + )} +
    + +

    + {t('invalid_email_forgot')} +

    +

    + {t('error_email_forgot')} +

    -
    - +
    +
    + {panel && ( + + {t('button_cancel')} + + )} + {hub && ( +
    + {t('button_cancel')} +
    + )} +
    +
    + +
    -
    - + +
    ); diff --git a/app/shared-components/ForgotPassword/ForgotPassword.scss b/app/shared-components/ForgotPassword/ForgotPassword.scss index 1e272e2d2..0b1d84d89 100644 --- a/app/shared-components/ForgotPassword/ForgotPassword.scss +++ b/app/shared-components/ForgotPassword/ForgotPassword.scss @@ -37,7 +37,8 @@ margin: 40px auto; } .ForgotPasswordForm { - width: 420px; + max-width: 420px; + margin: 0 auto; } #ForgotPasswordMessage { font-size: 1.25rem; @@ -60,6 +61,9 @@ } #send-button { width: 150px; + @include breakpoint(medium down) { + width: 100px; + } } } diff --git a/manifest.json b/manifest.json index 500ff02c3..8a00a9832 100644 --- a/manifest.json +++ b/manifest.json @@ -7,6 +7,8 @@ "version_name": "8.5.2", "default_locale": "en", "description": "__MSG_short_description__", + "debug": true, + "log": true, "icons": { "16": "app/images/icon16.png", "48": "app/images/icon48.png", @@ -14,8 +16,8 @@ }, "browser_action": { "default_icon": { - "19": "app/images/icon19_off.png", - "38": "app/images/icon38_off.png" + "19": "app/images/icon19.png", + "38": "app/images/icon38.png" }, "default_title": "Ghostery" }, diff --git a/package.json b/package.json index d8264c86d..5db26f6ab 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "react-redux": "^7.2.0", "react-router-dom": "^5.2.0", "react-svg": "^11.0.26", + "react-window": "^1.8.5", "redux": "^4.0.5", "redux-object": "^0.5.10", "redux-thunk": "^2.2.0", diff --git a/src/background.js b/src/background.js index 29538812d..f17467dfd 100644 --- a/src/background.js +++ b/src/background.js @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -17,7 +17,7 @@ import { debounce, every, size } from 'underscore'; import moment from 'moment/min/moment-with-locales.min'; import cliqz, { HUMANWEB_MODULE, HPN_MODULE } from './classes/Cliqz'; -// object class +// object classes import Events from './classes/EventHandlers'; import Policy from './classes/Policy'; // static classes @@ -65,6 +65,7 @@ const { } = globals; const IS_EDGE = (BROWSER_INFO.name === 'edge'); const IS_FIREFOX = (BROWSER_INFO.name === 'firefox'); +const IS_ANDROID = (BROWSER_INFO.os === 'android'); const VERSION_CHECK_URL = `${CDN_BASE_URL}/update/version`; const REAL_ESTATE_ID = 'ghostery'; const onBeforeRequest = events.onBeforeRequest.bind(events); @@ -208,7 +209,7 @@ function reloadTab(data) { * @memberOf Background */ function closeAndroidPanelTabs() { - if (BROWSER_INFO.os !== 'android') { return; } + if (!IS_ANDROID) { return; } chrome.tabs.query({ active: true, url: chrome.extension.getURL('app/templates/panel_android.html*') @@ -746,9 +747,7 @@ function onMessageHandler(request, sender, callback) { } // HANDLE UNIVERSAL EVENTS HERE (NO ORIGIN LISTED ABOVE) - // The 'getPanelData' message is never sent by the panel, which uses ports only since 8.3.2 - // The message is still sent by panel-android and by the setup hub as of 8.4.0 - if (name === 'getPanelData') { + if (name === 'getPanelData') { // Used by panel-android and the intro hub if (!message.tabId) { utils.getActiveTab((activeTab) => { const data = panelData.get(message.view, activeTab); @@ -798,9 +797,17 @@ function onMessageHandler(request, sender, callback) { return false; } if (name === 'getCliqzModuleData') { // panel-android only - utils.getActiveTab((activeTab) => { - sendCliqzModuleCounts(activeTab.id, activeTab.pageHost, callback); - }); + if (!message.tabId) { + utils.getActiveTab((activeTab) => { + const pageHost = (activeTab.url && utils.processUrl(activeTab.url).hostname) || ''; + sendCliqzModuleCounts(activeTab.id, pageHost, callback); + }); + } else { + chrome.tabs.get(+message.tabId, (messageTab) => { + const pageHost = (messageTab.url && utils.processUrl(messageTab.url).hostname) || ''; + sendCliqzModuleCounts(messageTab.id, pageHost, callback); + }); + } return true; } if (name === 'getTrackerDescription') { @@ -970,6 +977,17 @@ function onMessageHandler(request, sender, callback) { closeAndroidPanelTabs(); return false; } + if (name === 'getAndroidSettingsForExport') { + const settings = account.buildUserSettings(); + settings.site_blacklist = conf.site_blacklist; + settings.site_whitelist = conf.site_whitelist; + + const hash = common.hashCode(JSON.stringify({ conf: settings })); + const backup = JSON.stringify({ hash, settings: { conf: settings } }); + const msg = { type: 'Ghostery-Backup', content: backup }; + callback(msg); + return true; + } if (name === 'getSettingsForExport') { utils.getActiveTab((activeTab) => { if (activeTab && activeTab.id && activeTab.url.startsWith('http')) { @@ -982,8 +1000,9 @@ function onMessageHandler(request, sender, callback) { try { const hash = common.hashCode(JSON.stringify({ conf: settings })); const backup = JSON.stringify({ hash, settings: { conf: settings } }); + const msg = { type: 'Ghostery-Backup', content: backup }; utils.injectNotifications(activeTab.id, true).then(() => { - sendMessage(activeTab.id, 'exportFile', backup); + sendMessage(activeTab.id, 'exportFile', msg); }); callback(true); } catch (e) { @@ -1031,6 +1050,15 @@ function onMessageHandler(request, sender, callback) { utils.openNewTab({ url: hubUrl, become_active: true }); return false; } + if (name === 'openAccountAndroid') { + if (confData.account) { + utils.openNewTab({ url: `${globals.ACCOUNT_BASE_URL}/`, become_active: true }); + } else { + const hubUrl = chrome.runtime.getURL('./app/templates/hub.html#log-in'); + utils.openNewTab({ url: hubUrl, become_active: true }); + } + return false; + } if (name === 'promoModals.sawPremiumPromo') { promoModals.recordPremiumPromoSighting(); return false; @@ -1359,7 +1387,7 @@ function getDataForGhosteryTab(callback) { * @memberOf Background */ function initializePopup() { - if (BROWSER_INFO.os === 'android') { + if (IS_ANDROID) { chrome.browserAction.setPopup({ popup: 'app/templates/panel_android.html', }); @@ -1628,9 +1656,9 @@ function initializeGhosteryModules() { conf.enable_ad_block = !adblocker.isDisabled; conf.enable_anti_tracking = !antitracking.isDisabled; conf.enable_human_web = !humanweb.isDisabled; - conf.enable_offers = !offers.isDisabled; + conf.enable_offers = !offers.isDisabled && !IS_ANDROID; - if (IS_FIREFOX) { + if (IS_FIREFOX && BROWSER_INFO.name !== 'ghostery_android') { if (globals.JUST_INSTALLED) { conf.enable_human_web = false; conf.enable_offers = false; @@ -1668,8 +1696,8 @@ function initializeGhosteryModules() { setCliqzModuleEnabled(offers, false); } - // Disable purplebox for Firefox Android users - if (BROWSER_INFO.os === 'android' && IS_FIREFOX) { + // Disable purplebox for Android users + if (IS_ANDROID) { conf.show_alert = false; } @@ -1727,12 +1755,12 @@ function initializeGhosteryModules() { ]).then(() => { // run scheduledTasks on init scheduledTasks().then(() => { - // open the Ghostery Hub on install with justInstalled query parameter set to true - // we need to do this after running scheduledTasks for the first time + // Open the Ghostery Hub on install with justInstalled query parameter set to true. + // We need to do this after running scheduledTasks for the first time // because of an A/B test that determines which promo variant is shown in the Hub on install if (globals.JUST_INSTALLED) { - const route = (conf.hub_promo_variant === 'upgrade' || conf.hub_promo_variant === 'not_yet_set') ? '' : '#home'; - const showPremiumPromoModal = conf.hub_promo_variant === 'midnight'; + const route = ((conf.hub_promo_variant === 'upgrade' || conf.hub_promo_variant === 'not_yet_set') && !IS_ANDROID) ? '' : '#home'; + const showPremiumPromoModal = (conf.hub_promo_variant === 'midnight' && !IS_ANDROID); chrome.tabs.create({ url: chrome.runtime.getURL(`./app/templates/hub.html?$justInstalled=true&pm=${showPremiumPromoModal}${route}`), active: true diff --git a/src/classes/Account.js b/src/classes/Account.js index ccac43193..9f2e55a34 100644 --- a/src/classes/Account.js +++ b/src/classes/Account.js @@ -6,7 +6,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/src/classes/BrowserButton.js b/src/classes/BrowserButton.js index e702fa214..96a290055 100644 --- a/src/classes/BrowserButton.js +++ b/src/classes/BrowserButton.js @@ -21,6 +21,8 @@ import { getTab } from '../utils/utils'; import { log } from '../utils/common'; import globals from './Globals'; +const IS_ANDROID = globals.BROWSER_INFO.os === 'android'; + /** * @class for handling Ghostery button. * @memberof BackgroundClasses @@ -38,6 +40,7 @@ class BrowserButton { * @param {number} tabId tab id */ update(tabId) { + if (IS_ANDROID) { return; } // Update this specific tab if (tabId) { // In ES6 classes, we need to bind context to callback function @@ -73,7 +76,6 @@ class BrowserButton { * @param {boolean} alert is it a special case which requires button to change its background color? */ _setIcon(active, tabId, trackerCount, alert) { - if (globals.BROWSER_INFO.os === 'android') { return; } if (tabId <= 0) { return; } const iconAlt = (!active) ? '_off' : ''; diff --git a/src/classes/Cliqz.js b/src/classes/Cliqz.js index 7ce2f20a9..013889dbc 100644 --- a/src/classes/Cliqz.js +++ b/src/classes/Cliqz.js @@ -18,7 +18,8 @@ import globals from './Globals'; const IS_ANDROID = globals.BROWSER_INFO.os === 'android'; export const HUMANWEB_MODULE = IS_ANDROID ? 'human-web-lite' : 'human-web'; export const HPN_MODULE = IS_ANDROID ? 'hpn-lite' : 'hpnv2'; -// override the default prefs based on the platform + +// Override the default prefs based on the platform CLIQZ.config.default_prefs = { ...CLIQZ.config.default_prefs, // the following are enabled by default on non-android platforms diff --git a/src/classes/ConfData.js b/src/classes/ConfData.js index 7c49a60b8..c542d5090 100644 --- a/src/classes/ConfData.js +++ b/src/classes/ConfData.js @@ -21,6 +21,7 @@ import { prefsGet } from '../utils/common'; const { IS_CLIQZ, BROWSER_INFO } = globals; const IS_FIREFOX = (BROWSER_INFO.name === 'firefox'); +const IS_ANDROID = (BROWSER_INFO.os === 'android'); /** * Class for handling user configuration properties synchronously. @@ -111,7 +112,7 @@ class ConfData { _initProperty('enable_click2play_social', true); _initProperty('enable_human_web', !IS_CLIQZ && !IS_FIREFOX); _initProperty('enable_metrics', false); - _initProperty('enable_offers', !IS_CLIQZ && !IS_FIREFOX); + _initProperty('enable_offers', !IS_CLIQZ && !IS_FIREFOX && !IS_ANDROID); _initProperty('enable_smart_block', true); _initProperty('expand_all_trackers', true); _initProperty('hide_alert_trusted', false); @@ -134,7 +135,7 @@ class ConfData { _initProperty('rewards_opted_in', false); // Migrated to Cliqz pref myoffrz.opted_in _initProperty('settings_last_imported', 0); _initProperty('settings_last_exported', 0); - _initProperty('show_alert', BROWSER_INFO.os !== 'android' && !IS_FIREFOX); + _initProperty('show_alert', !IS_ANDROID); _initProperty('show_badge', true); _initProperty('show_cmp', true); _initProperty('show_tracker_urls', true); diff --git a/src/classes/Globals.js b/src/classes/Globals.js index 0d8894f93..9aed8fa14 100644 --- a/src/classes/Globals.js +++ b/src/classes/Globals.js @@ -193,6 +193,29 @@ class Globals { // Set version property this.BROWSER_INFO.version = version; + + // Check for the Ghostery Android browser + this._checkForGhosteryAndroid(); + } + + /** + * Check for Ghostery Android Browser and update BROWSER_INFO. + * Note: This is asynchronous and not available at runtime. + * @private + * @return boolean + */ + _checkForGhosteryAndroid() { + if (typeof chrome.runtime.getBrowserInfo === 'function') { + chrome.runtime.getBrowserInfo().then((info) => { + if (info.name === 'Ghostery') { + this.BROWSER_INFO.displayName = 'Ghostery Android Browser'; + this.BROWSER_INFO.name = 'ghostery_android'; + this.BROWSER_INFO.token = 'ga'; + this.BROWSER_INFO.os = 'android'; + this.BROWSER_INFO.version = info.version; + } + }); + } } } diff --git a/src/classes/PanelData.js b/src/classes/PanelData.js index 884ad2ef1..ef103dca6 100644 --- a/src/classes/PanelData.js +++ b/src/classes/PanelData.js @@ -7,7 +7,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -28,7 +28,12 @@ import account from './Account'; import dispatcher from './Dispatcher'; import promoModals from './PromoModals'; import { getCliqzGhosteryBugs, sendCliqzModuleCounts } from '../utils/cliqzModulesData'; -import { getActiveTab, flushChromeMemoryCache, processUrl } from '../utils/utils'; +import { + getTab, + getActiveTab, + flushChromeMemoryCache, + processUrl +} from '../utils/utils'; import { log } from '../utils/common'; const SYNC_SET = new Set(globals.SYNC_ARRAY); @@ -71,7 +76,7 @@ class PanelData { this._panelPort = port; this._mountedComponents.panel = true; - getActiveTab((tab) => { + function tabCallback(tab) { const { url } = tab; this._activeTab = tab; @@ -86,7 +91,21 @@ class PanelData { account.getUserSettings() .then(userSettings => this._postUserSettings(userSettings)) .catch(() => log('Failed getting remote user settings from PanelData#initPort. User not logged in.')); - }); + } + + function tabErrorCallback(err) { + const { message } = err; + log(`Error: ${message}`); + this._postMessage('panel', { error: message }); + } + + const paramTabId = +(new URL(port.sender.url)).searchParams.get('tabId'); + + if (paramTabId) { + getTab(paramTabId, tabCallback.bind(this), tabErrorCallback.bind(this)); + } else { + getActiveTab(tabCallback.bind(this), tabErrorCallback.bind(this)); + } } /** diff --git a/src/utils/cliqzModulesData.js b/src/utils/cliqzModulesData.js index 4a882a34e..ed0cc52cd 100644 --- a/src/utils/cliqzModulesData.js +++ b/src/utils/cliqzModulesData.js @@ -89,11 +89,11 @@ export function getCliqzData(tabId, tabHostUrl, antiTracking) { if (dataPoints || whitelisted) { const type = antiTracking ? 'antiTracking' : 'adBlock'; const { - name, domains, ads, cookies, fingerprints + name, domains, ads, cookies, fingerprints, wtm } = other; unknownTrackers.push({ - name, domains, ads, cookies, fingerprints, whitelisted, type + name, domains, ads, cookies, fingerprints, whitelisted, type, wtm }); } }); diff --git a/webpack.config.js b/webpack.config.js index 6a379a862..95dac4452 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,7 +4,7 @@ * Ghostery Browser Extension * https://www.ghostery.com/ * - * Copyright 2019 Ghostery, Inc. All rights reserved. + * Copyright 2020 Ghostery, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/yarn.lock b/yarn.lock index 73390ca35..ec768faa0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -375,6 +375,13 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" +"@babel/runtime@^7.0.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99" + integrity sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.5.5": version "7.10.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" @@ -5726,6 +5733,11 @@ mdurl@^1.0.1: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +"memoize-one@>=3.1.1 <6": + version "5.1.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" + integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -7068,6 +7080,14 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.13.1: react-is "^16.8.6" scheduler "^0.19.1" +react-window@^1.8.5: + version "1.8.5" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1" + integrity sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"