change(ui): small ui fixes, new border for assist etc

This commit is contained in:
sylenien 2022-09-21 15:10:13 +02:00 committed by Delirium
parent e8c9e95dca
commit 9e25f8ef69
13 changed files with 91 additions and 84 deletions

View file

@ -146,7 +146,7 @@ const NewAlert = (props: IProps) => {
// @ts-ignore // @ts-ignore
.toJS(); .toJS();
const writeQueryOption = ( const writeQueryOption = (
e: React.ChangeEvent, e: React.ChangeEvent,
@ -196,7 +196,7 @@ const NewAlert = (props: IProps) => {
> >
</div> </div>
</div> </div>
<div className="px-6 pb-3 flex flex-col"> <div className="px-6 pb-3 flex flex-col">
<Section <Section
index="1" index="1"
@ -244,7 +244,7 @@ const NewAlert = (props: IProps) => {
title="Notify Through" title="Notify Through"
description="You'll be noticed in app notifications. Additionally opt in to receive alerts on:" description="You'll be noticed in app notifications. Additionally opt in to receive alerts on:"
content={ content={
<NotifyHooks <NotifyHooks
instance={instance} instance={instance}
onChangeCheck={onChangeCheck} onChangeCheck={onChangeCheck}
slackChannels={slackChannels} slackChannels={slackChannels}
@ -257,14 +257,14 @@ const NewAlert = (props: IProps) => {
</div> </div>
<div className="flex items-center justify-between p-6 border-t"> <div className="flex items-center justify-between p-6 border-t">
<BottomButtons <BottomButtons
loading={loading} loading={loading}
instance={instance} instance={instance}
deleting={deleting} deleting={deleting}
onDelete={onDelete} onDelete={onDelete}
/> />
</div> </div>
</Form> </Form>
<div className="bg-white mt-4 border rounded mb-10"> <div className="bg-white mt-4 border rounded mb-10">

View file

@ -9,7 +9,7 @@ import DashboardListItem from './DashboardListItem';
function DashboardList() { function DashboardList() {
const { dashboardStore } = useStore(); const { dashboardStore } = useStore();
const [shownDashboards, setDashboards] = React.useState([]); const [shownDashboards, setDashboards] = React.useState([]);
const dashboards = dashboardStore.dashboards; const dashboards = dashboardStore.sortedDashboards;
const dashboardsSearch = dashboardStore.dashboardsSearch; const dashboardsSearch = dashboardStore.dashboardsSearch;
React.useEffect(() => { React.useEffect(() => {

View file

@ -74,11 +74,13 @@ function DashboardRouter(props: Props) {
<Alerts siteId={siteId} /> <Alerts siteId={siteId} />
</Route> </Route>
<Route exact strict path={withSiteId(alertCreate(), siteId)}> <Route exact path={withSiteId(alertCreate(), siteId)}>
<CreateAlert siteId={siteId} /> {/* @ts-ignore */}
<CreateAlert siteId={siteId as string} />
</Route> </Route>
<Route exact strict path={withSiteId(alertEdit(), siteId)}> <Route exact path={withSiteId(alertEdit(), siteId)}>
{/* @ts-ignore */}
<CreateAlert siteId={siteId} {...props} /> <CreateAlert siteId={siteId} {...props} />
</Route> </Route>
</Switch> </Switch>

View file

@ -1,18 +1,18 @@
import { useObserver } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { NoContent, Pagination, Icon } from 'UI'; import { NoContent, Pagination, Icon } from 'UI';
import { useStore } from 'App/mstore'; import { useStore } from 'App/mstore';
import { filterList } from 'App/utils'; import { filterList } from 'App/utils';
import MetricListItem from '../MetricListItem'; import MetricListItem from '../MetricListItem';
import { sliceListPerPage } from 'App/utils'; import { sliceListPerPage } from 'App/utils';
import { IWidget } from 'App/mstore/types/widget'; import Widget from 'App/mstore/types/widget';
function MetricsList({ siteId }: { siteId: string }) { function MetricsList({ siteId }: { siteId: string }) {
const { metricStore } = useStore(); const { metricStore } = useStore();
const metrics = useObserver(() => metricStore.metrics); const metrics = metricStore.sortedWidgets;
const metricsSearch = useObserver(() => metricStore.metricsSearch); const metricsSearch = metricStore.metricsSearch;
const filterByDashboard = (item: IWidget, searchRE: RegExp) => { const filterByDashboard = (item: Widget, searchRE: RegExp) => {
const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' '); const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' ');
return searchRE.test(dashboardsStr); return searchRE.test(dashboardsStr);
}; };
@ -26,7 +26,7 @@ function MetricsList({ siteId }: { siteId: string }) {
metricStore.updateKey('sessionsPage', 1); metricStore.updateKey('sessionsPage', 1);
}, []); }, []);
return useObserver(() => ( return (
<NoContent <NoContent
show={lenth === 0} show={lenth === 0}
title={ title={
@ -68,7 +68,7 @@ function MetricsList({ siteId }: { siteId: string }) {
/> />
</div> </div>
</NoContent> </NoContent>
)); );
} }
export default MetricsList; export default observer(MetricsList);

View file

@ -2,12 +2,13 @@ import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { NavLink, withRouter } from 'react-router-dom'; import { NavLink, withRouter } from 'react-router-dom';
import cn from 'classnames'; import cn from 'classnames';
import { import {
sessions, sessions,
metrics, metrics,
assist, assist,
client, client,
dashboard, dashboard,
alerts,
withSiteId, withSiteId,
CLIENT_DEFAULT_TAB, CLIENT_DEFAULT_TAB,
} from 'App/routes'; } from 'App/routes';
@ -28,18 +29,19 @@ import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite'; import { useObserver } from 'mobx-react-lite';
const DASHBOARD_PATH = dashboard(); const DASHBOARD_PATH = dashboard();
const ALERTS_PATH = alerts();
const METRICS_PATH = metrics(); const METRICS_PATH = metrics();
const SESSIONS_PATH = sessions(); const SESSIONS_PATH = sessions();
const ASSIST_PATH = assist(); const ASSIST_PATH = assist();
const CLIENT_PATH = client(CLIENT_DEFAULT_TAB); const CLIENT_PATH = client(CLIENT_DEFAULT_TAB);
const Header = (props) => { const Header = (props) => {
const { const {
sites, location, account, sites, location, account,
onLogoutClick, siteId, onLogoutClick, siteId,
boardingCompletion = 100, showAlerts = false, boardingCompletion = 100, showAlerts = false,
} = props; } = props;
const name = account.get('name').split(" ")[0]; const name = account.get('name').split(" ")[0];
const [hideDiscover, setHideDiscover] = useState(false) const [hideDiscover, setHideDiscover] = useState(false)
const { userStore, notificationStore } = useStore(); const { userStore, notificationStore } = useStore();
@ -52,7 +54,7 @@ const Header = (props) => {
useEffect(() => { useEffect(() => {
if (!account.id || initialDataFetched) return; if (!account.id || initialDataFetched) return;
setTimeout(() => { setTimeout(() => {
Promise.all([ Promise.all([
userStore.fetchLimits(), userStore.fetchLimits(),
@ -101,9 +103,11 @@ const Header = (props) => {
className={ styles.nav } className={ styles.nav }
activeClassName={ styles.active } activeClassName={ styles.active }
isActive={ (_, location) => { isActive={ (_, location) => {
return location.pathname.includes(DASHBOARD_PATH) || location.pathname.includes(METRICS_PATH); return location.pathname.includes(DASHBOARD_PATH)
|| location.pathname.includes(METRICS_PATH)
|| location.pathname.includes(ALERTS_PATH)
}} }}
> >
<span>{ 'Dashboards' }</span> <span>{ 'Dashboards' }</span>
</NavLink> </NavLink>
<div className={ styles.right }> <div className={ styles.right }>
@ -111,18 +115,18 @@ const Header = (props) => {
<div className={ styles.divider } /> <div className={ styles.divider } />
{ (boardingCompletion < 100 && !hideDiscover) && ( { (boardingCompletion < 100 && !hideDiscover) && (
<React.Fragment> <React.Fragment>
<OnboardingExplore onComplete={() => setHideDiscover(true)} /> <OnboardingExplore onComplete={() => setHideDiscover(true)} />
<div className={ styles.divider } /> <div className={ styles.divider } />
</React.Fragment> </React.Fragment>
)} )}
<Notifications /> <Notifications />
<div className={ styles.divider } /> <div className={ styles.divider } />
<Popup content={ `Preferences` } > <Popup content={ `Preferences` } >
<NavLink to={ CLIENT_PATH } className={ styles.headerIcon }><Icon name="cog" size="20" /></NavLink> <NavLink to={ CLIENT_PATH } className={ styles.headerIcon }><Icon name="cog" size="20" /></NavLink>
</Popup> </Popup>
<div className={ styles.divider } /> <div className={ styles.divider } />
<div className={ styles.userDetails }> <div className={ styles.userDetails }>
<div className="flex items-center"> <div className="flex items-center">

View file

@ -1,9 +1,8 @@
import React from 'react'; import React from 'react';
import cn from "classnames"; import cn from "classnames";
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { } from 'Player';
import { import {
NONE, OVERVIEW, NONE,
} from 'Duck/components/player'; } from 'Duck/components/player';
import Player from './Player'; import Player from './Player';
import SubHeader from './Subheader'; import SubHeader from './Subheader';

View file

@ -1,6 +1,7 @@
import { import {
makeAutoObservable, makeAutoObservable,
runInAction, runInAction,
computed,
} from "mobx"; } from "mobx";
import Dashboard from "./types/dashboard"; import Dashboard from "./types/dashboard";
import Widget from "./types/widget"; import Widget from "./types/widget";
@ -64,6 +65,11 @@ export default class DashboardStore {
this.drillDownFilter.updateKey("endTimestamp", timeStamps.endTimestamp); this.drillDownFilter.updateKey("endTimestamp", timeStamps.endTimestamp);
} }
@computed
get sortedDashboards() {
return [...this.dashboards].sort((a, b) => b.createdAt - a.createdAt)
}
toggleAllSelectedWidgets(isSelected: boolean) { toggleAllSelectedWidgets(isSelected: boolean) {
if (isSelected) { if (isSelected) {
const allWidgets = this.widgetCategories.reduce((acc, cat) => { const allWidgets = this.widgetCategories.reduce((acc, cat) => {
@ -89,7 +95,7 @@ export default class DashboardStore {
} }
removeSelectedWidgetByCategory = (category: any) => { removeSelectedWidgetByCategory = (category: any) => {
const categoryWidgetIds = category.widgets.map((w) => w.metricId); const categoryWidgetIds = category.widgets.map((w: Widget) => w.metricId);
this.selectedWidgets = this.selectedWidgets.filter( this.selectedWidgets = this.selectedWidgets.filter(
(widget: any) => !categoryWidgetIds.includes(widget.metricId) (widget: any) => !categoryWidgetIds.includes(widget.metricId)
); );
@ -119,7 +125,8 @@ export default class DashboardStore {
this.selectedWidgets = []; this.selectedWidgets = [];
} }
updateKey(key: any, value: any) { updateKey(key: string, value: any) {
// @ts-ignore
this[key] = value; this[key] = value;
} }
@ -138,7 +145,7 @@ export default class DashboardStore {
.getDashboards() .getDashboards()
.then((list: any) => { .then((list: any) => {
runInAction(() => { runInAction(() => {
this.dashboards = list.map((d) => this.dashboards = list.map((d: Record<string, any>) =>
new Dashboard().fromJson(d) new Dashboard().fromJson(d)
); );
}); });
@ -168,7 +175,7 @@ export default class DashboardStore {
this.fetchingDashboard = value; this.fetchingDashboard = value;
} }
save(dashboard: IDashboard): Promise<any> { save(dashboard: Dashboard): Promise<any> {
this.isSaving = true; this.isSaving = true;
const isCreating = !dashboard.dashboardId; const isCreating = !dashboard.dashboardId;
@ -205,7 +212,7 @@ export default class DashboardStore {
}); });
} }
saveMetric(metric: IWidget, dashboardId: string): Promise<any> { saveMetric(metric: Widget, dashboardId: string): Promise<any> {
const isCreating = !metric.widgetId; const isCreating = !metric.widgetId;
return dashboardService return dashboardService
.saveMetric(metric, dashboardId) .saveMetric(metric, dashboardId)
@ -252,7 +259,7 @@ export default class DashboardStore {
fromJson(json: any) { fromJson(json: any) {
runInAction(() => { runInAction(() => {
this.dashboards = json.dashboards.map((d) => this.dashboards = json.dashboards.map((d: Record<string, any>) =>
new Dashboard().fromJson(d) new Dashboard().fromJson(d)
); );
}); });
@ -367,7 +374,7 @@ export default class DashboardStore {
}); });
} }
addWidgetToDashboard(dashboard: IDashboard, metricIds: any): Promise<any> { addWidgetToDashboard(dashboard: Dashboard, metricIds: any): Promise<any> {
this.isSaving = true; this.isSaving = true;
return dashboardService return dashboardService
.addWidget(dashboard, metricIds) .addWidget(dashboard, metricIds)
@ -399,7 +406,7 @@ export default class DashboardStore {
} }
fetchMetricChartData( fetchMetricChartData(
metric: IWidget, metric: Widget,
data: any, data: any,
isWidget: boolean = false, isWidget: boolean = false,
period: Record<string, any> period: Record<string, any>

View file

@ -1,5 +1,5 @@
import { makeAutoObservable, runInAction, observable, action, reaction, computed } from "mobx" import { makeAutoObservable, computed } from "mobx"
import Widget, { IWidget } from "./types/widget"; import Widget from "./types/widget";
import { metricService, errorService } from "App/services"; import { metricService, errorService } from "App/services";
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import Error from "./types/error"; import Error from "./types/error";
@ -8,8 +8,8 @@ export default class MetricStore {
isLoading: boolean = false isLoading: boolean = false
isSaving: boolean = false isSaving: boolean = false
metrics: IWidget[] = [] metrics: Widget[] = []
instance: IWidget = new Widget() instance = new Widget()
page: number = 1 page: number = 1
pageSize: number = 10 pageSize: number = 10
@ -20,41 +20,21 @@ export default class MetricStore {
sessionsPageSize: number = 10 sessionsPageSize: number = 10
constructor() { constructor() {
makeAutoObservable(this, { makeAutoObservable(this)
isLoading: observable, }
metrics: observable,
instance: observable,
page: observable,
pageSize: observable,
metricsSearch: observable,
sort: observable,
init: action, @computed
updateKey: action, get sortedWidgets() {
merge: action, return [...this.metrics].sort((a, b) => b.lastModified - a.lastModified)
reset: action,
addToList: action,
updateInList: action,
findById: action,
removeById: action,
save: action,
fetchList: action,
fetch: action,
delete: action,
fetchError: action,
paginatedList: computed,
})
} }
// State Actions // State Actions
init(metric?: IWidget|null) { init(metric?: Widget | null) {
this.instance.update(metric || new Widget()) this.instance.update(metric || new Widget())
} }
updateKey(key: string, value: any) { updateKey(key: string, value: any) {
// @ts-ignore
this[key] = value this[key] = value
} }
@ -70,33 +50,36 @@ export default class MetricStore {
} }
} }
addToList(metric: IWidget) { addToList(metric: Widget) {
this.metrics.push(metric) this.metrics.push(metric)
} }
updateInList(metric: IWidget) { updateInList(metric: Widget) {
const index = this.metrics.findIndex((m: IWidget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY]) // @ts-ignore
const index = this.metrics.findIndex((m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY])
if (index >= 0) { if (index >= 0) {
this.metrics[index] = metric this.metrics[index] = metric
} }
} }
findById(id: string) { findById(id: string) {
// @ts-ignore
return this.metrics.find(m => m[Widget.ID_KEY] === id) return this.metrics.find(m => m[Widget.ID_KEY] === id)
} }
removeById(id: string): void { removeById(id: string): void {
// @ts-ignore
this.metrics = this.metrics.filter(m => m[Widget.ID_KEY] !== id) this.metrics = this.metrics.filter(m => m[Widget.ID_KEY] !== id)
} }
get paginatedList(): IWidget[] { get paginatedList(): Widget[] {
const start = (this.page - 1) * this.pageSize const start = (this.page - 1) * this.pageSize
const end = start + this.pageSize const end = start + this.pageSize
return this.metrics.slice(start, end) return this.metrics.slice(start, end)
} }
// API Communication // API Communication
save(metric: IWidget, dashboardId?: string): Promise<any> { save(metric: Widget, dashboardId?: string): Promise<any> {
const wasCreating = !metric.exists() const wasCreating = !metric.exists()
this.isSaving = true this.isSaving = true
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -143,10 +126,12 @@ export default class MetricStore {
}) })
} }
delete(metric: IWidget) { delete(metric: Widget) {
this.isSaving = true this.isSaving = true
// @ts-ignore
return metricService.deleteMetric(metric[Widget.ID_KEY]) return metricService.deleteMetric(metric[Widget.ID_KEY])
.then(() => { .then(() => {
// @ts-ignore
this.removeById(metric[Widget.ID_KEY]) this.removeById(metric[Widget.ID_KEY])
toast.success('Metric deleted successfully') toast.success('Metric deleted successfully')
}).finally(() => { }).finally(() => {

View file

@ -15,7 +15,7 @@ export default class Dashboard {
isValid: boolean = false isValid: boolean = false
currentWidget: Widget = new Widget() currentWidget: Widget = new Widget()
config: any = {} config: any = {}
createdAt: Date = new Date() createdAt: number = new Date().getTime()
constructor() { constructor() {
makeAutoObservable(this) makeAutoObservable(this)

View file

@ -24,7 +24,7 @@ export default class Widget {
sessions: [] = [] sessions: [] = []
isPublic: boolean = true isPublic: boolean = true
owner: string = "" owner: string = ""
lastModified: Date = new Date() lastModified: number = new Date().getTime()
dashboards: any[] = [] dashboards: any[] = []
dashboardIds: any[] = [] dashboardIds: any[] = []
config: any = {} config: any = {}

View file

@ -23,7 +23,7 @@ function getElementsFromInternalPoint(doc: Document, { x, y }: Point): Element[]
} }
if (typeof doc.elementsFromPoint === 'function') { if (typeof doc.elementsFromPoint === 'function') {
return doc.elementsFromPoint(x, y) return doc.elementsFromPoint(x, y)
} }
const el = doc.elementFromPoint(x, y) const el = doc.elementFromPoint(x, y)
return el ? [ el ] : [] return el ? [ el ] : []
@ -53,10 +53,13 @@ function isIframe(el: Element): el is HTMLIFrameElement {
} }
export default abstract class BaseScreen { export default abstract class BaseScreen {
public readonly overlay: HTMLDivElement; public readonly overlay: HTMLDivElement;
private readonly iframe: HTMLIFrameElement;
protected readonly screen: HTMLDivElement; private readonly iframe: HTMLIFrameElement;
protected readonly screen: HTMLDivElement;
protected readonly controlButton: HTMLDivElement;
protected parentElement: HTMLElement | null = null; protected parentElement: HTMLElement | null = null;
constructor() { constructor() {
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
iframe.className = styles.iframe; iframe.className = styles.iframe;
@ -83,7 +86,7 @@ export default abstract class BaseScreen {
this.parentElement = parentElement; this.parentElement = parentElement;
// parentElement.onresize = this.scale; // parentElement.onresize = this.scale;
window.addEventListener('resize', this.scale); window.addEventListener('resize', this.scale);
this.scale(); this.scale();
/* == For the Inspecting Document content == */ /* == For the Inspecting Document content == */
@ -101,6 +104,11 @@ export default abstract class BaseScreen {
}) })
} }
toggleRemoteControlStatus(isEnabled: boolean ) {
const styles = isEnabled ? { border: '2px dashed blue' } : { border: 'unset'}
return Object.assign(this.screen.style, styles)
}
get window(): WindowProxy | null { get window(): WindowProxy | null {
return this.iframe.contentWindow; return this.iframe.contentWindow;
} }
@ -145,7 +153,7 @@ export default abstract class BaseScreen {
} }
getElementFromInternalPoint({ x, y }: Point): Element | null { getElementFromInternalPoint({ x, y }: Point): Element | null {
// elementFromPoint && elementFromPoints require viewpoint-related coordinates, // elementFromPoint && elementFromPoints require viewpoint-related coordinates,
// not document-related // not document-related
return this.document?.elementFromPoint(x, y) || null; return this.document?.elementFromPoint(x, y) || null;
} }

View file

@ -18,5 +18,5 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
z-index: 10; z-index: 10;
} }

View file

@ -295,11 +295,13 @@ export default class AssistManager {
this.md.overlay.addEventListener("mousemove", this.onMouseMove) this.md.overlay.addEventListener("mousemove", this.onMouseMove)
this.md.overlay.addEventListener("click", this.onMouseClick) this.md.overlay.addEventListener("click", this.onMouseClick)
this.md.overlay.addEventListener("wheel", this.onWheel) this.md.overlay.addEventListener("wheel", this.onWheel)
this.md.toggleRemoteControlStatus(true)
update({ remoteControl: RemoteControlStatus.Enabled }) update({ remoteControl: RemoteControlStatus.Enabled })
} else { } else {
this.md.overlay.removeEventListener("mousemove", this.onMouseMove) this.md.overlay.removeEventListener("mousemove", this.onMouseMove)
this.md.overlay.removeEventListener("click", this.onMouseClick) this.md.overlay.removeEventListener("click", this.onMouseClick)
this.md.overlay.removeEventListener("wheel", this.onWheel) this.md.overlay.removeEventListener("wheel", this.onWheel)
this.md.toggleRemoteControlStatus(false)
update({ remoteControl: RemoteControlStatus.Disabled }) update({ remoteControl: RemoteControlStatus.Disabled })
this.toggleAnnotation(false) this.toggleAnnotation(false)
} }