+
{ this.renderArrows() }
{ this.renderDropDown() }
diff --git a/assets/js/dashboard/filters.js b/assets/js/dashboard/filters.js
index 4adbe091dd53..f5f2c96a84b3 100644
--- a/assets/js/dashboard/filters.js
+++ b/assets/js/dashboard/filters.js
@@ -1,61 +1,145 @@
import React from 'react';
import { withRouter } from 'react-router-dom'
-import {navigateToQuery, removeQueryParam} from './query'
+import { countFilters, navigateToQuery, removeQueryParam } from './query'
import Datamap from 'datamaps'
+import Transition from "../transition.js";
-function filterText(key, value, query) {
- if (key === "goal") {
- return
Completed goal {value}
- }
- if (key === "props") {
- const [metaKey, metaValue] = Object.entries(value)[0]
- const eventName = query.filters["goal"] ? query.filters["goal"] : 'event'
- return
{eventName}.{metaKey} is {metaValue}
- }
- if (key === "source") {
- return
Source: {value}
- }
- if (key === "utm_medium") {
- return
UTM medium: {value}
- }
- if (key === "utm_source") {
- return
UTM source: {value}
- }
- if (key === "utm_campaign") {
- return
UTM campaign: {value}
- }
- if (key === "referrer") {
- return
Referrer: {value}
- }
- if (key === "screen") {
- return
Screen size: {value}
- }
- if (key === "browser") {
- return
Browser: {value}
+class Filters extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ dropdownOpen: false,
+ wrapped: 1, // 0=unwrapped, 1=waiting to check, 2=wrapped
+ viewport: 1080
+ };
+
+ this.appliedFilters = Object.keys(props.query.filters)
+ .map((key) => [key, props.query.filters[key]])
+ .filter(([key, value]) => !!value);
+
+ this.renderDropDown = this.renderDropDown.bind(this);
+ this.renderDropDownContent = this.renderDropDownContent.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ this.handleResize = this.handleResize.bind(this);
+ this.rewrapFilters = this.rewrapFilters.bind(this);
+ this.renderFilterList = this.renderFilterList.bind(this);
}
- if (key === "browser_version") {
- const browserName = query.filters["browser"] ? query.filters["browser"] : 'Browser'
- return
{browserName}.Version: {value}
+
+ componentDidMount() {
+ document.addEventListener('mousedown', this.handleClick, false);
+ window.addEventListener('resize', this.handleResize, false);
+
+ this.rewrapFilters();
+ this.handleResize();
}
- if (key === "os") {
- return
Operating System: {value}
+
+ componentDidUpdate(prevProps, prevState) {
+ const { query } = this.props;
+ const { viewport, wrapped } = this.state;
+
+ this.appliedFilters = Object.keys(query.filters)
+ .map((key) => [key, query.filters[key]])
+ .filter(([key, value]) => !!value)
+
+ if (JSON.stringify(query) !== JSON.stringify(prevProps.query) || viewport !== prevState.viewport) {
+ this.setState({ wrapped: 1 });
+ }
+
+ if (wrapped === 1 && prevState.wrapped !== 1) {
+ this.rewrapFilters();
+ }
}
- if (key === "os_version") {
- const osName = query.filters["os"] ? query.filters["os"] : 'OS'
- return
{osName}.Version: {value}
+
+ componentWillUnmount() {
+ document.removeEventListener('mousedown', this.handleClick, false);
+ window.removeEventListener('resize', this.handleResize, false);
}
- if (key === "country") {
- const allCountries = Datamap.prototype.worldTopo.objects.world.geometries;
- const selectedCountry = allCountries.find((c) => c.id === value)
- return
Country: {selectedCountry.properties.name}
+
+ handleResize() {
+ this.setState({ viewport: window.innerWidth || 639});
}
- if (key === "page") {
- return
Page: {value}
+
+ handleClick(e) {
+ if (this.dropDownNode && this.dropDownNode.contains(e.target)) return;
+
+ this.setState({ dropdownOpen: false });
+ };
+
+ // Checks if the filter container is wrapping items
+ rewrapFilters() {
+ let currItem, prevItem, items = document.getElementById('filters');
+ const { wrapped } = this.state;
+
+ this.setState({ wrapped: 0 });
+
+ // Don't rewrap if we're already properly wrapped, there are no DOM children, or there is only filter
+ if (wrapped !== 1 || !items || this.appliedFilters.length === 1) {
+ return;
+ };
+
+ // For every filter DOM Node, check if its y value is higher than the previous (this indicates a wrap)
+ [...(items.childNodes)].forEach(item => {
+ currItem = item.getBoundingClientRect();
+ if (prevItem && prevItem.top < currItem.top) {
+ this.setState({ wrapped: 2 });
+ }
+ prevItem = currItem;
+ });
+ };
+
+ filterText(key, value, query) {
+ if (key === "goal") {
+ return
Completed goal {value}
+ }
+ if (key === "props") {
+ const [metaKey, metaValue] = Object.entries(value)[0]
+ const eventName = query.filters["goal"] ? query.filters["goal"] : 'event'
+ return
{eventName}.{metaKey} is {metaValue}
+ }
+ if (key === "source") {
+ return
Source: {value}
+ }
+ if (key === "utm_medium") {
+ return
UTM medium: {value}
+ }
+ if (key === "utm_source") {
+ return
UTM source: {value}
+ }
+ if (key === "utm_campaign") {
+ return
UTM campaign: {value}
+ }
+ if (key === "referrer") {
+ return
Referrer: {value}
+ }
+ if (key === "screen") {
+ return
Screen size: {value}
+ }
+ if (key === "browser") {
+ return
Browser: {value}
+ }
+ if (key === "browser_version") {
+ const browserName = query.filters["browser"] ? query.filters["browser"] : 'Browser'
+ return
{browserName}.Version: {value}
+ }
+ if (key === "os") {
+ return
Operating System: {value}
+ }
+ if (key === "os_version") {
+ const osName = query.filters["os"] ? query.filters["os"] : 'OS'
+ return
{osName}.Version: {value}
+ }
+ if (key === "country") {
+ const allCountries = Datamap.prototype.worldTopo.objects.world.geometries;
+ const selectedCountry = allCountries.find((c) => c.id === value)
+ return
Country: {selectedCountry.properties.name}
+ }
+ if (key === "page") {
+ return
Page: {value}
+ }
}
-}
-function renderFilter(history, [key, value], query) {
- function removeFilter() {
+ removeFilter(key, history, query) {
const newOpts = {
[key]: false
}
@@ -67,27 +151,97 @@ function renderFilter(history, [key, value], query) {
)
}
- return (
-
- {filterText(key, value, query)} ✕
-
- )
-}
+ renderDropdownFilter(history, [key, value], query) {
+ return (
+
+ {this.filterText(key, value, query)}
+ this.removeFilter(key, history, query)}>✕
+
+ )
+ }
+
+ renderListFilter(history, [key, value], query) {
+ return (
+
+ {this.filterText(key, value, query)} this.removeFilter(key, history, query)}>✕
+
+ )
+ }
+
+ clearAllFilters(history, query) {
+ const newOpts = Object.keys(query.filters).reduce((acc, red) => ({ ...acc, [red]: false }), {});
+ navigateToQuery(
+ history,
+ query,
+ newOpts
+ );
+ }
-function Filters({query, history, location}) {
- const appliedFilters = Object.keys(query.filters)
- .map((key) => [key, query.filters[key]])
- .filter(([key, value]) => !!value)
+ renderDropDownContent() {
+ const { viewport } = this.state;
+ const { history, query } = this.props;
- if (appliedFilters.length > 0) {
return (
-
- { appliedFilters.map((filter) => renderFilter(history, filter, query)) }
+
this.dropDownNode = node}>
+
+ {this.appliedFilters.map((filter) => this.renderDropdownFilter(history, filter, query))}
+
this.clearAllFilters(history, query)}>
+ Clear All Filters
+
+
)
}
- return null
+ renderDropDown() {
+ return (
+
+
+
this.setState((state) => ({ dropdownOpen: !state.dropdownOpen }))} className="flex items-center justify-between rounded bg-white dark:bg-gray-800 shadow px-4 pr-3 py-2 leading-tight cursor-pointer text-sm font-medium text-gray-800 dark:text-gray-200 h-full">
+
Filters
+
+
+
+ {this.renderDropDownContent()}
+
+
+
+ );
+ }
+
+ renderFilterList() {
+ const { history, query } = this.props;
+
+ return (
+
+ {(this.appliedFilters.map((filter) => this.renderListFilter(history, filter, query)))}
+
+ );
+ }
+
+ render() {
+ const { wrapped, viewport } = this.state;
+
+ if (this.appliedFilters.length > 0) {
+ if (wrapped === 2 || viewport <= 768) {
+ return this.renderDropDown();
+ }
+
+ return this.renderFilterList();
+ }
+
+ return null;
+ }
}
-export default withRouter(Filters)
+export default withRouter(Filters);
diff --git a/assets/js/dashboard/historical.js b/assets/js/dashboard/historical.js
index 21f9c044daba..5e1c79efe10b 100644
--- a/assets/js/dashboard/historical.js
+++ b/assets/js/dashboard/historical.js
@@ -27,15 +27,15 @@ class Historical extends React.Component {
return (
-
-
-
+
diff --git a/assets/js/dashboard/query.js b/assets/js/dashboard/query.js
index fcd65dafc56b..adbf00292757 100644
--- a/assets/js/dashboard/query.js
+++ b/assets/js/dashboard/query.js
@@ -43,11 +43,17 @@ export function parseQuery(querystring, site) {
}
}
+export function countFilters(query) {
+ let count = 0;
+ for (const filter of Object.values(query.filters)) {
+ if (filter) count++;
+ }
+
+ return count;
+}
+
function generateQueryString(data) {
const query = new URLSearchParams(window.location.search)
- query.delete("date");
- query.delete("from");
- query.delete("to");
Object.keys(data).forEach(key => {
if (!data[key]) {
query.delete(key)
diff --git a/assets/js/dashboard/realtime.js b/assets/js/dashboard/realtime.js
index 33a3303b5389..e5ad08faa487 100644
--- a/assets/js/dashboard/realtime.js
+++ b/assets/js/dashboard/realtime.js
@@ -27,14 +27,14 @@ class Realtime extends React.Component {
return (
-
+
diff --git a/assets/js/dashboard/site-switcher.js b/assets/js/dashboard/site-switcher.js
index 0ac34d3e901d..13d8a9121f38 100644
--- a/assets/js/dashboard/site-switcher.js
+++ b/assets/js/dashboard/site-switcher.js
@@ -45,9 +45,9 @@ export default class SiteSwitcher extends React.Component {
}
renderSiteLink(domain) {
- const extraClass = domain === this.props.site.domain ? 'font-medium text-gray-900 dark:text-gray-100' : 'hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-900 dark:focus:text-gray-100'
+ const extraClass = domain === this.props.site.domain ? 'font-medium text-gray-900 dark:text-gray-100 cursor-default font-bold' : 'hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-900 dark:focus:text-gray-100'
return (
-
+
{domain}
@@ -63,12 +63,12 @@ export default class SiteSwitcher extends React.Component {
return (
-
+
{ this.state.sites.map(this.renderSiteLink.bind(this)) }
@@ -91,7 +91,7 @@ export default class SiteSwitcher extends React.Component {
const hoverClass = this.props.loggedIn ? 'hover:text-gray-500 dark:hover:text-gray-200 focus:border-blue-300 focus:ring ' : 'cursor-default'
return (
-
+