Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/mn 566/vehicle part selection #812

Merged
merged 22 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
950dcf4
Added wireframes SVG
dlymonkai Jun 21, 2024
009e91a
wip on types
dlymonkai Jun 21, 2024
20e2757
✨ add part selection wire frame to sights
arunachalam-monk Jul 1, 2024
3903b81
⚡️ wireframe to apply different svgo rules
arunachalam-monk Jul 5, 2024
5d462e7
⬆️ use group elements instead of groupIds
arunachalam-monk Jul 9, 2024
f67653d
✨add vehicle wireframe component
arunachalam-monk Jul 9, 2024
9802483
⚡️ add states to handle from multiple view
arunachalam-monk Jul 9, 2024
9587450
✨ add rotate left and rotate right icon
arunachalam-monk Jul 10, 2024
073da75
✅add tests
arunachalam-monk Jul 16, 2024
c811e12
♻️ remove generics to make it much simpler
arunachalam-monk Jul 16, 2024
182c8a3
📝global docs for vehicle wireframe selection
arunachalam-monk Jul 16, 2024
f8cd7bb
✅ fix linting
arunachalam-monk Jul 16, 2024
4dec492
✅ add test for build wireframe
arunachalam-monk Jul 16, 2024
fb6ea02
♻️ modify VehicleDynamicWireframe to move styling to it's parent
arunachalam-monk Jul 17, 2024
f6df517
👌 rename wireFrame, vehicleModel, test util function
arunachalam-monk Jul 17, 2024
e0d07fc
👌 add more standardized comments
arunachalam-monk Jul 19, 2024
b43db77
♻️ move components to folders
arunachalam-monk Jul 19, 2024
03e8344
♻️ remove VehicleTypeMapVehicleModel
arunachalam-monk Jul 19, 2024
7bc1509
👌 add correct tsdocs
arunachalam-monk Jul 25, 2024
90b23ac
👕 add if to be block, set max width of code
arunachalam-monk Jul 25, 2024
ef1798d
👌 missing changes
arunachalam-monk Jul 25, 2024
b824dd1
✅ onPartSelected is wrong
arunachalam-monk Jul 26, 2024
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
69 changes: 41 additions & 28 deletions configs/eslint-config-base/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,63 @@ const ERROR = 2;
module.exports = {
root: true,
extends: [
'airbnb-base',
'eslint:recommended',
'plugin:jest/recommended',
'plugin:import/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:prettier/recommended',
'plugin:promise/recommended',
"airbnb-base",
"eslint:recommended",
"plugin:jest/recommended",
"plugin:import/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:prettier/recommended",
"plugin:promise/recommended",
],
plugins: ['import', 'prettier', 'jest'],
plugins: ["import", "prettier", "jest"],
env: {
browser: true,
es6: true,
jest: true,
},
settings: {
'import/resolver': {
"import/resolver": {
node: {
extensions: ['.js', '.jsx'],
extensions: [".js", ".jsx"],
},
},
},
rules: {
'prettier/prettier': ERROR,
'import/prefer-default-export': OFF,
'import/extensions': OFF,
'promise/always-return': OFF,
'no-plusplus': [ERROR, { allowForLoopAfterthoughts: true }],
'lines-between-class-members': OFF,
'dot-notation': OFF,
'indent': OFF,
'promise/catch-or-return': [ERROR, { allowFinally: true }],
'import/no-unresolved': [ERROR, { caseSensitive: false }],
'jest/no-mocks-import': OFF,
'jest/expect-expect': OFF,
"prettier/prettier": ERROR,
"import/prefer-default-export": OFF,
"import/extensions": OFF,
"promise/always-return": OFF,
"no-plusplus": [ERROR, { allowForLoopAfterthoughts: true }],
"lines-between-class-members": OFF,
"dot-notation": OFF,
indent: OFF,
curly: [ERROR, "all"],
"max-len": [
ERROR,
{
code: 120,
comments: 120,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true,
},
],
"capitalized-comments": [ERROR],
"promise/catch-or-return": [ERROR, { allowFinally: true }],
"import/no-unresolved": [ERROR, { caseSensitive: false }],
"jest/no-mocks-import": OFF,
"jest/expect-expect": OFF,
},
overrides: [
{
files: ['test/**/*.{js,jsx}'],
files: ["test/**/*.{js,jsx}"],
rules: {
'import/first': OFF,
'import/order': OFF,
'@typescript-eslint/no-empty-function': OFF,
'class-methods-use-this': OFF,
"import/first": OFF,
"import/order": OFF,
"@typescript-eslint/no-empty-function": OFF,
"class-methods-use-this": OFF,
},
},
],
Expand Down
31 changes: 30 additions & 1 deletion configs/test-utils/src/__mocks__/@monkvision/sights.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { TaskName, VehicleModel, VehicleType } from '@monkvision/types';
import {
PartSelectionOrientation,
TaskName,
VehicleModel,
VehiclePart,
VehicleType,
WireframeDictionary,
} from '@monkvision/types';

const vehicles = {
[VehicleModel.FESC20]: {
Expand All @@ -7,6 +14,12 @@ const vehicles = {
model: 'Escape SE 2020',
type: VehicleType.CUV,
},
[VehicleModel.FF150]: {
id: VehicleModel.FF150,
make: 'Ford',
model: 'F-150 Super Cab XL 2014',
type: VehicleType.PICKUP,
},
};

const labels = {
Expand Down Expand Up @@ -80,8 +93,24 @@ const sights = {
},
};

const partSelectionWireframes: WireframeDictionary = {
[VehicleModel.FESC20]: {
[PartSelectionOrientation.FRONT_LEFT]: `<svg><path id="${VehiclePart.BUMPER_BACK}"></path></svg>`,
[PartSelectionOrientation.REAR_LEFT]: '<svg>FR</svg>',
[PartSelectionOrientation.REAR_RIGHT]: '<svg>RL</svg>',
[PartSelectionOrientation.FRONT_RIGHT]: '<svg>RR</svg>',
},
[VehicleModel.AUDIA7]: {
[PartSelectionOrientation.FRONT_LEFT]: `<svg><path id="${VehiclePart.BUMPER_FRONT}"></path></svg>`,
[PartSelectionOrientation.REAR_LEFT]: `<svg><path id="${VehiclePart.BUMPER_BACK}"></path></svg>`,
[PartSelectionOrientation.REAR_RIGHT]: `<svg><path id="${VehiclePart.TAIL_LIGHT_RIGHT}"></path></svg>`,
[PartSelectionOrientation.FRONT_RIGHT]: `<svg><path id="${VehiclePart.WHEEL_BACK_RIGHT}"></path></svg>`,
},
};

export = {
vehicles,
labels,
sights,
partSelectionWireframes,
};
10 changes: 10 additions & 0 deletions configs/test-utils/src/expects/props.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ export function expectPropsOnChildMock(
expect(Component).toHaveBeenCalledWith(expect.objectContaining(props), expect.anything());
}

/**
* Similar to expectPropsOnChildMock but check for last mock call only.
*/
export function expectLastPropsOnChildMock<T extends jest.Mock | FC<any> | ForwardedRef<any>>(
Component: T,
props: T extends (prop: infer P) => any ? Partial<P> : never,
): void {
expect(Component).toHaveBeenLastCalledWith(expect.objectContaining(props), expect.anything());
}

export type SimpleTestProps = { [key: string]: unknown };

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/analytics/src/adapters/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export interface AnalyticsAdapter {
resetUser: () => void;
/**
* Track an event.
* We recommend using the '[object][verb]' format for your event names, where '[object]' is the entity that the behavior relates to, and '[verb]' is the behavior itself.
* We recommend using the '[object][verb]' format for your event names, where '[object]' is the entity that the
* behavior relates to, and '[verb]' is the behavior itself.
* For example: `Project Created`, `User Signed Up`, or `Invite Sent`.
*
* @param name The name of the event.
Expand Down
59 changes: 55 additions & 4 deletions packages/common-ui-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ function App() {
| Prop | Type | Description | Required | Default Value |
|---------------|--------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------|
| svg | string | The XML string representing the SVG to display | ✔️ | |
| getAttributes | <code>(element: Element, groupIds: string[]) => SVGProps<SVGSVGElement> &#124; null</code> | A customization function that lets you specify custom HTML attributes to give to the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
| getInnerText | <code>(element: Element, groupIds: string[]) => string &#124; null</code> | A customization function that lets you specify the innner text of the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
| getAttributes | <code>(element: Element, groups: SVGGElement[]) => SVGProps<SVGSVGElement> &#124; null</code> | A customization function that lets you specify custom HTML attributes to give to the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
| getInnerText | <code>(element: Element, groups: SVGGElement[]) => string &#124; null</code> | A customization function that lets you specify the innner text of the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |

---

Expand Down Expand Up @@ -415,8 +415,8 @@ function App() {
| Prop | Type | Description | Required | Default Value |
|---------------|--------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------|
| sight | Sight | The sight to display the SVG overlay of. | ✔️ | |
| getAttributes | <code>(element: Element, groupIds: string[]) => SVGProps<SVGSVGElement> &#124; null</code> | A customization function that lets you specify custom HTML attributes to give to the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
| getInnerText | <code>(element: Element, groupIds: string[]) => string &#124; null</code> | A customization function that lets you specify the innner text of the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
| getAttributes | <code>(element: Element, groups: SVGGElement[]) => SVGProps<SVGSVGElement> &#124; null</code> | A customization function that lets you specify custom HTML attributes to give to the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |
| getInnerText | <code>(element: Element, groups: SVGGElement[]) => string &#124; null</code> | A customization function that lets you specify the innner text of the tags in the SVG file based on the HTML element itself and the IDs of the groups it is part of. | | |

---

Expand Down Expand Up @@ -621,3 +621,54 @@ function VehicleSelectionPage() {
| inspectionId | string | The ID of the inspection. | | |
| apiDomain | string | The domain of the Monk API. | | |
| authToken | string | The authentication token used to communicate with the API. | | |
## VehiclePartSelection
I shows the collections of VehicleDynamicWireframe and we can switch between 4 different views front left, front right, rear left and rear right.
### Example
```tsx
function Component() {
return <VehiclePartSelection
vehicleModel={VehicleModel.FESC20}
onPartsSelected={(p) => console.log(p)} />
}
```
### Props
| Prop | Type | Description | Required| Default Value|
|-----------------|--------------------------------|-----------------------------------------------------------------------------------------|---------|--------------|
| vehicleModel | VehicleModel | Initial vehicle model. | ✔️ | |
| orientation | PartSelectionOrientation | Orientation where the vehicle want to face. | | front-left |
| onPartsSelected | (parts: VehiclePart[]) => void | Callback called when update selected parts. | | |

## VehicleDynamicWireframe
For the given Vehicle Model and orientation. It shows the wireframe on the view and we can able to select it.
### Example
```tsx
import { PartSelectionOrientation, VehicleModel, VehiclePart } from '@monkvision/types';

function Component() {
const [parts, setParts] = useState<Array<VehiclePart>>([]);
const onPartSelected = (parts) => {
console.log(parts);
setParts(parts);
}
return <VehicleDynamicWireframe
vehicleModel={VehicleModel.FESC20}
orientation={PartSelectionOrientation.FRONT_LEFT}
parts={selectedParts}
onPartsSelected={onPartSelected}
/>
}
```
vehicleModel
orientation
onClickPart
getPartAttributes

### Props
| Prop | Type | Description | Required| Default Value|
|-------------------|---------------------------------|-----------------------------------------------------------------------------------------|---------|--------------|
| vehicleModel | VehicleModel | Initial vehicle model. | ✔️ | |
| orientation | PartSelectionOrientation | Orientation where the vehicle want to face. | | front-left |
| parts | VehiclePart[] | Initial selected parts. Mainly used to persist selected parts state between rerendering.| | [] |
| onClickPart | (part: VehiclePart) => void | Callback called when a part is clicked. | | |
| getPartAttributes | (part: VehiclePart) => SVGProps | Custom function for HTML attributes to give to the tags based on part. | | |

Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export interface DynamicSVGProps
* import React, { useCallback } from 'react';
* import { DynamicSVG } from '@monkvision/common-ui-web';
*
* const svg = '<svg height="100" width="100"><circle id="circle1" cx="20" cy="20" r="30"/><circle id="circle2" cx="80" cy="80" r="30"/></svg>';
* const svg = `<svg height="100" width="100">
* <circle id="circle1" cx="20" cy="20" r="30"/><circle id="circle2" cx="80" cy="80" r="30"/></svg>`;
*
* // Applies a red fill and an onClick handler on the element with ID "circle1"
* function MyCustomSVG() {
Expand Down
17 changes: 8 additions & 9 deletions packages/common-ui-web/src/components/DynamicSVG/SVGElement.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { JSX } from 'react';
import { JSXIntrinsicSVGElements } from '@monkvision/types';
import { JSX } from 'react';
import {
DynamicSVGCustomizationFunctions,
SVGElementCustomProps,
Expand All @@ -18,9 +18,8 @@ export type SVGElementProps<T extends keyof JSXIntrinsicSVGElements> = JSX.Intri
DynamicSVGCustomizationFunctions &
SVGElementCustomProps;

function getChildrenGroupIds({ element, groupIds }: Required<SVGElementCustomProps>): string[] {
const id = element.getAttribute('id');
return element.tagName === 'g' && id ? [...groupIds, id] : groupIds;
function getChildrenGroupIds({ element, groups }: Required<SVGElementCustomProps>): SVGGElement[] {
return element.tagName === 'g' ? [...groups, element as SVGGElement] : groups;
}

/**
Expand All @@ -34,7 +33,7 @@ function getChildrenGroupIds({ element, groupIds }: Required<SVGElementCustomPro
*/
export function SVGElement<T extends keyof JSXIntrinsicSVGElements = 'svg'>({
element,
groupIds = [],
groups = [],
getAttributes,
getInnerText,
...passThroughProps
Expand All @@ -43,13 +42,13 @@ export function SVGElement<T extends keyof JSXIntrinsicSVGElements = 'svg'>({
const attributes = useJSXTransformAttributes(element);
const customAttributes = useCustomAttributes({
element,
groupIds,
groups,
getAttributes,
style: attributes.style ?? {},
});
const tagAttr = { ...attributes, ...customAttributes, ...passThroughProps } as any;
const innerHTML = useInnerHTML({ element, groupIds, getInnerText });
const childrenGroupIds = getChildrenGroupIds({ element, groupIds });
const innerHTML = useInnerHTML({ element, groups, getInnerText });
const childrenGroupIds = getChildrenGroupIds({ element, groups });

return (
<Tag {...tagAttr}>
Expand All @@ -59,7 +58,7 @@ export function SVGElement<T extends keyof JSXIntrinsicSVGElements = 'svg'>({
<SVGElement
key={id.toString()}
element={child as SVGSVGElement}
groupIds={childrenGroupIds}
groups={childrenGroupIds}
getAttributes={getAttributes}
getInnerText={getInnerText}
/>
Expand Down
20 changes: 10 additions & 10 deletions packages/common-ui-web/src/components/DynamicSVG/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@ import { SVGProps } from 'react';
*/
export interface DynamicSVGCustomizationFunctions {
/**
* A callback used to customize SVG tags in a DynamicSVG component based on the HTMLElement itself, or the IDs of the
* groups this element is part of.
* A callback used to customize SVG tags in a DynamicSVG component based on the HTMLElement itself, or the Elements
* of the groups this element is part of.
*
* @param element The element to apply the custom attributes to.
* @param groupIds The IDs of the SVG group elements this element is part of (the IDs are in order).
* @param groups SVG group elements this element is part of (the elements are in order).
* @return This callback should return a set of custom HTML attributes to pass to the element or `null` for no
* attributes.
*/
getAttributes?: (element: SVGElement, groupIds: string[]) => SVGProps<SVGElement>;
getAttributes?: (element: SVGElement, groups: SVGGElement[]) => SVGProps<SVGElement>;
/**
* A callback used to customize the inner text of SVG tags in a DynamicSVG component based on the HTMLElement itself,
* or the IDs of the groups this element is part of.
* or the Elements of the groups this element is part of.
*
* @param element The element to set the innerText.
* @param groupIds The IDs of the SVG group elements this element is part of (the IDs are in order).
* @param groups SVG group elements this element is part of (the elements are in order).
* @return This callback should return a string to use for the innerText of the element or `null` for no innerText.
*/
getInnerText?: (element: SVGSVGElement, groupIds: string[]) => string | null;
getInnerText?: (element: SVGElement, groups: SVGGElement[]) => string | null;
}

/**
Expand All @@ -34,11 +34,11 @@ export interface SVGElementCustomProps {
/**
* The HTMLElement representing the SVG tag that the SVGElement is supposed to display.
*/
element: SVGSVGElement;
element: SVGElement;
/**
* The IDs of the SVG groups this element is part of (in order).
* The Elements of the SVG groups this element is part of (in order).
*
* @default []
*/
groupIds?: string[];
groups?: SVGGElement[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ import { DynamicSVGCustomizationFunctions, SVGElementCustomProps } from './types
*/
export function useCustomAttributes({
element,
groupIds,
groups,
getAttributes,
style,
}: Pick<DynamicSVGCustomizationFunctions, 'getAttributes'> &
Required<SVGElementCustomProps> & { style: CSSProperties }): SVGProps<SVGElement> | null {
return useMemo(() => {
const elementTag = element.tagName;
if (['svg', 'g'].includes(elementTag)) {
if (elementTag === 'svg') {
return { pointerEvents: 'box-none' };
}
if (!getAttributes) return { style };
const attributes = getAttributes(element, groupIds);
if (!getAttributes) {
return { style };
}
const attributes = getAttributes(element, groups);
if (elementTag === 'g') {
attributes.pointerEvents = 'box-none';
}
return { ...attributes, style: { ...attributes?.style, ...style } };
}, [element, groupIds, getAttributes]);
}, [element, groups, getAttributes]);
}
Loading