add a hook to track window resize so we can dynamically size charts; default highlight Home link in nav; some typescript fixes
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import { LineChart, XAxis, YAxis, Line, Tooltip, Legend } from "recharts";
|
import { LineChart, XAxis, YAxis, Line, Tooltip, Legend } from "recharts";
|
||||||
import { timeFormat } from "d3-time-format";
|
import { timeFormat } from "d3-time-format";
|
||||||
|
import useWindowSize from '../../utils/hook-windowresize';
|
||||||
|
import styles from '../../styles/styles.module.css';
|
||||||
|
|
||||||
interface ToolTipProps {
|
interface ToolTipProps {
|
||||||
active?: boolean,
|
active?: boolean,
|
||||||
@@ -10,20 +12,18 @@ interface ToolTipProps {
|
|||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
active: false,
|
active: false,
|
||||||
payload: Object,
|
payload: Object,
|
||||||
unit: "",
|
unit: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface TimedValue {
|
interface TimedValue {
|
||||||
time: Date;
|
time: Date;
|
||||||
value: Number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChartProps {
|
interface ChartProps {
|
||||||
// eslint-disable-next-line react/require-default-props
|
|
||||||
data?: TimedValue[],
|
data?: TimedValue[],
|
||||||
color: string,
|
color: string,
|
||||||
unit: string,
|
unit: string,
|
||||||
// eslint-disable-next-line react/require-default-props
|
|
||||||
dataCollections?: any[],
|
dataCollections?: any[],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,61 +47,67 @@ export default function Chart({ data, color, unit, dataCollections }: ChartProps
|
|||||||
if (!data && !dataCollections) {
|
if (!data && !dataCollections) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const windowSize = useWindowSize();
|
||||||
|
const chartWidth = windowSize.width * .68;
|
||||||
|
const chartHeight = chartWidth * .333;
|
||||||
|
|
||||||
const timeFormatter = (tick: string) => {
|
const timeFormatter = (tick: string) => {
|
||||||
return timeFormat("%I:%M")(new Date(tick));
|
return timeFormat("%I:%M")(new Date(tick));
|
||||||
};
|
};
|
||||||
|
|
||||||
let ticks
|
let ticks = [];
|
||||||
if (dataCollections.length > 0) {
|
if (dataCollections.length > 0) {
|
||||||
ticks = dataCollections[0].data?.map(function (collection) {
|
ticks = dataCollections[0].data?.map((collection) => {
|
||||||
return collection?.time;
|
return collection?.time;
|
||||||
})
|
})
|
||||||
} else if (data?.length > 0){
|
} else if (data?.length > 0){
|
||||||
ticks = data?.map(function (item) {
|
ticks = data?.map(item => {
|
||||||
return item?.time;
|
return item?.time;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LineChart width={1200} height={400} data={data}>
|
<div className={styles.lineChartContainer}>
|
||||||
<XAxis
|
<LineChart width={chartWidth} height={chartHeight} data={data}>
|
||||||
dataKey="time"
|
<XAxis
|
||||||
tickFormatter={timeFormatter}
|
dataKey="time"
|
||||||
interval="preserveStartEnd"
|
tickFormatter={timeFormatter}
|
||||||
tickCount={5}
|
interval="preserveStartEnd"
|
||||||
minTickGap={15}
|
tickCount={5}
|
||||||
domain={["dataMin", "dataMax"]}
|
minTickGap={15}
|
||||||
ticks={ticks}
|
domain={["dataMin", "dataMax"]}
|
||||||
/>
|
ticks={ticks}
|
||||||
<YAxis
|
/>
|
||||||
dataKey="value"
|
<YAxis
|
||||||
interval="preserveStartEnd"
|
|
||||||
unit={unit}
|
|
||||||
domain={["dataMin", "dataMax"]}
|
|
||||||
/>
|
|
||||||
<Tooltip content={<CustomizedTooltip unit={unit} />} />
|
|
||||||
<Legend />
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="value"
|
|
||||||
stroke={color}
|
|
||||||
dot={null}
|
|
||||||
strokeWidth={3}
|
|
||||||
/>
|
|
||||||
{dataCollections?.map((s) => (
|
|
||||||
<Line
|
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
data={s.data}
|
interval="preserveStartEnd"
|
||||||
name={s.name}
|
unit={unit}
|
||||||
key={s.name}
|
domain={["dataMin", "dataMax"]}
|
||||||
|
/>
|
||||||
|
<Tooltip content={<CustomizedTooltip unit={unit} />} />
|
||||||
|
<Legend />
|
||||||
|
<Line
|
||||||
type="monotone"
|
type="monotone"
|
||||||
stroke={s.color}
|
dataKey="value"
|
||||||
|
stroke={color}
|
||||||
dot={null}
|
dot={null}
|
||||||
strokeWidth={3}
|
strokeWidth={3}
|
||||||
/>
|
/>
|
||||||
))}
|
{dataCollections?.map((s) => (
|
||||||
</LineChart>
|
<Line
|
||||||
|
dataKey="value"
|
||||||
|
data={s.data}
|
||||||
|
name={s.name}
|
||||||
|
key={s.name}
|
||||||
|
type="monotone"
|
||||||
|
stroke={s.color}
|
||||||
|
dot={null}
|
||||||
|
strokeWidth={3}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</LineChart>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
MinusSquareFilled,
|
MinusSquareFilled,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {parseSecondsToDurationString} from '../../utils/format'
|
import { parseSecondsToDurationString } from '../../utils/format'
|
||||||
|
|
||||||
import OwncastLogo from './logo';
|
import OwncastLogo from './logo';
|
||||||
import { BroadcastStatusContext } from '../../utils/broadcast-status-context';
|
import { BroadcastStatusContext } from '../../utils/broadcast-status-context';
|
||||||
@@ -24,7 +24,7 @@ export default function MainLayout(props) {
|
|||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
const context = useContext(BroadcastStatusContext);
|
const context = useContext(BroadcastStatusContext);
|
||||||
const { broadcastActive } = context || {};
|
const { broadcastActive, broadcaster } = context || {};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { route } = router || {};
|
const { route } = router || {};
|
||||||
@@ -33,7 +33,7 @@ export default function MainLayout(props) {
|
|||||||
const { SubMenu } = Menu;
|
const { SubMenu } = Menu;
|
||||||
|
|
||||||
const streamDurationString = broadcastActive ?
|
const streamDurationString = broadcastActive ?
|
||||||
parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(context.broadcaster.time))) : ""
|
parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : ""
|
||||||
|
|
||||||
const statusIcon = broadcastActive ?
|
const statusIcon = broadcastActive ?
|
||||||
<PlayCircleFilled /> : <MinusSquareFilled />;
|
<PlayCircleFilled /> : <MinusSquareFilled />;
|
||||||
@@ -56,7 +56,7 @@ export default function MainLayout(props) {
|
|||||||
>
|
>
|
||||||
<Menu
|
<Menu
|
||||||
theme="dark"
|
theme="dark"
|
||||||
defaultSelectedKeys={[route.substring(1)]}
|
defaultSelectedKeys={[route.substring(1) || "home"]}
|
||||||
defaultOpenKeys={["current-stream-menu", "utilities-menu"]}
|
defaultOpenKeys={["current-stream-menu", "utilities-menu"]}
|
||||||
mode="inline"
|
mode="inline"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export default function ViewersOverTime() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await fetchData(CONNECTED_CLIENTS);
|
const result = await fetchData(CONNECTED_CLIENTS);
|
||||||
console.log("result", result);
|
|
||||||
setClients(result);
|
setClients(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("==== error", error);
|
console.log("==== error", error);
|
||||||
@@ -71,7 +70,6 @@ export default function ViewersOverTime() {
|
|||||||
return "no info";
|
return "no info";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "User name",
|
title: "User name",
|
||||||
@@ -124,11 +122,8 @@ export default function ViewersOverTime() {
|
|||||||
</Row>
|
</Row>
|
||||||
<div className="chart-container">
|
<div className="chart-container">
|
||||||
<Chart data={viewerInfo} color="#ff84d8" unit="" />
|
<Chart data={viewerInfo} color="#ff84d8" unit="" />
|
||||||
|
|
||||||
<div>
|
|
||||||
<Table dataSource={clients} columns={columns} />;
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Table dataSource={clients} columns={columns} />;
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,3 +23,12 @@ a {
|
|||||||
.owncast-layout .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
.owncast-layout .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||||
background-color: $owncast-purple;
|
background-color: $owncast-purple;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// misc system overrides
|
||||||
|
.ant-card {
|
||||||
|
border-radius: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recharts-wrapper {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
@@ -67,4 +67,7 @@
|
|||||||
color: #52c41a;
|
color: #52c41a;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* //844-227-3943 */
|
|
||||||
|
.lineChartContainer {
|
||||||
|
margin: 2em auto;
|
||||||
|
}
|
||||||
32
web/utils/hook-windowresize.tsx
Normal file
32
web/utils/hook-windowresize.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export default function useWindowSize() {
|
||||||
|
// Initialize state with undefined width/height so server and client renders match
|
||||||
|
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
|
||||||
|
const [windowSize, setWindowSize] = useState({
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Handler to call on window resize
|
||||||
|
function handleResize() {
|
||||||
|
// Set window width/height to state
|
||||||
|
setWindowSize({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
|
// Call handler right away so state gets updated with initial window size
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
// Remove event listener on cleanup
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
}, []); // Empty array ensures that effect is only run on mount
|
||||||
|
|
||||||
|
return windowSize;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user