Merge pull request 'Actually respect prettier configuration' (#82) from ivan/respect-prettier-configuration into master

Reviewed-on: railbird/rn-playground#82
This commit is contained in:
Ivan Malison 2024-02-03 20:25:25 -07:00
commit 3f0e0bb9a9
37 changed files with 1629 additions and 1497 deletions

View File

@ -21,5 +21,7 @@ jobs:
run: nix develop --impure --command bash -c 'export HOME=$PWD; yarn run lint' run: nix develop --impure --command bash -c 'export HOME=$PWD; yarn run lint'
- name: typecheck - name: typecheck
run: nix develop --impure --command bash -c 'export HOME=$PWD; yarn tsc --noEmit' run: nix develop --impure --command bash -c 'export HOME=$PWD; yarn tsc --noEmit'
- name: prettier
run: nix develop --impure --command bash -c 'export HOME=$PWD; prettier . --check'
- name: test - name: test
run: nix develop --impure --command bash -c 'export HOME=$PWD; yarn run test --no-watchman' run: nix develop --impure --command bash -c 'export HOME=$PWD; yarn run test --no-watchman'

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
/android
/ios
/react-native-vision-camera
flake.lock

View File

@ -1,16 +1,16 @@
import React from 'react'; import React from "react";
import * as scale from 'd3-scale'; import * as scale from "d3-scale";
import { View } from 'react-native'; import { View } from "react-native";
import { BarChart, XAxis, YAxis } from 'react-native-svg-charts'; import { BarChart, XAxis, YAxis } from "react-native-svg-charts";
import { useGraphData } from '../use-graph-data'; import { useGraphData } from "../use-graph-data";
import { GraphData, YAxisProps } from '../graph-types'; import { GraphData, YAxisProps } from "../graph-types";
import { CustomBars } from '../custom-bars'; import { CustomBars } from "../custom-bars";
import { CustomGrid } from '../custom-grid'; import { CustomGrid } from "../custom-grid";
import { graphStyles } from '../chart-styles'; import { graphStyles } from "../chart-styles";
import ChartView from '../chart-view'; import ChartView from "../chart-view";
import ChartLabel from '../chart-label/chart-label'; import ChartLabel from "../chart-label/chart-label";
interface Props { interface Props {
data: GraphData; data: GraphData;
@ -28,9 +28,14 @@ interface Props {
// TODO: separate PR will update useGraphData to take into account useCommonScale // TODO: separate PR will update useGraphData to take into account useCommonScale
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID, ...props }) => { export const BarGraph: React.FC<Props> = ({
data,
useCommonScale = false,
testID,
...props
}) => {
if (!data) { if (!data) {
return null return null;
} // TODO:#38 } // TODO:#38
const { const {
xValues, xValues,
@ -49,21 +54,31 @@ export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID
maxLeftYAxisValue, maxLeftYAxisValue,
maxRightYAxisValue, maxRightYAxisValue,
formatRightYAxisLabel, formatRightYAxisLabel,
formatLeftYAxisLabel formatLeftYAxisLabel,
} },
} },
// Proper error/loading handling from useQueryHandler can work with this rule #38 // Proper error/loading handling from useQueryHandler can work with this rule #38
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
} = useGraphData(data, { includeColors: false, ...props}); } = useGraphData(data, { includeColors: false, ...props });
// TODO: Will come from BE & destructured / color assigned in useGraphData // TODO: Will come from BE & destructured / color assigned in useGraphData
const yLabels = [{ displayName: 'Shots Taken', axis: 'LEFT' as 'LEFT', color: barColors[0] }, { displayName:'Make Percentage', axis: 'RIGHT' as 'RIGHT', color: barColors[1] }] const yLabels = [
const title = 'Shots Taken / Make Percentage by Cut Angle' { displayName: "Shots Taken", axis: "LEFT" as "LEFT", color: barColors[0] },
{
displayName: "Make Percentage",
axis: "RIGHT" as "RIGHT",
color: barColors[1],
},
];
const title = "Shots Taken / Make Percentage by Cut Angle";
return ( return (
<ChartView> <ChartView>
<ChartLabel title={title} yLabels={yLabels} /> <ChartLabel title={title} yLabels={yLabels} />
<View style={[graphStyles.rowContainer, { height: height }]} testID={`bar-graph-${testID}`}> <View
style={[graphStyles.rowContainer, { height: height }]}
testID={`bar-graph-${testID}`}
>
<YAxis <YAxis
data={yAxisLeftLabels.values} data={yAxisLeftLabels.values}
contentInset={contentInset} contentInset={contentInset}
@ -81,13 +96,19 @@ export const BarGraph: React.FC<Props> = ({ data, useCommonScale = false, testID
data={yData} data={yData}
gridMin={min} gridMin={min}
numberOfTicks={numberOfTicks} // rethink numberOfTicks, it should be determined automatically if we do our y axis scaling properly numberOfTicks={numberOfTicks} // rethink numberOfTicks, it should be determined automatically if we do our y axis scaling properly
yAccessor={({ item }) => (item as unknown as { value: number }).value} yAccessor={({ item }) =>
(item as unknown as { value: number }).value
}
contentInset={contentInset} contentInset={contentInset}
spacingInner={spacingInner} spacingInner={spacingInner}
spacingOuter={spacingOuter} spacingOuter={spacingOuter}
> >
<CustomGrid /> <CustomGrid />
<CustomBars barData={yData} xValues={xValues} barColors={barColors} /> <CustomBars
barData={yData}
xValues={xValues}
barColors={barColors}
/>
</BarChart> </BarChart>
<XAxis <XAxis
data={xValues.map((_, index) => index)} data={xValues.map((_, index) => index)}

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from "react";
import { Path } from 'react-native-svg'; import { Path } from "react-native-svg";
import { calculateBarOrigin, drawBarPath } from './custom-bar-utils'; import { calculateBarOrigin, drawBarPath } from "./custom-bar-utils";
import { ScaleBandType, ScaleLinearType, } from './graph-types'; import { ScaleBandType, ScaleLinearType } from "./graph-types";
interface BarProps { interface BarProps {
scaleX: ScaleBandType; scaleX: ScaleBandType;
@ -25,7 +25,7 @@ export const Bar: React.FC<BarProps> = ({
fill, fill,
barWidth, barWidth,
gap, gap,
roundedRadius roundedRadius,
}) => { }) => {
const { xOrigin, yOrigin, height } = calculateBarOrigin({ const { xOrigin, yOrigin, height } = calculateBarOrigin({
scaleX, scaleX,
@ -34,7 +34,7 @@ export const Bar: React.FC<BarProps> = ({
data, data,
barNumber, barNumber,
barWidth, barWidth,
gap gap,
}); });
return ( return (

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from "react";
import { Text, View } from 'react-native'; import { Text, View } from "react-native";
import { chartLabel } from '../chart-styles'; import { chartLabel } from "../chart-styles";
type Axis = 'RIGHT' | 'LEFT' type Axis = "RIGHT" | "LEFT";
interface YLabel { interface YLabel {
axis: Axis; axis: Axis;
@ -18,7 +18,10 @@ type ChartLabelProps = {
const renderLabels = (yLabels: Array<YLabel>) => { const renderLabels = (yLabels: Array<YLabel>) => {
return yLabels.map((label) => ( return yLabels.map((label) => (
<View key={`${label.axis}-${label.displayName}`} style={chartLabel.labelInnerRow}> <View
key={`${label.axis}-${label.displayName}`}
style={chartLabel.labelInnerRow}
>
<View <View
style={[chartLabel.labelColorBox, { backgroundColor: label.color }]} style={[chartLabel.labelColorBox, { backgroundColor: label.color }]}
/> />
@ -30,7 +33,6 @@ const renderLabels = (yLabels: Array<YLabel>) => {
}; };
export default function ChartLabel({ title, yLabels }: ChartLabelProps) { export default function ChartLabel({ title, yLabels }: ChartLabelProps) {
return ( return (
<View> <View>
<View style={chartLabel.titleRow}> <View style={chartLabel.titleRow}>

View File

@ -1,46 +1,46 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from "react-native";
import { colors, shadows } from '../../styles'; import { colors, shadows } from "../../styles";
export const graphStyles = StyleSheet.create({ export const graphStyles = StyleSheet.create({
container: { container: {
backgroundColor: colors.panelWhite, backgroundColor: colors.panelWhite,
borderColor: 'black', borderColor: "black",
borderRadius: 5, borderRadius: 5,
marginVertical: 10, marginVertical: 10,
marginHorizontal: 15, marginHorizontal: 15,
paddingTop: 15, paddingTop: 15,
paddingHorizontal: 15, paddingHorizontal: 15,
...shadows.standard ...shadows.standard,
}, },
rowContainer: { flexDirection: 'row', padding: 10 }, rowContainer: { flexDirection: "row", padding: 10 },
flex: { flex: 1 }, flex: { flex: 1 },
yAxisLeftPadding: { paddingRight: 3 }, yAxisLeftPadding: { paddingRight: 3 },
yAxisRightPadding: { paddingLeft: 3 }, yAxisRightPadding: { paddingLeft: 3 },
yAxisFontStyle: { fontSize: 10, fill: 'grey' }, yAxisFontStyle: { fontSize: 10, fill: "grey" },
xAxisFontStyle: { fontSize: 12, fill: 'black' }, xAxisFontStyle: { fontSize: 12, fill: "black" },
xAxisMarginTop: { marginTop: -15 }, xAxisMarginTop: { marginTop: -15 },
horizontalInset: { right: 10, left: 10 }, horizontalInset: { right: 10, left: 10 },
}); });
export const chartLabel = StyleSheet.create({ export const chartLabel = StyleSheet.create({
titleRow: { titleRow: {
flexDirection: 'row', flexDirection: "row",
marginHorizontal: 10 marginHorizontal: 10,
}, },
titleText: { fontWeight: '500' }, titleText: { fontWeight: "500" },
labelOuterRow: { labelOuterRow: {
flexDirection: 'row', flexDirection: "row",
flexWrap: 'wrap', flexWrap: "wrap",
marginHorizontal: 10 marginHorizontal: 10,
}, },
labelInnerRow: { flexDirection: 'row', alignItems: 'center', marginTop: 5 }, labelInnerRow: { flexDirection: "row", alignItems: "center", marginTop: 5 },
labelColorBox: { labelColorBox: {
height: 15, height: 15,
width: 15, width: 15,
borderRadius: 4, borderRadius: 4,
marginRight: 2 marginRight: 2,
}, },
labelTextMargin: { marginRight: 15 }, labelTextMargin: { marginRight: 15 },
labelText: { fontSize: 12 } labelText: { fontSize: 12 },
}); });

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from "react";
import { StyleProp, View, ViewStyle } from 'react-native'; import { StyleProp, View, ViewStyle } from "react-native";
import { graphStyles } from './chart-styles'; import { graphStyles } from "./chart-styles";
interface ChartViewProps { interface ChartViewProps {
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
@ -10,7 +10,11 @@ interface ChartViewProps {
} }
const ChartView: React.FC<ChartViewProps> = ({ children, style, testID }) => { const ChartView: React.FC<ChartViewProps> = ({ children, style, testID }) => {
return <View style={[graphStyles.container, style]} testID={testID} >{children}</View>; return (
<View style={[graphStyles.container, style]} testID={testID}>
{children}
</View>
);
}; };
export default ChartView; export default ChartView;

View File

@ -1,6 +1,6 @@
import { path as d3 } from 'd3-path'; import { path as d3 } from "d3-path";
import { ScaleLinearType, ScaleBandType } from './graph-types'; import { ScaleLinearType, ScaleBandType } from "./graph-types";
type BarCalculationProps = { type BarCalculationProps = {
scaleX: ScaleBandType; scaleX: ScaleBandType;
@ -19,10 +19,11 @@ export function calculateBarOrigin({
data, data,
index, index,
barNumber, barNumber,
gap gap,
}: BarCalculationProps): { xOrigin: number; yOrigin: number; height: number } { }: BarCalculationProps): { xOrigin: number; yOrigin: number; height: number } {
const firstBar = barNumber === 0; const firstBar = barNumber === 0;
const xOrigin = scaleX(index) + (firstBar ? 0 : barWidth * barNumber + gap * barNumber); const xOrigin =
scaleX(index) + (firstBar ? 0 : barWidth * barNumber + gap * barNumber);
const yOrigin = scaleY(data.value); const yOrigin = scaleY(data.value);
const height = scaleY(0) - yOrigin; const height = scaleY(0) - yOrigin;
@ -34,7 +35,7 @@ export function drawBarPath(
yOrigin: number, yOrigin: number,
barWidth: number, barWidth: number,
height: number, height: number,
roundedRadius: number roundedRadius: number,
): string { ): string {
const path = d3(); const path = d3();
path.moveTo(xOrigin, yOrigin + height); path.moveTo(xOrigin, yOrigin + height);
@ -46,7 +47,7 @@ export function drawBarPath(
yOrigin, yOrigin,
xOrigin + barWidth, xOrigin + barWidth,
yOrigin + roundedRadius, yOrigin + roundedRadius,
roundedRadius roundedRadius,
); );
path.lineTo(xOrigin + barWidth, yOrigin + height); path.lineTo(xOrigin + barWidth, yOrigin + height);
path.lineTo(xOrigin, yOrigin + height); path.lineTo(xOrigin, yOrigin + height);
@ -55,5 +56,8 @@ export function drawBarPath(
return path.toString(); return path.toString();
} }
export const calculateBarWidth = (bandwidth: number, combinedDataLength: number, gap: number) => export const calculateBarWidth = (
(bandwidth - gap * (combinedDataLength - 1)) / combinedDataLength; bandwidth: number,
combinedDataLength: number,
gap: number,
) => (bandwidth - gap * (combinedDataLength - 1)) / combinedDataLength;

View File

@ -1,9 +1,8 @@
import { Svg } from "react-native-svg";
import { Svg } from 'react-native-svg'; import { Bar } from "./bar";
import { ScaleBandType, ScaleLinearType } from "./graph-types";
import { Bar } from './bar'; import { calculateBarWidth } from "./custom-bar-utils";
import { ScaleBandType, ScaleLinearType } from './graph-types';
import { calculateBarWidth } from './custom-bar-utils';
export interface CombinedData { export interface CombinedData {
data: { value: number }[]; data: { value: number }[];
@ -29,7 +28,7 @@ export const CustomBars: React.FC<Partial<CustomBarsProps>> = ({
xValues, xValues,
barColors, barColors,
gap = 2, gap = 2,
roundedRadius = 4 roundedRadius = 4,
}) => { }) => {
const barWidth = calculateBarWidth(bandwidth, barData.length, gap); const barWidth = calculateBarWidth(bandwidth, barData.length, gap);

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from "react";
import { G, Line } from 'react-native-svg'; import { G, Line } from "react-native-svg";
import { colors } from '../../styles'; import { colors } from "../../styles";
import { ScaleBandType, ScaleLinearType } from './graph-types'; import { ScaleBandType, ScaleLinearType } from "./graph-types";
interface CustomGridProps { interface CustomGridProps {
x: ScaleBandType; x: ScaleBandType;
@ -38,7 +38,7 @@ export const CustomGrid: React.FC<Partial<CustomGridProps>> = ({
y, y,
ticks, ticks,
xTicks, xTicks,
includeVertical = false includeVertical = false,
}) => { }) => {
const [firstTick, ...remainingTicks] = ticks; const [firstTick, ...remainingTicks] = ticks;
const dashArray = [1, 3]; const dashArray = [1, 3];
@ -47,7 +47,12 @@ export const CustomGrid: React.FC<Partial<CustomGridProps>> = ({
const strokeDashWidth = 1; const strokeDashWidth = 1;
const strokeDashColor = colors.lightGrey; const strokeDashColor = colors.lightGrey;
const renderHorizontalLine = (tick: number, stroke: string, strokeWidth: number, dashArray?: number[]) => ( const renderHorizontalLine = (
tick: number,
stroke: string,
strokeWidth: number,
dashArray?: number[],
) => (
<Line <Line
key={`line-${tick}`} key={`line-${tick}`}
x1="0%" x1="0%"
@ -62,7 +67,12 @@ export const CustomGrid: React.FC<Partial<CustomGridProps>> = ({
const topY = y(Math.max(...ticks)); const topY = y(Math.max(...ticks));
const bottomY = y(Math.min(...ticks)); const bottomY = y(Math.min(...ticks));
const renderVerticalLine = (tick: number, stroke: string, strokeWidth: number, dashArray?: number[]) => { const renderVerticalLine = (
tick: number,
stroke: string,
strokeWidth: number,
dashArray?: number[],
) => {
return ( return (
<Line <Line
key={`vertical-line-${tick}`} key={`vertical-line-${tick}`}
@ -77,12 +87,21 @@ export const CustomGrid: React.FC<Partial<CustomGridProps>> = ({
); );
}; };
return ( return (
<G> <G>
{renderHorizontalLine(firstTick, strokeSolidColor, strokeSolidWidth)} {renderHorizontalLine(firstTick, strokeSolidColor, strokeSolidWidth)}
{remainingTicks.map((tick) => renderHorizontalLine(tick, strokeDashColor, strokeDashWidth, dashArray))} {remainingTicks.map((tick) =>
{includeVertical && xTicks.map((_, index) => renderVerticalLine(index, strokeDashColor, strokeDashWidth, dashArray))} renderHorizontalLine(tick, strokeDashColor, strokeDashWidth, dashArray),
)}
{includeVertical &&
xTicks.map((_, index) =>
renderVerticalLine(
index,
strokeDashColor,
strokeDashWidth,
dashArray,
),
)}
</G> </G>
); );
}; };

View File

@ -7,7 +7,7 @@ export const chartDefaults = {
contentInset: { top: 30, bottom: 30 }, contentInset: { top: 30, bottom: 30 },
numberOfTicks: 6, numberOfTicks: 6,
min: 0, min: 0,
barColors: ['#598EBB', '#F2D4BC', '#DB7878'], barColors: ["#598EBB", "#F2D4BC", "#DB7878"],
includeColors: true, includeColors: true,
lineStrokeWidth: 2 lineStrokeWidth: 2,
}; };

View File

@ -1,4 +1,4 @@
import { ScaleBand, ScaleLinear } from 'd3-scale' import { ScaleBand, ScaleLinear } from "d3-scale";
export type ScaleLinearType = ScaleLinear<number, number>; export type ScaleLinearType = ScaleLinear<number, number>;
export type ScaleBandType = ScaleBand<number | string>; export type ScaleBandType = ScaleBand<number | string>;
@ -13,7 +13,7 @@ export interface YAxisData {
formatLabel?: (value: number) => string; formatLabel?: (value: number) => string;
} }
export type XValue = string | number export type XValue = string | number;
export interface GraphData { export interface GraphData {
xValues: Array<XValue>; xValues: Array<XValue>;

View File

@ -1,6 +1,5 @@
import { GraphData } from "./graph-types"; import { GraphData } from "./graph-types";
export const convertToGraphData = ( export const convertToGraphData = (
graphData: GraphData, graphData: GraphData,
options: { options: {
@ -9,35 +8,45 @@ export const convertToGraphData = (
maxRightYAxisValue: number; maxRightYAxisValue: number;
maxLeftYAxisValue: number; maxLeftYAxisValue: number;
includeColors: boolean; includeColors: boolean;
barColors: Array<string> barColors: Array<string>;
} },
) => { ) => {
const xValues = graphData.xValues; const xValues = graphData.xValues;
const leftAxisIndex = graphData.yValues.findIndex( const leftAxisIndex = graphData.yValues.findIndex(
(y) => y.key === options.selectedLeftYAxisLabel (y) => y.key === options.selectedLeftYAxisLabel,
); );
const rightAxisIndex = graphData.yValues.findIndex( const rightAxisIndex = graphData.yValues.findIndex(
(y) => y.key === options.selectedRightYAxisLabel (y) => y.key === options.selectedRightYAxisLabel,
); );
// scale data points according to max value // scale data points according to max value
const yData = graphData.yValues.map((yAxis, index) => { const yData = graphData.yValues.map((yAxis, index) => {
const maxValue = index === rightAxisIndex ? options.maxRightYAxisValue : options.maxLeftYAxisValue; const maxValue =
index === rightAxisIndex
? options.maxRightYAxisValue
: options.maxLeftYAxisValue;
// scale values as a percentage of the max value // scale values as a percentage of the max value
const scaledValues = yAxis.values.map((value) => (value / maxValue) * 100); const scaledValues = yAxis.values.map((value) => (value / maxValue) * 100);
const strokeColor = options.includeColors && options.barColors ? options.barColors[index % options.barColors.length] : 'transparent'; const strokeColor =
options.includeColors && options.barColors
? options.barColors[index % options.barColors.length]
: "transparent";
const mappedData = scaledValues.map((scaledValue) => ({ value: scaledValue })); const mappedData = scaledValues.map((scaledValue) => ({
value: scaledValue,
}));
return { return {
data: mappedData, data: mappedData,
svg: { fill: 'transparent', stroke: strokeColor } // Apply the stroke color here svg: { fill: "transparent", stroke: strokeColor }, // Apply the stroke color here
}; };
}); });
const yAxisLeftLabels = leftAxisIndex !== -1 ? graphData.yValues[leftAxisIndex] : null; const yAxisLeftLabels =
const yAxisRightLabels = rightAxisIndex !== -1 ? graphData.yValues[rightAxisIndex] : undefined; leftAxisIndex !== -1 ? graphData.yValues[leftAxisIndex] : null;
const yAxisRightLabels =
rightAxisIndex !== -1 ? graphData.yValues[rightAxisIndex] : undefined;
return { yData, yAxisLeftLabels, yAxisRightLabels, xValues }; return { yData, yAxisLeftLabels, yAxisRightLabels, xValues };
}; };

View File

@ -1,20 +1,25 @@
import React from 'react'; import React from "react";
import * as shape from 'd3-shape'; import * as shape from "d3-shape";
import { View } from 'react-native'; import { View } from "react-native";
import { LineChart, XAxis, YAxis } from 'react-native-svg-charts'; import { LineChart, XAxis, YAxis } from "react-native-svg-charts";
import { useGraphData } from '../use-graph-data'; import { useGraphData } from "../use-graph-data";
import { CustomGrid } from '../custom-grid'; import { CustomGrid } from "../custom-grid";
import { graphStyles } from '../chart-styles'; import { graphStyles } from "../chart-styles";
import ChartView from '../chart-view'; import ChartView from "../chart-view";
import { CommonProps } from '../graph-types'; import { CommonProps } from "../graph-types";
// TODO: separate PR will update useGraphData to take into account useCommonScale // TODO: separate PR will update useGraphData to take into account useCommonScale
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const LineGraph: React.FC<CommonProps> = ({ data, useCommonScale = false, testID, ...props }) => { const LineGraph: React.FC<CommonProps> = ({
if (!data || typeof data !== 'object') { data,
return null useCommonScale = false,
}; // TODO:#38 testID,
...props
}) => {
if (!data || typeof data !== "object") {
return null;
} // TODO:#38
const { const {
xValues, xValues,
yData, yData,
@ -30,16 +35,19 @@ const LineGraph: React.FC<CommonProps> = ({ data, useCommonScale = false, testID
maxLeftYAxisValue, maxLeftYAxisValue,
maxRightYAxisValue, maxRightYAxisValue,
formatLeftYAxisLabel, formatLeftYAxisLabel,
formatRightYAxisLabel formatRightYAxisLabel,
} },
} },
// Proper error/loading handling from useQueryHandler can work with this rule #38 // Proper error/loading handling from useQueryHandler can work with this rule #38
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks
} = useGraphData(data, props); } = useGraphData(data, props);
return ( return (
<ChartView> <ChartView>
<View style={[graphStyles.rowContainer, { height: height }]} testID={`line-graph-${testID}`}> <View
style={[graphStyles.rowContainer, { height: height }]}
testID={`line-graph-${testID}`}
>
<YAxis <YAxis
data={yAxisLeftLabels.values} data={yAxisLeftLabels.values}
contentInset={contentInset} contentInset={contentInset}
@ -55,8 +63,10 @@ const LineGraph: React.FC<CommonProps> = ({ data, useCommonScale = false, testID
style={graphStyles.flex} style={graphStyles.flex}
data={yData} data={yData}
curve={shape.curveNatural} curve={shape.curveNatural}
svg={{ strokeWidth: lineStrokeWidth}} svg={{ strokeWidth: lineStrokeWidth }}
yAccessor={({ item }) => (item as unknown as { value: number }).value} yAccessor={({ item }) =>
(item as unknown as { value: number }).value
}
xAccessor={({ index }) => xValues[index] as number} xAccessor={({ index }) => xValues[index] as number}
gridMin={min} gridMin={min}
contentInset={contentInset} contentInset={contentInset}
@ -76,7 +86,10 @@ const LineGraph: React.FC<CommonProps> = ({ data, useCommonScale = false, testID
data={yAxisRightLabels?.values ?? yAxisLeftLabels.values} data={yAxisRightLabels?.values ?? yAxisLeftLabels.values}
contentInset={contentInset} contentInset={contentInset}
svg={graphStyles.yAxisFontStyle} svg={graphStyles.yAxisFontStyle}
style={[graphStyles.yAxisRightPadding, { height: useCommonScale ? 0 : 'auto' }]} style={[
graphStyles.yAxisRightPadding,
{ height: useCommonScale ? 0 : "auto" },
]}
min={min} min={min}
max={maxRightYAxisValue} max={maxRightYAxisValue}
numberOfTicks={numberOfTicks} numberOfTicks={numberOfTicks}

View File

@ -1,10 +1,8 @@
import { useMemo } from 'react'; import { useMemo } from "react";
import { convertToGraphData } from './graph-utils';
import { chartDefaults } from './graph-config';
import { GraphData, GraphProps, XValue, YAxisData } from './graph-types';
import { convertToGraphData } from "./graph-utils";
import { chartDefaults } from "./graph-config";
import { GraphData, GraphProps, XValue, YAxisData } from "./graph-types";
interface useGraphDataInterface { interface useGraphDataInterface {
xValues: Array<XValue>; xValues: Array<XValue>;
@ -24,7 +22,10 @@ interface useGraphDataInterface {
// this version assumes string values for X, this isn't necessarily the case // this version assumes string values for X, this isn't necessarily the case
// convertToGraphData is specifically tailored to bar/group bar graphs // convertToGraphData is specifically tailored to bar/group bar graphs
// ultimately this component could be used by any x & y axis graph types (line/bar/scatter) // ultimately this component could be used by any x & y axis graph types (line/bar/scatter)
export const useGraphData = (graphData: GraphData, props: Partial<GraphProps>): useGraphDataInterface => { export const useGraphData = (
graphData: GraphData,
props: Partial<GraphProps>,
): useGraphDataInterface => {
const { yAxisProps = {}, ...otherProps } = props; const { yAxisProps = {}, ...otherProps } = props;
const defaultProps = { const defaultProps = {
...chartDefaults, ...chartDefaults,
@ -33,18 +34,30 @@ export const useGraphData = (graphData: GraphData, props: Partial<GraphProps>):
yAxisProps: { yAxisProps: {
maxLeftYAxisValue: Math.max(...(graphData.yValues[0]?.values ?? [0])), maxLeftYAxisValue: Math.max(...(graphData.yValues[0]?.values ?? [0])),
maxRightYAxisValue: maxRightYAxisValue:
graphData.yValues.length > 1 ? Math.max(...graphData.yValues[1]?.values) : undefined, graphData.yValues.length > 1
? Math.max(...graphData.yValues[1]?.values)
: undefined,
formatRightYAxisLabel: yAxisProps.formatRightYAxisLabel, formatRightYAxisLabel: yAxisProps.formatRightYAxisLabel,
formatLeftYAxisLabel: yAxisProps.formatLeftYAxisLabel, formatLeftYAxisLabel: yAxisProps.formatLeftYAxisLabel,
selectedLeftYAxisLabel: graphData.yValues[0]?.key, selectedLeftYAxisLabel: graphData.yValues[0]?.key,
selectedRightYAxisLabel: graphData.yValues[1]?.key, selectedRightYAxisLabel: graphData.yValues[1]?.key,
...yAxisProps ...yAxisProps,
} },
}; };
const { yData, yAxisLeftLabels, yAxisRightLabels, xValues } = useMemo( const { yData, yAxisLeftLabels, yAxisRightLabels, xValues } = useMemo(
() => convertToGraphData(graphData, { ...defaultProps.yAxisProps, includeColors: defaultProps.includeColors, barColors: defaultProps.barColors}), () =>
[graphData, defaultProps.yAxisProps, defaultProps.includeColors, defaultProps.barColors] convertToGraphData(graphData, {
...defaultProps.yAxisProps,
includeColors: defaultProps.includeColors,
barColors: defaultProps.barColors,
}),
[
graphData,
defaultProps.yAxisProps,
defaultProps.includeColors,
defaultProps.barColors,
],
); );
return { return {
@ -52,6 +65,6 @@ export const useGraphData = (graphData: GraphData, props: Partial<GraphProps>):
yData, yData,
yAxisLeftLabels, yAxisLeftLabels,
yAxisRightLabels, yAxisRightLabels,
defaultProps defaultProps,
}; };
}; };

View File

@ -15,14 +15,16 @@ import { RecordingButton } from "./capture-button";
import { useIsForeground } from "./is-foreground"; import { useIsForeground } from "./is-foreground";
import { useIsFocused } from "@react-navigation/native"; import { useIsFocused } from "@react-navigation/native";
export default function CameraScreen({ route, navigation }): React.ReactElement { export default function CameraScreen({
route,
navigation,
}): React.ReactElement {
// TODO: #73 Does this need to be passed to Camera component? // TODO: #73 Does this need to be passed to Camera component?
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const { gameType, tableSize, tags, location } = route.params const { gameType, tableSize, tags, location } = route.params;
// LOG for params -- Remove when no longer needed // LOG for params -- Remove when no longer needed
// Note: camelCased value being passed, change on record.tsx if you want a different value format // Note: camelCased value being passed, change on record.tsx if you want a different value format
console.log(gameType, tableSize, tags, location) console.log(gameType, tableSize, tags, location);
const camera = useRef<Camera>(null); const camera = useRef<Camera>(null);
const { hasPermission, requestPermission } = useCameraPermission(); const { hasPermission, requestPermission } = useCameraPermission();
@ -95,7 +97,13 @@ export default function CameraScreen({ route, navigation }): React.ReactElement
orientation={orientation} // TODO: #60 orientation={orientation} // TODO: #60
isActive={isActive} isActive={isActive}
/> />
<View style={orientation === "portrait" ? styles.goBackPortrait : styles.goBackLandscape}> <View
style={
orientation === "portrait"
? styles.goBackPortrait
: styles.goBackLandscape
}
>
<Button title="Go back" onPress={() => navigation.goBack()} /> <Button title="Go back" onPress={() => navigation.goBack()} />
</View> </View>
<RecordingButton <RecordingButton
@ -156,14 +164,14 @@ const styles = StyleSheet.create({
left: 20, // needs refined left: 20, // needs refined
}, },
goBackPortrait: { goBackPortrait: {
position: 'absolute', position: "absolute",
top: 20, // or wherever you want the button to be positioned in portrait top: 20, // or wherever you want the button to be positioned in portrait
left: 20, // or wherever you want the button to be positioned in portrait left: 20, // or wherever you want the button to be positioned in portrait
}, },
goBackLandscape: { goBackLandscape: {
position: 'absolute', position: "absolute",
top: 40, top: 40,
right: 20, right: 20,
transform: [{ rotate: '90deg' }], transform: [{ rotate: "90deg" }],
}, },
}); });

View File

@ -1,31 +1,32 @@
import { Dimensions, Platform } from 'react-native' import { Dimensions, Platform } from "react-native";
import StaticSafeAreaInsets from 'react-native-static-safe-area-insets' import StaticSafeAreaInsets from "react-native-static-safe-area-insets";
export const CONTENT_SPACING = 15 export const CONTENT_SPACING = 15;
const SAFE_BOTTOM = const SAFE_BOTTOM =
Platform.select({ Platform.select({
ios: StaticSafeAreaInsets.safeAreaInsetsBottom, ios: StaticSafeAreaInsets.safeAreaInsetsBottom,
}) ?? 0 }) ?? 0;
export const SAFE_AREA_PADDING = { export const SAFE_AREA_PADDING = {
paddingLeft: StaticSafeAreaInsets.safeAreaInsetsLeft + CONTENT_SPACING, paddingLeft: StaticSafeAreaInsets.safeAreaInsetsLeft + CONTENT_SPACING,
paddingTop: StaticSafeAreaInsets.safeAreaInsetsTop + CONTENT_SPACING, paddingTop: StaticSafeAreaInsets.safeAreaInsetsTop + CONTENT_SPACING,
paddingRight: StaticSafeAreaInsets.safeAreaInsetsRight + CONTENT_SPACING, paddingRight: StaticSafeAreaInsets.safeAreaInsetsRight + CONTENT_SPACING,
paddingBottom: SAFE_BOTTOM + CONTENT_SPACING, paddingBottom: SAFE_BOTTOM + CONTENT_SPACING,
} };
// The maximum zoom _factor_ you should be able to zoom in // The maximum zoom _factor_ you should be able to zoom in
export const MAX_ZOOM_FACTOR = 10 export const MAX_ZOOM_FACTOR = 10;
export const SCREEN_WIDTH = Dimensions.get('window').width export const SCREEN_WIDTH = Dimensions.get("window").width;
export const SCREEN_HEIGHT = Platform.select<number>({ export const SCREEN_HEIGHT = Platform.select<number>({
android: Dimensions.get('screen').height - StaticSafeAreaInsets.safeAreaInsetsBottom, android:
ios: Dimensions.get('window').height, Dimensions.get("screen").height - StaticSafeAreaInsets.safeAreaInsetsBottom,
}) as number ios: Dimensions.get("window").height,
}) as number;
// Capture Button // Capture Button
export const CAPTURE_BUTTON_SIZE = 78 export const CAPTURE_BUTTON_SIZE = 78;
// Control Button like Flash // Control Button like Flash
export const CONTROL_BUTTON_SIZE = 40 export const CONTROL_BUTTON_SIZE = 40;

View File

@ -1,16 +1,16 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from "react";
import { AppState, AppStateStatus } from 'react-native' import { AppState, AppStateStatus } from "react-native";
export const useIsForeground = (): boolean => { export const useIsForeground = (): boolean => {
const [isForeground, setIsForeground] = useState(true) const [isForeground, setIsForeground] = useState(true);
useEffect(() => { useEffect(() => {
const onChange = (state: AppStateStatus): void => { const onChange = (state: AppStateStatus): void => {
setIsForeground(state === 'active') setIsForeground(state === "active");
} };
const listener = AppState.addEventListener('change', onChange) const listener = AppState.addEventListener("change", onChange);
return () => listener.remove() return () => listener.remove();
}, [setIsForeground]) }, [setIsForeground]);
return isForeground return isForeground;
} };

View File

@ -1,6 +1,6 @@
import { registerRootComponent } from 'expo'; import { registerRootComponent } from "expo";
import App from './App'; import App from "./App";
// registerRootComponent calls AppRegistry.registerComponent('main', () => App); // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build, // It also ensures that whether you load the app in Expo Go or in a native build,

View File

@ -1,64 +1,64 @@
export const graph_data_one_measures = { export const graph_data_one_measures = {
xValues: ['x_1', 'x_2', 'x_3', 'x_4', 'x_5'], xValues: ["x_1", "x_2", "x_3", "x_4", "x_5"],
yValues: [ yValues: [
{ {
key: 'measure_1', key: "measure_1",
values: [100, 140, 90, 80, 40] values: [100, 140, 90, 80, 40],
} },
] ],
}; };
export const graph_data_two_measures = { export const graph_data_two_measures = {
xValues: ['x_1', 'x_2', 'x_3', 'x_4', 'x_5'], xValues: ["x_1", "x_2", "x_3", "x_4", "x_5"],
yValues: [ yValues: [
{ {
key: 'measure_1', key: "measure_1",
values: [100, 140, 90, 80, 40] values: [100, 140, 90, 80, 40],
}, },
{ {
key: 'measure_2', key: "measure_2",
values: [78, 82, 73, 56, 61], values: [78, 82, 73, 56, 61],
formatLabel: (value: number) => `${value}%` formatLabel: (value: number) => `${value}%`,
} },
] ],
}; };
export const graph_data_three_measures = { export const graph_data_three_measures = {
xValues: ['x_1', 'x_2', 'x_3', 'x_4', 'x_5'], xValues: ["x_1", "x_2", "x_3", "x_4", "x_5"],
yValues: [ yValues: [
{ {
key: 'measure_1', key: "measure_1",
values: [100, 140, 90, 80, 40] values: [100, 140, 90, 80, 40],
}, },
{ {
key: 'measure_2', key: "measure_2",
values: [78, 82, 73, 56, 61], values: [78, 82, 73, 56, 61],
formatLabel: (value: number) => `${value}%` formatLabel: (value: number) => `${value}%`,
}, },
{ {
key: 'measure_3', key: "measure_3",
values: [77, 32, 45, 65, 50] values: [77, 32, 45, 65, 50],
} },
] ],
}; };
export const line_chart_one_y_data = { export const line_chart_one_y_data = {
xValues: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], xValues: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
yValues: [ yValues: [
{ {
key: 'measure_1', key: "measure_1",
values: [100, 140, 90, 80, 40, 20, 70, 20, 30, 30] values: [100, 140, 90, 80, 40, 20, 70, 20, 30, 30],
} },
] ],
}; };
export const line_chart_two_y_data = { export const line_chart_two_y_data = {
xValues: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], xValues: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
yValues: [ yValues: [
{ {
key: 'measure_1', key: "measure_1",
values: [100, 140, 90, 80, 40, 20, 70, 20, 30, 30] values: [100, 140, 90, 80, 40, 20, 70, 20, 30, 30],
}, },
{ {
key: 'measure_2', key: "measure_2",
values: [50, 67, 123, 140, 156, 147, 126, 180, 123, 87] values: [50, 67, 123, 140, 156, 147, 126, 180, 123, 87],
} },
] ],
}; };

View File

@ -1,11 +1,15 @@
import { useColorScheme } from 'react-native'; import { useColorScheme } from "react-native";
import { NavigationContainer, DarkTheme, DefaultTheme } from '@react-navigation/native'; import {
import { createNativeStackNavigator } from '@react-navigation/native-stack' NavigationContainer,
DarkTheme,
DefaultTheme,
} from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import Tabs from './tab-navigator'; import Tabs from "./tab-navigator";
import Login from '../screens/login'; import Login from "../screens/login";
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator();
const ScreensStack = () => ( const ScreensStack = () => (
<Stack.Navigator> <Stack.Navigator>
@ -20,7 +24,7 @@ const ScreensStack = () => (
options={{ headerShown: false }} options={{ headerShown: false }}
/> />
</Stack.Navigator> </Stack.Navigator>
) );
/** /**
* Functional component for app navigation. Configures a navigation container with a stack navigator. * Functional component for app navigation. Configures a navigation container with a stack navigator.
* Dynamically selects between dark and light themes based on the device's color scheme. * Dynamically selects between dark and light themes based on the device's color scheme.
@ -29,15 +33,14 @@ const ScreensStack = () => (
* @returns {React.ComponentType} A NavigationContainer wrapping a Stack.Navigator for app screens. * @returns {React.ComponentType} A NavigationContainer wrapping a Stack.Navigator for app screens.
*/ */
export default function AppNavigator(): React.JSX.Element { export default function AppNavigator(): React.JSX.Element {
// useColorScheme get's the theme from device settings // useColorScheme get's the theme from device settings
const scheme = useColorScheme(); const scheme = useColorScheme();
return ( return (
<NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}> <NavigationContainer theme={scheme === "dark" ? DarkTheme : DefaultTheme}>
<Stack.Navigator screenOptions={{ headerShown: false }}> <Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="App" component={ScreensStack} /> <Stack.Screen name="App" component={ScreensStack} />
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
) );
} }

View File

@ -1,33 +1,27 @@
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { Image } from 'react-native'; import { Image } from "react-native";
import CameraScreen from '../component/video/camera'; import CameraScreen from "../component/video/camera";
import Session from '../screens/session'; import Session from "../screens/session";
import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createNativeStackNavigator } from "@react-navigation/native-stack";
import RecordScreen from '../screens/video-stack/record'; import RecordScreen from "../screens/video-stack/record";
// TODO: add ts support for assets folder to use imports // TODO: add ts support for assets folder to use imports
const Icon = require('../assets/favicon.png') const Icon = require("../assets/favicon.png");
const Tab = createBottomTabNavigator(); const Tab = createBottomTabNavigator();
const RecordStack = createNativeStackNavigator(); const RecordStack = createNativeStackNavigator();
// tabBarIcon configuration should live on separate file and contain all logic/icons/rendering for the Tabs // tabBarIcon configuration should live on separate file and contain all logic/icons/rendering for the Tabs
const tabIcons = { const tabIcons = {
'Session': <Image source={Icon} style={{ width: 20, height: 20 }} />, Session: <Image source={Icon} style={{ width: 20, height: 20 }} />,
'VideoStack': <Image source={Icon} style={{ width: 20, height: 20 }} />, VideoStack: <Image source={Icon} style={{ width: 20, height: 20 }} />,
}; };
function VideoTabStack() { function VideoTabStack() {
return ( return (
<RecordStack.Navigator screenOptions={{ headerShown: false }}> <RecordStack.Navigator screenOptions={{ headerShown: false }}>
<RecordStack.Screen <RecordStack.Screen name="Record" component={RecordScreen} />
name="Record" <RecordStack.Screen name="Camera" component={CameraScreen} />
component={RecordScreen}
/>
<RecordStack.Screen
name="Camera"
component={CameraScreen}
/>
</RecordStack.Navigator> </RecordStack.Navigator>
); );
} }
@ -45,15 +39,23 @@ export default function Tabs(): React.JSX.Element {
<Tab.Navigator <Tab.Navigator
screenOptions={({ route }) => ({ screenOptions={({ route }) => ({
headerShown: false, headerShown: false,
tabBarActiveTintColor: 'tomato', tabBarActiveTintColor: "tomato",
tabBarInactiveTintColor: 'gray', tabBarInactiveTintColor: "gray",
tabBarIcon: () => { tabBarIcon: () => {
return tabIcons[route.name]; return tabIcons[route.name];
} },
})} })}
> >
<Tab.Screen name="Session" component={Session} options={{ tabBarLabel: 'Session' }} /> <Tab.Screen
<Tab.Screen name="VideoStack" component={VideoTabStack} options={{ tabBarLabel: 'Record' }} /> name="Session"
component={Session}
options={{ tabBarLabel: "Session" }}
/>
<Tab.Screen
name="VideoStack"
component={VideoTabStack}
options={{ tabBarLabel: "Record" }}
/>
</Tab.Navigator> </Tab.Navigator>
); );
} }

View File

@ -1,15 +1,14 @@
import React from 'react' import React from "react";
import { View, StyleSheet } from "react-native"; import { View, StyleSheet } from "react-native";
import BarGraph from '../component/charts/bar-graph/bar-graph'; import BarGraph from "../component/charts/bar-graph/bar-graph";
import { graph_data_two_measures } from '../mock/charts/mock-data'; import { graph_data_two_measures } from "../mock/charts/mock-data";
// Session Mock - can be used for session summary screen using a query handler component // Session Mock - can be used for session summary screen using a query handler component
// BarGraph component using mocked data currently // BarGraph component using mocked data currently
export default function SessionScreen() { export default function SessionScreen() {
return ( return (
<View style={StyleSheet.absoluteFill}> <View style={StyleSheet.absoluteFill}>
<BarGraph data={graph_data_two_measures} /> <BarGraph data={graph_data_two_measures} />
</View> </View>
) );
} }

View File

@ -1,7 +1,14 @@
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from "react";
import { View, TextInput, TouchableWithoutFeedback, Text, TouchableOpacity, Keyboard } from 'react-native'; import {
import DropDownPicker from 'react-native-dropdown-picker'; View,
import { recordStyles as styles } from './styles'; TextInput,
TouchableWithoutFeedback,
Text,
TouchableOpacity,
Keyboard,
} from "react-native";
import DropDownPicker from "react-native-dropdown-picker";
import { recordStyles as styles } from "./styles";
interface CameraScreenParams { interface CameraScreenParams {
gameType: string; gameType: string;
@ -14,14 +21,13 @@ interface CameraScreenParams {
// Precedes Camera.tsx // Precedes Camera.tsx
// Can be made into Modal when ready // Can be made into Modal when ready
export default function RecordScreen({ navigation }): React.JSX.Element { export default function RecordScreen({ navigation }): React.JSX.Element {
// Game type dropdown // Game type dropdown
const [gameTypeOpen, setGameTypeOpen] = useState<boolean>(false) const [gameTypeOpen, setGameTypeOpen] = useState<boolean>(false);
const [gameType, setGameType] = useState<string | null>(null) // This is a dropdown const [gameType, setGameType] = useState<string | null>(null); // This is a dropdown
const [gameTypes, setGameTypes] = useState([ const [gameTypes, setGameTypes] = useState([
{ label: 'Free Play', value: 'freePlay' }, { label: "Free Play", value: "freePlay" },
{ label: 'Straight Pool', value: 'straightPool' }, { label: "Straight Pool", value: "straightPool" },
{ label: 'Nine Ball', value: 'nineBall' } { label: "Nine Ball", value: "nineBall" },
]); ]);
const onGameTypeOpen = useCallback(() => { const onGameTypeOpen = useCallback(() => {
setTableSizeOpen(false); setTableSizeOpen(false);
@ -29,12 +35,12 @@ export default function RecordScreen({ navigation }): React.JSX.Element {
}, []); }, []);
// Table size dropdown // Table size dropdown
const [tableSizeOpen, setTableSizeOpen] = useState<boolean>(false) const [tableSizeOpen, setTableSizeOpen] = useState<boolean>(false);
const [tableSize, setTableSize] = useState<string>('') const [tableSize, setTableSize] = useState<string>("");
const [tableSizes, setTableSizes] = useState([ const [tableSizes, setTableSizes] = useState([
{ label: `9'`, value: 'nineFoot' }, { label: `9'`, value: "nineFoot" },
{ label: `8'`, value: 'eightFoot' }, { label: `8'`, value: "eightFoot" },
{ label: '7', value: 'sevenFoot' } { label: "7", value: "sevenFoot" },
]); ]);
const onTableSizeOpen = useCallback(() => { const onTableSizeOpen = useCallback(() => {
setGameTypeOpen(false); setGameTypeOpen(false);
@ -42,12 +48,12 @@ export default function RecordScreen({ navigation }): React.JSX.Element {
}, []); }, []);
// Tags multi-select dropdown // Tags multi-select dropdown
const [tagsOpen, setTagsOpen] = useState<boolean>(false) const [tagsOpen, setTagsOpen] = useState<boolean>(false);
const [tags, setTags] = useState<Array<string>>([]) const [tags, setTags] = useState<Array<string>>([]);
const [tagsList, setTagsList] = useState([ const [tagsList, setTagsList] = useState([
{ label: `Tag1`, value: 'tag1' }, { label: `Tag1`, value: "tag1" },
{ label: `Tag2`, value: 'tag2' }, { label: `Tag2`, value: "tag2" },
{ label: 'Tag3', value: 'tag3' } { label: "Tag3", value: "tag3" },
]); ]);
const onTagsOpen = useCallback(() => { const onTagsOpen = useCallback(() => {
setTableSizeOpen(false); setTableSizeOpen(false);
@ -55,7 +61,7 @@ export default function RecordScreen({ navigation }): React.JSX.Element {
}, []); }, []);
// Location // Location
const [location, setLocation] = useState<string>('') const [location, setLocation] = useState<string>("");
const handleSubmit = () => { const handleSubmit = () => {
// needs to pass info as params or store in a context/state provider // needs to pass info as params or store in a context/state provider
@ -63,10 +69,10 @@ export default function RecordScreen({ navigation }): React.JSX.Element {
gameType: gameType, gameType: gameType,
tableSize: tableSize, tableSize: tableSize,
tags: tags, tags: tags,
location: location location: location,
};
navigation.push("Camera", params);
}; };
navigation.push('Camera', params)
}
const dropDownStyles = { const dropDownStyles = {
style: styles.dropdownStyle, style: styles.dropdownStyle,
@ -75,7 +81,6 @@ export default function RecordScreen({ navigation }): React.JSX.Element {
return ( return (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}> <TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<View style={styles.container}> <View style={styles.container}>
<View style={styles.dropdownContainer}> <View style={styles.dropdownContainer}>
<Text style={styles.dropdownTitle}>Game Type</Text> <Text style={styles.dropdownTitle}>Game Type</Text>
<DropDownPicker <DropDownPicker
@ -102,7 +107,6 @@ export default function RecordScreen({ navigation }): React.JSX.Element {
setItems={setTableSizes} setItems={setTableSizes}
onOpen={onTableSizeOpen} onOpen={onTableSizeOpen}
{...dropDownStyles} {...dropDownStyles}
/> />
<Text style={styles.dropdownTitle}>Tags</Text> <Text style={styles.dropdownTitle}>Tags</Text>
<DropDownPicker <DropDownPicker
@ -119,19 +123,21 @@ export default function RecordScreen({ navigation }): React.JSX.Element {
setItems={setTagsList} setItems={setTagsList}
onOpen={onTagsOpen} onOpen={onTagsOpen}
{...dropDownStyles} {...dropDownStyles}
/> />
</View> </View>
<Text style={styles.dropdownTitle}>Location</Text> <Text style={styles.dropdownTitle}>Location</Text>
<TextInput <TextInput
style={styles.input} style={styles.input}
placeholder='Location' placeholder="Location"
value={location} value={location}
onChangeText={(value) => setLocation(value)} onChangeText={(value) => setLocation(value)}
/> />
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<TouchableOpacity style={styles.buttonStyle} onPress={() => navigation.goBack()}> <TouchableOpacity
style={styles.buttonStyle}
onPress={() => navigation.goBack()}
>
<Text style={styles.buttonText}>Back</Text> <Text style={styles.buttonText}>Back</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={styles.buttonStyle} onPress={handleSubmit}> <TouchableOpacity style={styles.buttonStyle} onPress={handleSubmit}>
@ -140,6 +146,5 @@ export default function RecordScreen({ navigation }): React.JSX.Element {
</View> </View>
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
) );
} }

View File

@ -1,56 +1,56 @@
import { StyleSheet } from 'react-native' import { StyleSheet } from "react-native";
export const recordStyles = StyleSheet.create({ export const recordStyles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
alignItems: 'center', alignItems: "center",
justifyContent: 'center', justifyContent: "center",
padding: 20, padding: 20,
}, },
dropdownContainer: { dropdownContainer: {
width: '100%', width: "100%",
marginBottom: 20, marginBottom: 20,
zIndex: 50 zIndex: 50,
}, },
dropdownTitle: { dropdownTitle: {
fontSize: 16, fontSize: 16,
fontWeight: '500', fontWeight: "500",
marginBottom: 5, marginBottom: 5,
alignSelf: 'flex-start' alignSelf: "flex-start",
}, },
input: { input: {
width: '100%', width: "100%",
marginBottom: 20, marginBottom: 20,
borderWidth: 1, borderWidth: 1,
borderColor: 'grey', borderColor: "grey",
borderRadius: 5, borderRadius: 5,
padding: 10, padding: 10,
}, },
buttonContainer: { buttonContainer: {
flexDirection: 'row', flexDirection: "row",
justifyContent: 'space-between', justifyContent: "space-between",
width: '100%', width: "100%",
}, },
buttonStyle: { buttonStyle: {
backgroundColor: 'lightblue', backgroundColor: "lightblue",
paddingVertical: 10, paddingVertical: 10,
paddingHorizontal: 20, paddingHorizontal: 20,
borderRadius: 20, borderRadius: 20,
margin: 10, margin: 10,
}, },
buttonText: { buttonText: {
color: 'white', color: "white",
textAlign: 'center', textAlign: "center",
}, },
dropdownStyle: { dropdownStyle: {
backgroundColor: '#ffffff', backgroundColor: "#ffffff",
borderColor: '#D1D1D1', borderColor: "#D1D1D1",
borderWidth: 1, borderWidth: 1,
borderRadius: 4, borderRadius: 4,
}, },
dropdownContainerStyle: { dropdownContainerStyle: {
marginBottom: 10, marginBottom: 10,
borderColor: '#D1D1D1', borderColor: "#D1D1D1",
borderWidth: 1, borderWidth: 1,
borderRadius: 4, borderRadius: 4,
}, },

View File

@ -2,25 +2,25 @@
// COLORS: // COLORS:
// can be made more granular to specify utility (ex: fontColors vs backgroundColors) // can be made more granular to specify utility (ex: fontColors vs backgroundColors)
export const colors = { export const colors = {
bgBlack: '#121212', bgBlack: "#121212",
lightGrey: '#BFC2C8', lightGrey: "#BFC2C8",
themeBrown: '#D9AA84', themeBrown: "#D9AA84",
panelWhite: '#F2FBFE', panelWhite: "#F2FBFE",
tournamentBlue: '#50a6c2', tournamentBlue: "#50a6c2",
blueCloth: '#539dc2', blueCloth: "#539dc2",
buttonBlue: '#1987ff', buttonBlue: "#1987ff",
textWhite: '#ffffff' textWhite: "#ffffff",
}; };
export const shadows = { export const shadows = {
standard: { standard: {
shadowColor: '#000000', shadowColor: "#000000",
shadowOffset: { shadowOffset: {
width: 0, width: 0,
height: 3 height: 3,
}, },
shadowOpacity: 0.1, shadowOpacity: 0.1,
shadowRadius: 4.65, shadowRadius: 4.65,
elevation: 3 elevation: 3,
}, },
}; };

View File

@ -1,20 +1,20 @@
import React from 'react'; import React from "react";
import { render } from '@testing-library/react-native'; import { render } from "@testing-library/react-native";
import BarGraph from '../../component/charts/bar-graph/bar-graph'; import BarGraph from "../../component/charts/bar-graph/bar-graph";
import { graph_data_two_measures } from '../../mock/charts/mock-data'; import { graph_data_two_measures } from "../../mock/charts/mock-data";
describe('BarGraph Component Tests', () => { describe("BarGraph Component Tests", () => {
it("renders correctly with data", () => {
it('renders correctly with data', () => { const { getByTestId } = render(
const { getByTestId } = render(<BarGraph data={graph_data_two_measures} testID='1'/>); <BarGraph data={graph_data_two_measures} testID="1" />,
);
expect(getByTestId(`bar-graph-1`)).toBeTruthy(); expect(getByTestId(`bar-graph-1`)).toBeTruthy();
}); });
it('does not render without data', () => { it("does not render without data", () => {
// Have to ts-ignore to test null data conditions // Have to ts-ignore to test null data conditions
// @ts-ignore // @ts-ignore
const { queryByTestId } = render(<BarGraph testID='2'/>); const { queryByTestId } = render(<BarGraph testID="2" />);
expect(queryByTestId(`bar-graph-2`)).toBeNull(); expect(queryByTestId(`bar-graph-2`)).toBeNull();
}); });
}); });

View File

@ -1,37 +1,41 @@
import React from 'react'; import React from "react";
import { render } from '@testing-library/react-native'; import { render } from "@testing-library/react-native";
import "@testing-library/jest-native/extend-expect"; import "@testing-library/jest-native/extend-expect";
import ChartLabel from '../../component/charts/chart-label/chart-label'; import ChartLabel from "../../component/charts/chart-label/chart-label";
describe('ChartLabel Component Tests', () => { describe("ChartLabel Component Tests", () => {
const mockData = { const mockData = {
yLabels: [ yLabels: [
{ displayName: 'Shots Taken', axis: 'LEFT' as 'LEFT', color: '#598EBB' }, { displayName: "Shots Taken", axis: "LEFT" as "LEFT", color: "#598EBB" },
{ displayName:'Make Percentage', axis: 'RIGHT' as 'RIGHT', color: '#F2D4BC'} {
displayName: "Make Percentage",
axis: "RIGHT" as "RIGHT",
color: "#F2D4BC",
},
], ],
title: 'Shots Taken / Make Percentage by Cut Angle' title: "Shots Taken / Make Percentage by Cut Angle",
}; };
it('should render the correct labels given yLabels', () => { it("should render the correct labels given yLabels", () => {
const { getByText } = render( const { getByText } = render(
<ChartLabel title={mockData.title} yLabels={mockData.yLabels} /> <ChartLabel title={mockData.title} yLabels={mockData.yLabels} />,
); );
mockData.yLabels.forEach(label => { mockData.yLabels.forEach((label) => {
expect(getByText(label.displayName)).toBeTruthy(); expect(getByText(label.displayName)).toBeTruthy();
}); });
}); });
it('should render the correct number of label boxes', () => { it("should render the correct number of label boxes", () => {
const { getAllByText } = render( const { getAllByText } = render(
<ChartLabel title={mockData.title} yLabels={mockData.yLabels} /> <ChartLabel title={mockData.title} yLabels={mockData.yLabels} />,
); );
// Assuming displayName is unique and used only for labels // Assuming displayName is unique and used only for labels
const labelElements = mockData.yLabels.map(label => const labelElements = mockData.yLabels
getAllByText(label.displayName) .map((label) => getAllByText(label.displayName))
).flat(); .flat();
expect(labelElements.length).toBe(mockData.yLabels.length); expect(labelElements.length).toBe(mockData.yLabels.length);
}); });

View File

@ -1,31 +1,31 @@
import React from 'react'; import React from "react";
import { Text } from 'react-native' import { Text } from "react-native";
import { render } from '@testing-library/react-native'; import { render } from "@testing-library/react-native";
import "@testing-library/jest-native/extend-expect"; import "@testing-library/jest-native/extend-expect";
import ChartView from '../../component/charts/chart-view'; import ChartView from "../../component/charts/chart-view";
describe('ChartView Component Tests', () => { describe("ChartView Component Tests", () => {
it('applies the passed style prop correctly', () => { it("applies the passed style prop correctly", () => {
const testStyle = { backgroundColor: 'blue', padding: 10 }; const testStyle = { backgroundColor: "blue", padding: 10 };
const { getByTestId } = render( const { getByTestId } = render(
<ChartView style={testStyle} testID="chart-view"> <ChartView style={testStyle} testID="chart-view">
<Text>Test Child</Text> <Text>Test Child</Text>
</ChartView> </ChartView>,
); );
const chartView = getByTestId('chart-view'); const chartView = getByTestId("chart-view");
expect(chartView.props.style).toEqual(expect.arrayContaining([testStyle])); expect(chartView.props.style).toEqual(expect.arrayContaining([testStyle]));
}); });
it('renders children correctly', () => { it("renders children correctly", () => {
const { getByText } = render( const { getByText } = render(
<ChartView> <ChartView>
<Text testID="child-text">Child Component</Text> <Text testID="child-text">Child Component</Text>
</ChartView> </ChartView>,
); );
const childText = getByText('Child Component'); const childText = getByText("Child Component");
expect(childText).toBeTruthy(); expect(childText).toBeTruthy();
}); });
}); });

View File

@ -1,11 +1,13 @@
import React from 'react'; import React from "react";
import { render } from '@testing-library/react-native'; import { render } from "@testing-library/react-native";
import "@testing-library/jest-native/extend-expect"; import "@testing-library/jest-native/extend-expect";
import * as scale from 'd3-scale' import * as scale from "d3-scale";
import { CustomBars } from '../../component/charts/custom-bars';
import { calculateBarOrigin, drawBarPath } from '../../component/charts/custom-bar-utils';
import { CustomBars } from "../../component/charts/custom-bars";
import {
calculateBarOrigin,
drawBarPath,
} from "../../component/charts/custom-bar-utils";
const mockYScaleFunction = scale.scaleLinear(); const mockYScaleFunction = scale.scaleLinear();
const mockXScaleFunction = scale.scaleBand(); const mockXScaleFunction = scale.scaleBand();
@ -13,17 +15,17 @@ const mockBandwidth = 100;
const mockCombinedData = [ const mockCombinedData = [
{ {
data: [{ value: 10 }, { value: 20 }], data: [{ value: 10 }, { value: 20 }],
svg: { fill: 'red' }, svg: { fill: "red" },
}, },
]; ];
const mockRawData = [ const mockRawData = [
{ label: 'Full ball', shotsTaken: 10, makePercentage: 20 }, { label: "Full ball", shotsTaken: 10, makePercentage: 20 },
]; ];
const mockBarColors = ['red', 'blue']; const mockBarColors = ["red", "blue"];
const mockGap = 2; const mockGap = 2;
const mockRoundedRadius = 4; const mockRoundedRadius = 4;
describe('CustomBars Component Tests', () => { describe("CustomBars Component Tests", () => {
it('should render correct number of Svg and Bar components', () => { it("should render correct number of Svg and Bar components", () => {
const { getAllByTestId } = render( const { getAllByTestId } = render(
<CustomBars <CustomBars
x={mockXScaleFunction} x={mockXScaleFunction}
@ -34,7 +36,7 @@ describe('CustomBars Component Tests', () => {
barColors={mockBarColors} barColors={mockBarColors}
gap={mockGap} gap={mockGap}
roundedRadius={mockRoundedRadius} roundedRadius={mockRoundedRadius}
/> />,
); );
const svgs = getAllByTestId(/svg-/); const svgs = getAllByTestId(/svg-/);
@ -43,12 +45,11 @@ describe('CustomBars Component Tests', () => {
expect(svgs.length).toBe(mockRawData.length); expect(svgs.length).toBe(mockRawData.length);
expect(bars.length).toBe(mockCombinedData.length * mockRawData.length); expect(bars.length).toBe(mockCombinedData.length * mockRawData.length);
}); });
}); });
describe('Bar utility functions', () => { describe("Bar utility functions", () => {
describe('calculateBarOrigin', () => { describe("calculateBarOrigin", () => {
it('calculates properties correctly', () => { it("calculates properties correctly", () => {
const mockData = { value: 10 }; const mockData = { value: 10 };
const mockIndex = 1; const mockIndex = 1;
const mockBarNumber = 2; const mockBarNumber = 2;
@ -62,26 +63,33 @@ describe('Bar utility functions', () => {
index: mockIndex, index: mockIndex,
barNumber: mockBarNumber, barNumber: mockBarNumber,
barWidth: mockBarWidth, barWidth: mockBarWidth,
gap: mockGap gap: mockGap,
}); });
expect(result).toEqual({ expect(result).toEqual({
xOrigin: expect.any(Number), xOrigin: expect.any(Number),
yOrigin: expect.any(Number), yOrigin: expect.any(Number),
height: expect.any(Number) height: expect.any(Number),
}); });
}); });
}); });
describe('drawBarPath', () => { describe("drawBarPath", () => {
it('generates a correct SVG path string', () => { it("generates a correct SVG path string", () => {
const xOrigin = 50; const xOrigin = 50;
const yOrigin = 100; const yOrigin = 100;
const barWidth = 20; const barWidth = 20;
const height = 60; const height = 60;
const roundedRadius = 10; const roundedRadius = 10;
const path = drawBarPath(xOrigin, yOrigin, barWidth, height, roundedRadius); const path = drawBarPath(
const expectedPath = 'M50,160L50,110A10,10,0,0,1,60,100L60,100A10,10,0,0,1,70,110L70,160L50,160Z' xOrigin,
yOrigin,
barWidth,
height,
roundedRadius,
);
const expectedPath =
"M50,160L50,110A10,10,0,0,1,60,100L60,100A10,10,0,0,1,70,110L70,160L50,160Z";
expect(path).toBe(expectedPath); expect(path).toBe(expectedPath);
}); });

View File

@ -1,21 +1,20 @@
import React from 'react'; import React from "react";
import { render } from '@testing-library/react-native'; import { render } from "@testing-library/react-native";
import LineGraph from '../../component/charts/line-graph/line-graph'; import LineGraph from "../../component/charts/line-graph/line-graph";
import { line_chart_two_y_data } from '../../mock/charts/mock-data'; import { line_chart_two_y_data } from "../../mock/charts/mock-data";
describe('LineGraph Component Tests', () => { describe("LineGraph Component Tests", () => {
it("renders correctly with data", () => {
it('renders correctly with data', () => { const { getByTestId } = render(
const { getByTestId } = render(<LineGraph data={line_chart_two_y_data} testID='1'/>); <LineGraph data={line_chart_two_y_data} testID="1" />,
);
expect(getByTestId(`line-graph-1`)).toBeTruthy(); expect(getByTestId(`line-graph-1`)).toBeTruthy();
}); });
it('does not render without data', () => { it("does not render without data", () => {
// Have to ts-ignore to test null data conditions // Have to ts-ignore to test null data conditions
// @ts-ignore // @ts-ignore
const { queryByTestId } = render(<LineGraph testID='2'/>); const { queryByTestId } = render(<LineGraph testID="2" />);
expect(queryByTestId(`line-graph-2`)).toBeNull(); expect(queryByTestId(`line-graph-2`)).toBeNull();
}); });
}); });

View File

@ -1,58 +1,66 @@
import { renderHook } from '@testing-library/react-native'; import { renderHook } from "@testing-library/react-native";
import { useGraphData } from '../../component/charts/use-graph-data'; import { useGraphData } from "../../component/charts/use-graph-data";
import { GraphData, GraphProps } from '../../component/charts/graph-types'; import { GraphData, GraphProps } from "../../component/charts/graph-types";
describe('useGraphData', () => { describe("useGraphData", () => {
it('should return correctly processed data from convertToGraphData', () => { it("should return correctly processed data from convertToGraphData", () => {
// mock values // mock values
const mockGraphData: GraphData = { const mockGraphData: GraphData = {
xValues: ['full hit', '3/4 ball ball', '1/2 ball'], xValues: ["full hit", "3/4 ball ball", "1/2 ball"],
yValues: [ yValues: [
{ key: 'left', values: [10, 20, 30] }, { key: "left", values: [10, 20, 30] },
{ key: 'right', values: [40, 50, 60] } { key: "right", values: [40, 50, 60] },
] ],
}; };
const mockProps: Partial<GraphProps> = { const mockProps: Partial<GraphProps> = {
yAxisProps: { yAxisProps: {
maxLeftYAxisValue: 30, maxLeftYAxisValue: 30,
maxRightYAxisValue: 60, maxRightYAxisValue: 60,
selectedLeftYAxisLabel: 'left', selectedLeftYAxisLabel: "left",
selectedRightYAxisLabel: 'right', selectedRightYAxisLabel: "right",
formatRightYAxisLabel: (value) => `${value}%`, formatRightYAxisLabel: (value) => `${value}%`,
formatLeftYAxisLabel: (value) => `${value}%` formatLeftYAxisLabel: (value) => `${value}%`,
} },
}; };
const { result } = renderHook(() => useGraphData(mockGraphData, mockProps)); const { result } = renderHook(() => useGraphData(mockGraphData, mockProps));
// values expected // values expected
const expectedYData = [ const expectedYData = [
{ {
data: [{ value: 33.33 }, { value: 66.67 }, { value: 100 }], data: [{ value: 33.33 }, { value: 66.67 }, { value: 100 }],
svg: { fill: 'transparent' }, svg: { fill: "transparent" },
}, },
{ {
data: [{ value: 66.67 }, { value: 83.33 }, { value: 100 }], data: [{ value: 66.67 }, { value: 83.33 }, { value: 100 }],
svg: { fill: 'transparent' }, svg: { fill: "transparent" },
}, },
]; ];
const expectedLeftLabels = { key: 'left', values: [10, 20, 30] }; const expectedLeftLabels = { key: "left", values: [10, 20, 30] };
const expectedRightLabels = { key: 'right', values: [40, 50, 60] }; const expectedRightLabels = { key: "right", values: [40, 50, 60] };
expect(result.current).toBeDefined(); expect(result.current).toBeDefined();
expect(result.current.xValues).toEqual(['full hit', '3/4 ball ball', '1/2 ball']); expect(result.current.xValues).toEqual([
"full hit",
"3/4 ball ball",
"1/2 ball",
]);
result.current.yData.forEach((yDataItem, index) => { result.current.yData.forEach((yDataItem, index) => {
yDataItem.data.forEach((dataItem, dataIndex) => { yDataItem.data.forEach((dataItem, dataIndex) => {
expect(dataItem.value).toBeCloseTo(expectedYData[index].data[dataIndex].value, 2); expect(dataItem.value).toBeCloseTo(
expectedYData[index].data[dataIndex].value,
2,
);
}); });
}); });
expect(result.current.yAxisLeftLabels).toEqual(expectedLeftLabels); expect(result.current.yAxisLeftLabels).toEqual(expectedLeftLabels);
expect(result.current.yAxisRightLabels).toEqual(expectedRightLabels); expect(result.current.yAxisRightLabels).toEqual(expectedRightLabels);
expect(result.current.defaultProps.yAxisProps.selectedLeftYAxisLabel).toEqual('left'); expect(
expect(result.current.defaultProps.yAxisProps.selectedRightYAxisLabel).toEqual('right'); result.current.defaultProps.yAxisProps.selectedLeftYAxisLabel,
).toEqual("left");
expect(
result.current.defaultProps.yAxisProps.selectedRightYAxisLabel,
).toEqual("right");
expect(result.current.defaultProps).toBeDefined(); expect(result.current.defaultProps).toBeDefined();
}); });
}); });

View File

@ -9513,6 +9513,11 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier-plugin-organize-imports@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz#77967f69d335e9c8e6e5d224074609309c62845e"
integrity sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==
pretty-bytes@5.6.0: pretty-bytes@5.6.0:
version "5.6.0" version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"