Skip to content

Commit

Permalink
Added thumbnail feature (#809)
Browse files Browse the repository at this point in the history
  • Loading branch information
dlymonkai authored Jul 15, 2024
1 parent 4793cc6 commit 7c6ab33
Show file tree
Hide file tree
Showing 19 changed files with 124 additions and 6 deletions.
1 change: 1 addition & 0 deletions apps/demo-app/src/local-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"tasks": ["damage_detection", "wheel_analysis"]
},
"apiDomain": "api.preview.monk.ai/v1",
"thumbnailDomain": "europe-west1-monk-preview-321715.cloudfunctions.net/image_resize",
"startTasksOnComplete": true,
"showCloseButton": false,
"enforceOrientation": "landscape",
Expand Down
1 change: 1 addition & 0 deletions apps/drive-app/src/local-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"tasks": ["damage_detection", "wheel_analysis"]
},
"apiDomain": "api.preview.monk.ai/v1",
"thumbnailDomain": "europe-west1-monk-preview-321715.cloudfunctions.net/image_resize",
"startTasksOnComplete": true,
"showCloseButton": false,
"enforceOrientation": "landscape",
Expand Down
1 change: 1 addition & 0 deletions apps/lux-demo-app/src/local-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"tasks": ["damage_detection", "wheel_analysis"]
},
"apiDomain": "api.preview.monk.ai/v1",
"thumbnailDomain": "europe-west1-monk-preview-321715.cloudfunctions.net/image_resize",
"startTasksOnComplete": true,
"showCloseButton": false,
"enforceOrientation": "landscape",
Expand Down
1 change: 1 addition & 0 deletions apps/renault-demo-app/src/local-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"tasks": ["damage_detection", "wheel_analysis"]
},
"apiDomain": "api.preview.monk.ai/v1",
"thumbnailDomain": "europe-west1-monk-preview-321715.cloudfunctions.net/image_resize",
"startTasksOnComplete": true,
"showCloseButton": false,
"enforceOrientation": "landscape",
Expand Down
5 changes: 5 additions & 0 deletions documentation/src/utils/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ export const LiveConfigSchema = z
allowVehicleTypeSelection: z.boolean(),
fetchFromSearchParams: z.boolean(),
apiDomain: z.enum(['api.monk.ai/v1', 'api.preview.monk.ai/v1', 'api.staging.monk.ai/v1']),
thumbnailDomain: z.enum([
'europe-west1-monk-staging-321715.cloudfunctions.net/image_resize',
'europe-west1-monk-preview-321715.cloudfunctions.net/image_resize',
'europe-west1-monk-prod.cloudfunctions.net/image_resize',
]),
requiredApiPermissions: z.array(z.nativeEnum(MonkApiPermission)).optional(),
palette: MonkPaletteSchema.partial().optional(),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { i18nWrap, useI18nSync } from '@monkvision/common';
import { i18nWrap, useI18nSync, useThumbnail } from '@monkvision/common';
import { useMemo, useState } from 'react';
import { Image, ImageType } from '@monkvision/types';
import { InspectionGalleryItem, InspectionGalleryProps, NavigateToCaptureReason } from './types';
Expand Down Expand Up @@ -44,6 +44,7 @@ export const InspectionGallery = i18nWrap((props: InspectionGalleryProps) => {
captureMode: props.captureMode,
isFilterActive: currentFilter !== null,
});
const { getThumbnailUrl } = useThumbnail(props.thumbnailDomain);

const handleItemClick = (item: InspectionGalleryItem) => {
if (item.isAddDamage && props.captureMode) {
Expand Down Expand Up @@ -113,6 +114,7 @@ export const InspectionGallery = i18nWrap((props: InspectionGalleryProps) => {
<div style={itemStyle} key={getItemKey(item)}>
<InspectionGalleryItemCard
item={item}
getThumbnailUrl={getThumbnailUrl}
captureMode={props.captureMode}
onClick={() => handleItemClick(item)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { SightOverlay } from '../../SightOverlay';

export function InspectionGalleryItemCard({
item,
getThumbnailUrl,
captureMode,
onClick,
}: InspectionGalleryItemCardProps) {
Expand All @@ -23,7 +24,7 @@ export function InspectionGalleryItemCard({
statusIcon,
sightOverlay,
addDamageIcon,
} = useInspectionGalleryItemCardStyles({ item, captureMode, status });
} = useInspectionGalleryItemCardStyles({ item, captureMode, status, getThumbnailUrl });
const statusIconName = useInspectionGalleryItemStatusIconName({ item, captureMode });
const label = useInspectionGalleryItemLabel(item);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { changeAlpha, useMonkTheme, useObjectTranslation, useSightLabel } from '@monkvision/common';
import { labels, sights } from '@monkvision/sights';
import { ImageStatus, InteractiveStatus } from '@monkvision/types';
import { ImageStatus, InteractiveStatus, Image } from '@monkvision/types';
import { useTranslation } from 'react-i18next';
import { styles } from './InspectionGalleryItemCard.styles';
import { InspectionGalleryItem } from '../types';
Expand All @@ -10,6 +10,7 @@ import { IconName } from '../../../icons';
export interface InspectionGalleryItemCardProps {
item: InspectionGalleryItem;
captureMode: boolean;
getThumbnailUrl: (image: Image) => string;
onClick?: () => void;
}

Expand Down Expand Up @@ -55,12 +56,14 @@ export function useInspectionGalleryItemStatusIconName({
export interface UseInspectionGalleryItemCardStylesParams {
item: InspectionGalleryItem;
status: InteractiveStatus;
getThumbnailUrl: (image: Image) => string;
captureMode: boolean;
}

export function useInspectionGalleryItemCardStyles({
item,
status,
getThumbnailUrl,
captureMode,
}: UseInspectionGalleryItemCardStylesParams) {
const { palette } = useMonkTheme();
Expand Down Expand Up @@ -93,6 +96,13 @@ export function useInspectionGalleryItemCardStyles({
}
}

let backgroundImage = 'none';
if (!item.isAddDamage && item.isTaken) {
backgroundImage = item.image.path.startsWith('blob')
? `url(${item.image.path})`
: `url(${getThumbnailUrl(item.image)})`;
}

return {
cardStyle: {
...styles['card'],
Expand All @@ -103,7 +113,7 @@ export function useInspectionGalleryItemCardStyles({
previewStyle: {
...styles['preview'],
backgroundColor: colors.previewBackground,
backgroundImage: !item.isAddDamage && item.isTaken ? `url(${item.image.path})` : 'none',
backgroundImage,
},
previewOverlayStyle: {
...styles['previewOverlay'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export type InspectionGalleryProps = {
* The config used to communicate with the API.
*/
apiConfig: MonkApiConfig;
/**
* The API domain used to communicate with the resize micro service
*/
thumbnailDomain: string;
/**
* The language used by the InspectionGallery component.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { changeAlpha, useMonkTheme } from '@monkvision/common';
import { Icon, SightOverlay } from '../../../../src';
import { InspectionGalleryItemCard } from '../../../../src/components/InspectionGallery/InspectionGalleryItemCard';
import {
InspectionGalleryItemCardProps,
InspectionGalleryItemCardProps, useInspectionGalleryItemCardStyles,
useInspectionGalleryItemLabel,
useInspectionGalleryItemStatusIconName,
} from '../../../../src/components/InspectionGallery/InspectionGalleryItemCard/hooks';
Expand Down Expand Up @@ -49,6 +49,7 @@ function createProps(): InspectionGalleryItemCardProps {
isTaken: true,
image: { path: 'test-image-path', status: ImageStatus.SUCCESS } as unknown as Image,
},
getThumbnailUrl: jest.fn(() => 'test-thumbnail-url'),
captureMode: true,
onClick: jest.fn(),
};
Expand Down Expand Up @@ -78,7 +79,7 @@ describe('InspectionGalleryItemCard component', () => {

const preview = screen.getByTestId(CARD_PREVIEW_TEST_ID);
expect(preview).toHaveStyle({
backgroundImage: `url(${(props.item as { image: Image }).image.path})`,
backgroundImage: `url(${props.getThumbnailUrl})`,
});

unmount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function createProps(): InspectionGalleryProps {
enableCompliance: true,
complianceIssues: [ComplianceIssue.INTERIOR_NOT_SUPPORTED],
apiConfig: { apiDomain: 'test-api-domain', authToken: 'test-auth-token' },
thumbnailDomain: 'test-thumbnail-domain',
refreshIntervalMs: 1234,
captureMode: true,
};
Expand Down
11 changes: 11 additions & 0 deletions packages/common/README/HOOKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,17 @@ function TestComponent() {
```
Custom hook used to get the label of a sight with the currently selected language.

### useThumbnail
```tsx
import { useThumbnail } from '@monkvision/common';

function TestComponent() {
const { getThumbnailUrl } = useThumbnail(thumbnailDomain);
console.log(getThumbnailUrl(image));
}
```
Custom hook used to generates a thumbnail URL from a full resolution picture.

### useWindowDimensions
```tsx
import { useWindowDimensions } from '@monkvision/common';
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './useInterval';
export * from './useAsyncInterval';
export * from './useObjectMemo';
export * from './usePreventExit';
export * from './useThumbnail';
39 changes: 39 additions & 0 deletions packages/common/src/hooks/useThumbnail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Image, THUMBNAIL_RESIZE_RATIO } from '@monkvision/types';
import { useCallback } from 'react';
import { useObjectMemo } from './useObjectMemo';

/**
* The result of the useThumbnail. It contains a function which takes a Image object and return the
* thumbnail url.
*/
export interface ThumbnailResult {
/**
* Function that generates the thumbnail image URL.
*/
getThumbnailUrl: (image: Image) => string;
}

function getResizedDimension(originalDimension: number): number {
return Math.round(originalDimension * THUMBNAIL_RESIZE_RATIO);
}

/**
* Custom hook used to get a function getThumbnailUrl that generates a thumbnail URL.
*/
export function useThumbnail(thumbnailDomain: string): ThumbnailResult {
const getThumbnailUrl = useCallback(
(image: Image) => {
const baseThumbnailUrl = `https://${thumbnailDomain}${
thumbnailDomain.endsWith('/') ? '' : '/'
}`;
const imageUrlParam = `image_url=${encodeURIComponent(image.path)}`;
const widthUrlParam = `width=${getResizedDimension(image.width)}`;
const heightUrlParam = `height=${getResizedDimension(image.height)}`;

return `${baseThumbnailUrl}?${imageUrlParam}&${widthUrlParam}&${heightUrlParam}`;
},
[thumbnailDomain],
);

return useObjectMemo({ getThumbnailUrl });
}
26 changes: 26 additions & 0 deletions packages/common/test/hooks/useThumbnail.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { renderHook } from '@testing-library/react-hooks';
import { Image, THUMBNAIL_RESIZE_RATIO } from '@monkvision/types';
import { useThumbnail } from '../../src';

const thumbnailDomainMock = 'test-thumbnail-domain';

describe('useThumbnail hook', () => {
it('should return a getThumbnailUrl function', () => {
const { result, unmount } = renderHook(useThumbnail);

expect(typeof result.current.getThumbnailUrl).toBe('function');
unmount();
});

it('should return a formated url', () => {
const { result, unmount } = renderHook(useThumbnail, { initialProps: thumbnailDomainMock });

const imageMock = { path: 'test-path', width: 20, height: 40 } as Image;
expect(result.current.getThumbnailUrl(imageMock)).toEqual(
`https://${thumbnailDomainMock}/?image_url=${imageMock.path}&width=${
imageMock.width * THUMBNAIL_RESIZE_RATIO
}&height=${imageMock.height * THUMBNAIL_RESIZE_RATIO}`,
);
unmount();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface PhotoCaptureProps
| 'enableAddDamage'
| 'sightGuidelines'
| 'enableSightGuidelines'
| 'thumbnailDomain'
>,
Partial<CompressionOptions>,
Partial<ComplianceOptions> {
Expand Down Expand Up @@ -130,6 +131,7 @@ export function PhotoCapture({
lang,
enforceOrientation,
validateButtonLabel,
thumbnailDomain,
...initialCameraConfig
}: PhotoCaptureProps) {
useI18nSync(lang);
Expand Down Expand Up @@ -282,6 +284,7 @@ export function PhotoCapture({
<InspectionGallery
inspectionId={inspectionId}
apiConfig={apiConfig}
thumbnailDomain={thumbnailDomain}
captureMode={true}
lang={lang}
showBackButton={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function createProps(): PhotoCaptureProps {
sights: [sights['test-sight-1'], sights['test-sight-2'], sights['test-sight-3']],
inspectionId: 'test-inspection-test',
apiConfig: { apiDomain: 'test-api-domain-test', authToken: 'test-auth-token-test' },
thumbnailDomain: 'test-thumbnail-domain',
additionalTasks: [TaskName.DASHBOARD_OCR],
tasksBySight: { 'test-sight-1': [TaskName.IMAGE_EDITING] },
startTasksOnComplete: [TaskName.COMPLIANCES],
Expand Down
4 changes: 4 additions & 0 deletions packages/types/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ export type CaptureAppConfig = CameraConfig &
* The API domain used to communicate with the API.
*/
apiDomain: string;
/**
* The API domain used to communicate with the resize micro service
*/
thumbnailDomain: string;
/**
* Required API permissions to use the app.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/state/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { MonkEntity, MonkEntityType } from './entity';
import { VehiclePart } from './part';
import { TranslationObject } from '../i18n';

/**
* The resize ratio which will be apply to the image.
*/
export const THUMBNAIL_RESIZE_RATIO = 0.1;

/**
* Additional data that can be added to an image when it has been uploaded.
*/
Expand Down

0 comments on commit 7c6ab33

Please sign in to comment.