diff options
| author | Haishan <[email protected]> | 2019-04-21 00:05:44 +0800 |
|---|---|---|
| committer | Haishan <[email protected]> | 2019-04-21 21:58:33 +0800 |
| commit | 882b168082ddbcbe7991a71a09944f1a60084fc3 (patch) | |
| tree | d12345be635943537042a929aed8376ae4480324 /src/components | |
| parent | eda2501b1d68c6f82a4a824ffe12caf5be7b33f2 (diff) | |
squash: feat(config): add options to select traffic chart style
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/Config.js | 42 | ||||
| -rw-r--r-- | src/components/Selection.js | 61 | ||||
| -rw-r--r-- | src/components/Selection.module.css | 17 | ||||
| -rw-r--r-- | src/components/TrafficChart.js | 194 | ||||
| -rw-r--r-- | src/components/TrafficChartSample.js | 54 |
5 files changed, 193 insertions, 175 deletions
diff --git a/src/components/Config.js b/src/components/Config.js index 01bc4bd..f937692 100644 --- a/src/components/Config.js +++ b/src/components/Config.js @@ -3,15 +3,24 @@ import PropTypes from 'prop-types'; import { useStoreState, useActions } from 'm/store'; import { getConfigs, fetchConfigs, updateConfigs } from 'd/configs'; -import { clearStorage } from 'd/app'; +import { + clearStorage, + selectChartStyleIndex, + getSelectedChartStyleIndex +} from 'd/app'; import ContentHeader from 'c/ContentHeader'; import Switch from 'c/Switch'; import ToggleSwitch from 'c/ToggleSwitch'; import Input from 'c/Input'; import Button from 'c/Button'; +import Selection from 'c/Selection'; +import TrafficChartSample from 'c/TrafficChartSample'; + import s0 from 'c/Config.module.css'; +const propsList = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]; + const optionsRule = [ { label: 'Global', @@ -51,11 +60,14 @@ const optionsLogLevel = [ ]; const actions = { + selectChartStyleIndex, fetchConfigs, updateConfigs }; -const mapStateToProps = s => ({ configs: getConfigs(s) }); +const mapStateToProps = s => ({ + configs: getConfigs(s) +}); export default function ConfigContainer() { const { fetchConfigs } = useActions(actions); @@ -66,8 +78,13 @@ export default function ConfigContainer() { return <Config configs={configs} />; } +const mapStateToProps2 = s => ({ + selectedChartStyleIndex: getSelectedChartStyleIndex(s) +}); + function Config({ configs }) { - const { updateConfigs } = useActions(actions); + const { updateConfigs, selectChartStyleIndex } = useActions(actions); + const { selectedChartStyleIndex } = useStoreState(mapStateToProps2); // configState to track component internal state // prevConfigs to track external props.configs const [configState, _setConfigState] = useState(configs); @@ -129,6 +146,10 @@ function Config({ configs }) { } } + function handleChartStyleIndexOnChange(idx) { + selectChartStyleIndex(idx); + } + return ( <div> <ContentHeader title="Config" /> @@ -198,8 +219,19 @@ function Config({ configs }) { </div> <div className={s0.section}> - <div className={s0.label}>Actions</div> - <Button label="Log out" onClick={clearStorage} /> + <div> + <div className={s0.label}>Chart Style</div> + <Selection + OptionComponent={TrafficChartSample} + optionPropsList={propsList} + selectedIndex={selectedChartStyleIndex} + onChange={handleChartStyleIndexOnChange} + /> + </div> + <div> + <div className={s0.label}>Action</div> + <Button label="Log out" onClick={clearStorage} /> + </div> </div> </div> ); diff --git a/src/components/Selection.js b/src/components/Selection.js new file mode 100644 index 0000000..12816f9 --- /dev/null +++ b/src/components/Selection.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { func, array, number } from 'prop-types'; +import cx from 'classnames'; + +import s from './Selection.module.css'; + +export default function Selection({ + OptionComponent, + optionPropsList, + selectedIndex, + onChange +}) { + return ( + // TODO a11y + // tabIndex="0" + <div className={s.root}> + {optionPropsList.map((props, idx) => { + const className = cx(s.item, { [s.itemActive]: idx === selectedIndex }); + return ( + <div + key={idx} + className={className} + onClick={ev => { + ev.preventDefault(); + if (idx !== selectedIndex) { + onChange(idx); + } + }} + > + <OptionComponent {...props} /> + </div> + ); + })} + </div> + ); +} + +Selection.propTypes = { + OptionComponent: func, + optionPropsList: array, + selectedIndex: number, + onChange: func +}; + +// for test +export function Option({ title }) { + // eslint-disable-next-line no-undef + if (__DEV__) { + return ( + <div + style={{ + width: 100, + height: 60, + backgroundColor: '#eee' + }} + > + {title} + </div> + ); + } +} diff --git a/src/components/Selection.module.css b/src/components/Selection.module.css new file mode 100644 index 0000000..a562058 --- /dev/null +++ b/src/components/Selection.module.css @@ -0,0 +1,17 @@ +.root { + display: flex; + flex-wrap: wrap; +} + +.item { + flex-grow: 0; + flex-wrap: 0; + margin-right: 10px; + margin-bottom: 10px; + cursor: pointer; + border: 2px solid transparent; +} + +.itemActive { + border-color: #387cec; +} diff --git a/src/components/TrafficChart.js b/src/components/TrafficChart.js index 84773c8..cd9e853 100644 --- a/src/components/TrafficChart.js +++ b/src/components/TrafficChart.js @@ -1,193 +1,47 @@ -import React, { useEffect } from 'react'; -import prettyBytes from 'm/pretty-bytes'; +import React, { useMemo } from 'react'; import { fetchData } from '../api/traffic'; -import { unstable_createResource as createResource } from '@hsjs/react-cache'; +import useLineChart from '../hooks/useLineChart'; import { useStoreState } from 'm/store'; -import { getClashAPIConfig, getTheme } from 'd/app'; - -// const delay = ms => new Promise(r => setTimeout(r, ms)); -const chartJSResource = createResource(() => { - return import(/* webpackChunkName: "chartjs" */ - /* webpackPrefetch: true */ - /* webpackPreload: true */ - 'chart.js/dist/Chart.min.js').then(c => c.default); -}); - -const colorCombo = [ - { - down: { - backgroundColor: 'rgba(176, 209, 132, 0.8)', - borderColor: 'rgb(176, 209, 132)' - }, - up: { - backgroundColor: 'rgba(181, 220, 231, 0.8)', - borderColor: 'rgb(181, 220, 231)' - } - }, - { - up: { - backgroundColor: 'rgb(98, 190, 100)', - borderColor: 'rgb(78,146,79)' - }, - down: { - backgroundColor: 'rgb(160, 230, 66)', - borderColor: 'rgb(110, 156, 44)' - } - }, - { - up: { - backgroundColor: 'rgba(94, 175, 223, 0.3)', - borderColor: 'rgb(94, 175, 223)' - }, - down: { - backgroundColor: 'rgba(139, 227, 195, 0.3)', - borderColor: 'rgb(139, 227, 195)' - } - }, - { - up: { - backgroundColor: 'rgba(242, 174, 62, 0.3)', - borderColor: 'rgb(242, 174, 62)' - }, - down: { - backgroundColor: 'rgba(69, 154, 248, 0.3)', - borderColor: 'rgb(69, 154, 248)' - } - } -]; - -const commonDataSetProps = { - borderWidth: 1, - lineTension: 0, - pointRadius: 0 -}; - -function getColorComboIndexByTheme(theme) { - return theme === 'dark' ? 0 : 2; -} - -function getUploadProps(theme = 'dark') { - const i = getColorComboIndexByTheme(theme); - return { - ...commonDataSetProps, - ...colorCombo[i].up, - label: 'Up' - }; -} - -function getDownloadProps(theme = 'dark') { - const i = getColorComboIndexByTheme(theme); - return { - ...commonDataSetProps, - ...colorCombo[i].down, - label: 'Down' - }; -} - -const options = { - responsive: true, - maintainAspectRatio: true, - title: { - display: false - }, - legend: { - display: true, - position: 'top', - labels: { - fontColor: '#ccc', - boxWidth: 20 - } - }, - tooltips: { - // it's hard to follow the tooltip while the data is streaming - // so disable it for now - enabled: false, - mode: 'index', - intersect: false, - animationDuration: 100 - // callbacks: { - // label(tooltipItem, data) { - // console.log(tooltipItem); - // const { datasetIndex, yLabel } = tooltipItem; - // const l = data.datasets[tooltipItem.datasetIndex].label; - // console.log(yLabel); - // const b = prettyBytes(parseInt(yLabel, 10)); - // return l + b; - // } - // } - }, - hover: { - mode: 'nearest', - intersect: true - }, - scales: { - xAxes: [ - { - display: false, - gridLines: { - display: false - } - } - ], - yAxes: [ - { - display: true, - gridLines: { - display: true, - color: '#555', - borderDash: [3, 6], - drawBorder: false - }, - ticks: { - callback(value) { - return prettyBytes(value) + '/s '; - } - } - } - ] - } -}; +import { getClashAPIConfig, getSelectedChartStyleIndex } from 'd/app'; +import { chartJSResource, commonDataSetProps, chartStyles } from 'm/chart'; const chartWrapperStyle = { // make chartjs chart responsive position: 'relative', - width: '90%' + maxWidth: 1000 }; +const mapStateToProps = s => ({ + selectedChartStyleIndex: getSelectedChartStyleIndex(s) +}); + export default function TrafficChart() { const Chart = chartJSResource.read(); const { hostname, port, secret } = useStoreState(getClashAPIConfig); - const theme = useStoreState(getTheme); - - useEffect(() => { - const ctx = document.getElementById('trafficChart').getContext('2d'); - const traffic = fetchData({ hostname, port, secret }); - const upProps = getUploadProps(theme); - const downProps = getDownloadProps(theme); - const data = { + const { selectedChartStyleIndex } = useStoreState(mapStateToProps); + const traffic = fetchData({ hostname, port, secret }); + const data = useMemo( + () => ({ labels: traffic.labels, datasets: [ { - ...upProps, + ...commonDataSetProps, + ...chartStyles[selectedChartStyleIndex].up, + label: 'Up', data: traffic.up }, { - ...downProps, + ...commonDataSetProps, + ...chartStyles[selectedChartStyleIndex].down, + label: 'Down', data: traffic.down } ] - }; - const c = new Chart(ctx, { - type: 'line', - data, - options - }); - const unsubscribe = traffic.subscribe(() => c.update()); - return () => { - unsubscribe(); - c.destroy(); - }; - }, [hostname, port, secret, theme]); + }), + [traffic, selectedChartStyleIndex] + ); + + useLineChart(Chart, 'trafficChart', data, traffic); return ( <div style={chartWrapperStyle}> diff --git a/src/components/TrafficChartSample.js b/src/components/TrafficChartSample.js new file mode 100644 index 0000000..772f6d2 --- /dev/null +++ b/src/components/TrafficChartSample.js @@ -0,0 +1,54 @@ +import React, { useMemo } from 'react'; +import useLineChart from '../hooks/useLineChart'; +import { chartJSResource, commonDataSetProps, chartStyles } from 'm/chart'; + +const extraChartOptions = { + legend: { + display: false + }, + scales: { + xAxes: [{ display: false }], + yAxes: [{ display: false }] + } +}; + +const data1 = [23e3, 35e3, 46e3, 33e3, 90e3, 68e3, 23e3, 45e3]; +const data2 = [184e3, 183e3, 196e3, 182e3, 190e3, 186e3, 182e3, 189e3]; +const labels = data1; + +export default function TrafficChart({ id }) { + const Chart = chartJSResource.read(); + + const data = useMemo( + () => ({ + labels, + datasets: [ + { + ...commonDataSetProps, + ...chartStyles[id].up, + data: data1 + }, + { + ...commonDataSetProps, + ...chartStyles[id].down, + data: data2 + } + ] + }), + [] + ); + + const eleId = 'chart-' + id; + useLineChart(Chart, eleId, data, null, extraChartOptions); + + return ( + <div + style={{ + width: 150, + padding: 5 + }} + > + <canvas id={eleId} /> + </div> + ); +} |
