From db9e9fb5ea0ed1ca54248fbb843e0b9c0b2ceefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=81opaci=C5=84ski?= Date: Sun, 20 Aug 2023 14:57:57 +0200 Subject: [PATCH] Start working on additional props validations --- src/components/views/GraphView.tsx | 40 ++++++------------ src/utils/layout.ts | 65 ++++++++++++++++++------------ src/validations/GraphView.ts | 63 +++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 53 deletions(-) create mode 100644 src/validations/GraphView.ts diff --git a/src/components/views/GraphView.tsx b/src/components/views/GraphView.tsx index 0e6db872..bbb5a40a 100644 --- a/src/components/views/GraphView.tsx +++ b/src/components/views/GraphView.tsx @@ -1,21 +1,26 @@ -import { memo, PropsWithChildren, useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { StyleSheet, View } from 'react-native'; +import { useAnimatedReaction } from 'react-native-reanimated'; -import { DEFAULT_VIEW_SETTINGS } from '@/configs/view'; import GraphViewChildrenProvider, { useGraphViewChildrenContext } from '@/contexts/GraphViewChildrenProvider'; import OverlayProvider, { OverlayOutlet } from '@/contexts/OverlayProvider'; import CanvasProvider, { useGesturesContext } from '@/providers/view'; -import { GraphViewSettings } from '@/types/settings'; -import { deepMemoComparator } from '@/utils/objects'; - -type GraphViewProps = PropsWithChildren; +import { deepMemoComparator, unsharedify } from '@/utils/objects'; +import { GraphViewProps, validateProps } from '@/validations/GraphView'; function GraphView({ children, ...providerProps }: GraphViewProps) { - validateProps(providerProps); const providerComposer = useMemo(() => , []); + useAnimatedReaction( + () => unsharedify(providerProps), // TODO - fix (doesn't work with shared values) + props => { + console.log('>>>>>'); + validateProps(props); + } + ); + return ( @@ -25,27 +30,6 @@ function GraphView({ children, ...providerProps }: GraphViewProps) { ); } -const validateProps = ({ initialScale, scales }: GraphViewProps) => { - // Validate parameters - if (scales) { - if (scales.length === 0) { - throw new Error('At least one scale must be provided'); - } - if ( - scales.indexOf(initialScale ?? DEFAULT_VIEW_SETTINGS.initialScale) < 0 - ) { - throw new Error('Initial scale must be included in scales'); - } - if (scales.some(scale => scale <= 0)) { - throw new Error('All scales must be positive'); - } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (scales.some((scale, index) => scale <= scales[index - 1]!)) { - throw new Error('Scales must be in ascending order'); - } - } -}; - const GraphViewComposer = memo(function () { // CONTEXTS // Graph view children context diff --git a/src/utils/layout.ts b/src/utils/layout.ts index 1174e7a6..54c2df5e 100644 --- a/src/utils/layout.ts +++ b/src/utils/layout.ts @@ -61,44 +61,59 @@ export const getAlignedVertexAbsolutePosition = ( return { x, y }; }; -// Use object instead of set to support reanimated worklets +// Use objects instead of sets to support reanimated worklets const ALL_SPACING_KEYS = { bottom: true, left: true, right: true, top: true }; +const AXIS_SPACING_KEYS = { horizontal: true, vertical: true }; const isAllSpacing = (spacing: Spacing): spacing is BoundingRect => { 'worklet'; return ( typeof spacing === 'object' && + spacing !== null && Object.keys(spacing).every( value => ALL_SPACING_KEYS[value as keyof typeof ALL_SPACING_KEYS] ) ); }; +const isAxisSpacing = (spacing: Spacing): spacing is AxisSpacing => { + 'worklet'; + return ( + typeof spacing === 'object' && + spacing !== null && + Object.keys(spacing).every( + value => AXIS_SPACING_KEYS[value as keyof typeof AXIS_SPACING_KEYS] + ) + ); +}; + export const updateSpacing = (spacing?: Spacing): BoundingRect => { 'worklet'; - if (!spacing) { - return { bottom: 0, left: 0, right: 0, top: 0 }; - } - if (!isNaN(spacing as number)) { - return { - bottom: spacing as number, - left: spacing as number, - right: spacing as number, - top: spacing as number - }; + if (spacing) { + if (!isNaN(spacing as number)) { + return { + bottom: spacing as number, + left: spacing as number, + right: spacing as number, + top: spacing as number + }; + } + if (isAllSpacing(spacing)) { + return Object.fromEntries( + ([...Object.keys(ALL_SPACING_KEYS)] as Array).map( + key => [key, spacing[key] ?? 0] + ) + ) as BoundingRect; + } + if (isAxisSpacing(spacing)) { + const { horizontal, vertical } = spacing; + return { + bottom: vertical ?? 0, + left: horizontal ?? 0, + right: horizontal ?? 0, + top: vertical ?? 0 + }; + } } - if (isAllSpacing(spacing)) { - return Object.fromEntries( - ([...Object.keys(ALL_SPACING_KEYS)] as Array).map( - key => [key, spacing[key] ?? 0] - ) - ) as BoundingRect; - } - const { horizontal, vertical } = spacing as AxisSpacing; - return { - bottom: vertical ?? 0, - left: horizontal ?? 0, - right: horizontal ?? 0, - top: vertical ?? 0 - }; + return { bottom: 0, left: 0, right: 0, top: 0 }; }; diff --git a/src/validations/GraphView.ts b/src/validations/GraphView.ts new file mode 100644 index 00000000..56ace345 --- /dev/null +++ b/src/validations/GraphView.ts @@ -0,0 +1,63 @@ +import { PropsWithChildren } from 'react'; + +import { DEFAULT_VIEW_SETTINGS } from '@/configs/view'; +import { ObjectFit } from '@/types/layout'; +import { GraphViewSettings } from '@/types/settings'; +import { updateSpacing } from '@/utils/layout'; + +export type GraphViewProps = PropsWithChildren; + +const OBJECT_FIT_VALUES = new Set(['contain', 'cover', 'none']); + +export const validateProps = ({ + autoSizingTimeout, + initialScale, + objectFit, + padding, + scales +}: GraphViewProps) => { + // objectFit + if (objectFit !== undefined && !OBJECT_FIT_VALUES.has(objectFit)) { + throw new Error( + `objectFit must be one of ${Array.from(OBJECT_FIT_VALUES).join(', ')}` + ); + } + // padding + if (padding !== undefined) { + for (const [key, value] of Object.entries(updateSpacing(padding))) { + if (isNaN(value) || value < 0) { + throw new Error(`padding.${key} must be a positive number`); + } + } + } + // autoSizingTimeout + if (autoSizingTimeout !== undefined) { + if (isNaN(autoSizingTimeout) || autoSizingTimeout < 0) { + throw new Error('autoSizingTimeout must be a non-negative number'); + } + } + // initialScale + if (initialScale !== undefined) { + if (isNaN(initialScale) || initialScale <= 0) { + throw new Error('initialScale must be a positive number'); + } + } + // Scales + if (scales) { + if (scales.length === 0) { + throw new Error('At least one scale must be provided'); + } + if ( + scales.indexOf(initialScale ?? DEFAULT_VIEW_SETTINGS.initialScale) < 0 + ) { + throw new Error('Initial scale must be included in scales'); + } + if (scales.some(scale => scale <= 0)) { + throw new Error('All scales must be positive'); + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if (scales.some((scale, index) => scale <= scales[index - 1]!)) { + throw new Error('Scales must be in ascending order'); + } + } +};