Skip to content
This repository was archived by the owner on Apr 7, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 12 additions & 28 deletions src/components/views/GraphView.tsx
Original file line number Diff line number Diff line change
@@ -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<GraphViewSettings>;
import { deepMemoComparator, unsharedify } from '@/utils/objects';
import { GraphViewProps, validateProps } from '@/validations/GraphView';

function GraphView({ children, ...providerProps }: GraphViewProps) {
validateProps(providerProps);
const providerComposer = useMemo(() => <GraphViewComposer />, []);

useAnimatedReaction(
() => unsharedify(providerProps), // TODO - fix (doesn't work with shared values)
props => {
console.log('>>>>>');
validateProps(props);
}
);

return (
<View style={styles.container}>
<GraphViewChildrenProvider graphViewChildren={children}>
Expand All @@ -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
Expand Down
65 changes: 40 additions & 25 deletions src/utils/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<keyof BoundingRect>).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<keyof BoundingRect>).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 };
};
63 changes: 63 additions & 0 deletions src/validations/GraphView.ts
Original file line number Diff line number Diff line change
@@ -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<GraphViewSettings>;

const OBJECT_FIT_VALUES = new Set<ObjectFit>(['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');
}
}
};