Skip to content

Commit

Permalink
feat: use Thumbnails from PackedItem in ItemCard
Browse files Browse the repository at this point in the history
* fix: update ThumbnailUploader to fix flickering warning tooltip
* feat: use ThumbnailUploader in ThumbnailSetting and remove ThumbnailWithControls
* feat: update ThumbnailCrop's delete button
* fix(test): include MUI Tooltip in optimizeDeps of vite.config
  • Loading branch information
ReidyT committed Sep 26, 2024
1 parent 3e519f5 commit 8cedc55
Show file tree
Hide file tree
Showing 18 changed files with 162 additions and 440 deletions.
8 changes: 7 additions & 1 deletion cypress/e2e/item/authorization/itemLogin/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ItemLoginSchema, ItemLoginSchemaType, PackedItem } from '@graasp/sdk';
import {
ItemLoginSchema,
ItemLoginSchemaStatus,
ItemLoginSchemaType,
PackedItem,
} from '@graasp/sdk';

import { v4 } from 'uuid';

Expand All @@ -13,5 +18,6 @@ export const addItemLoginSchema = (
id: v4(),
createdAt: '2021-08-11T12:56:36.834Z',
updatedAt: '2021-08-11T12:56:36.834Z',
status: ItemLoginSchemaStatus.Active,
},
});
3 changes: 1 addition & 2 deletions cypress/e2e/item/move/move.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,7 @@ describe('Move Items', () => {
moveItems({ toItemPath: '' });

cy.wait('@moveItems').then(({ request: { url, body } }) => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(body.parentId).to.be.undefined;
expect(body.parentId).equal(undefined);
folders.forEach((item) => {
expect(url).to.contain(item.id);
});
Expand Down
23 changes: 12 additions & 11 deletions cypress/e2e/item/settings/thumbnail.cy.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { PackedFolderItemFactory } from '@graasp/sdk';
import { PackedFolderItemFactory, PackedItem } from '@graasp/sdk';

import { buildItemSettingsPath } from '../../../../src/config/paths';
import {
CROP_MODAL_CONFIRM_BUTTON_ID,
ITEM_THUMBNAIL_DELETE_BTN_ID,
THUMBNAIL_SETTING_UPLOAD_INPUT_ID,
IMAGE_THUMBNAIL_UPLOADER,
REMOVE_THUMBNAIL_BUTTON,
buildDataCyWrapper,
} from '../../../../src/config/selectors';
import {
ITEM_THUMBNAIL_LINK,
Expand All @@ -14,9 +15,9 @@ import { FILE_LOADING_PAUSE } from '../../../support/constants';

describe('Item Thumbnail', () => {
const item = PackedFolderItemFactory();
const itemWithThumbnails = {
const itemWithThumbnails: PackedItem = {
...PackedFolderItemFactory(),
thumnails: ITEM_THUMBNAIL_LINK,
thumbnails: { small: ITEM_THUMBNAIL_LINK, medium: ITEM_THUMBNAIL_LINK },
settings: { hasThumbnail: true },
};
beforeEach(() => {
Expand All @@ -30,7 +31,7 @@ describe('Item Thumbnail', () => {

// change item thumbnail
// target visually hidden input
cy.get(`#${THUMBNAIL_SETTING_UPLOAD_INPUT_ID}`).selectFile(
cy.get(buildDataCyWrapper(IMAGE_THUMBNAIL_UPLOADER)).selectFile(
THUMBNAIL_MEDIUM_PATH,
// use force because the input is visually hidden
{ force: true },
Expand All @@ -45,10 +46,10 @@ describe('Item Thumbnail', () => {
it('Delete thumbnail button should exist for item with thumbnail', () => {
cy.visit(buildItemSettingsPath(itemWithThumbnails.id));

cy.get(`#${ITEM_THUMBNAIL_DELETE_BTN_ID}`)
cy.get(buildDataCyWrapper(REMOVE_THUMBNAIL_BUTTON))
.invoke('show')
.should('be.visible');
cy.get(`#${ITEM_THUMBNAIL_DELETE_BTN_ID}`).click();
.should('be.visible')
.click();
cy.wait(`@deleteItemThumbnail`).then(({ request: { url } }) => {
expect(url).to.contain(itemWithThumbnails.id);
});
Expand All @@ -58,12 +59,12 @@ describe('Item Thumbnail', () => {
cy.visit(buildItemSettingsPath(item.id));
Cypress.on('fail', (error) => {
expect(error.message).to.include(
`Expected to find element: \`#${ITEM_THUMBNAIL_DELETE_BTN_ID}\`, but never found it.`,
`Expected to find element: \`${buildDataCyWrapper(REMOVE_THUMBNAIL_BUTTON)}\`, but never found it.`,
);
return false; // Prevent Cypress from failing the test
});
// this will throw an error as ITEM_THUMBNAIL_DELETE_BTN_ID not exist that going to be catches at cypress.on fail
cy.get(`#${ITEM_THUMBNAIL_DELETE_BTN_ID}`).invoke('show');
cy.get(`${buildDataCyWrapper(REMOVE_THUMBNAIL_BUTTON)}`).invoke('show');
});
});
});
3 changes: 1 addition & 2 deletions cypress/e2e/item/view/viewFolder.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,11 @@ describe('view Folder as admin', () => {
cy.get(`#${ITEM_SEARCH_INPUT_ID}`).type(searchText);

cy.wait('@getChildren').then(({ request: { query } }) => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(
(query.keywords as unknown as string[]).every((k) =>
searchText.includes(k),
),
).to.be.true;
).equal(true);
});

cy.get(`#${buildItemCard(child1.id)}`).should('be.visible');
Expand Down
8 changes: 4 additions & 4 deletions cypress/e2e/item/view/viewThumbnails.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PackedFolderItemFactory } from '@graasp/sdk';
import { PackedFolderItemFactory, PackedItem } from '@graasp/sdk';

import { HOME_PATH } from '../../../../src/config/paths';
import {
Expand All @@ -11,14 +11,14 @@ import { ITEM_THUMBNAIL_LINK } from '../../../fixtures/thumbnails/links';
const ITEM_WITHOUT_THUMBNAIL = PackedFolderItemFactory({
name: 'own_item_name1',
});
const ITEM_WITH_THUMBNAIL = {
const ITEM_WITH_THUMBNAIL: PackedItem = {
...PackedFolderItemFactory({
name: 'own_item_name2',
settings: {
hasThumbnail: true,
},
}),
thumbnails: ITEM_THUMBNAIL_LINK,
thumbnails: { small: ITEM_THUMBNAIL_LINK, medium: ITEM_THUMBNAIL_LINK },
};

describe('View Thumbnails', () => {
Expand All @@ -34,7 +34,7 @@ describe('View Thumbnails', () => {

cy.get(`#${buildItemCard(ITEM_WITH_THUMBNAIL.id)} img`)
.should('have.attr', 'src')
.and('contain', ITEM_WITH_THUMBNAIL.thumbnails);
.and('contain', ITEM_WITH_THUMBNAIL.thumbnails.medium);
});

it(`display member avatar`, () => {
Expand Down
3 changes: 2 additions & 1 deletion cypress/support/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ import {
RecycledItemData,
S3FileItemType,
ShortLink,
ThumbnailsBySize,
} from '@graasp/sdk';

export type ItemForTest = DiscriminatedItem & {
categories?: ItemCategory[];
thumbnails?: string;
thumbnails?: ThumbnailsBySize;
tags?: ItemTag[];
itemLoginSchema?: ItemLoginSchema;
readFilepath?: string;
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
"@emotion/react": "11.13.3",
"@emotion/styled": "11.13.0",
"@graasp/chatbox": "3.3.0",
"@graasp/map": "1.18.0",
"@graasp/map": "1.19.0",
"@graasp/query-client": "3.24.0",
"@graasp/sdk": "4.29.1",
"@graasp/sdk": "4.31.0",
"@graasp/stylis-plugin-rtl": "2.2.0",
"@graasp/translations": "1.38.0",
"@graasp/ui": "5.2.0",
Expand Down
1 change: 0 additions & 1 deletion src/components/item/MapView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ const MapView = ({
useAddressFromGeolocation={hooks.useAddressFromGeolocation}
useSuggestionsForAddress={hooks.useSuggestionsForAddress}
useItemsInMap={hooks.useItemsInMap}
useItemThumbnailUrl={hooks.useItemThumbnailUrl}
viewItem={viewItem}
viewItemInBuilder={viewItemInBuilder}
currentMember={currentMember}
Expand Down
4 changes: 2 additions & 2 deletions src/components/item/publish/PublicationThumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useState } from 'react';
import WarningIcon from '@mui/icons-material/Warning';
import { Tooltip } from '@mui/material';

import { DiscriminatedItem } from '@graasp/sdk';
import { PackedItem } from '@graasp/sdk';

import { title } from 'process';

Expand All @@ -19,7 +19,7 @@ const THUMBNAIL_SIZE = 150;
const SYNC_STATUS_KEY = 'PublicationThumbnail';

type Props = {
item: DiscriminatedItem;
item: PackedItem;
thumbnailSize?: number;
fullWidth?: boolean;
};
Expand Down
163 changes: 35 additions & 128 deletions src/components/item/settings/ThumbnailSetting.tsx
Original file line number Diff line number Diff line change
@@ -1,146 +1,53 @@
import { FormEventHandler, useRef, useState } from 'react';
import { useState } from 'react';

import { Dialog, Stack, styled } from '@mui/material';
import { Stack, Typography } from '@mui/material';

import {
DiscriminatedItem,
ItemType,
ThumbnailSize,
getLinkThumbnailUrl,
} from '@graasp/sdk';
import { PackedItem } from '@graasp/sdk';

import { useUploadWithProgress } from '@/components/hooks/uploadWithProgress';
import { THUMBNAIL_SETTING_UPLOAD_INPUT_ID } from '@/config/selectors';
import ThumbnailUploader, {
EventChanges,
} from '@/components/thumbnails/ThumbnailUploader';

import { useBuilderTranslation } from '../../../config/i18n';
import { hooks, mutations } from '../../../config/queryClient';
import { BUILDER } from '../../../langs/constants';
import CropModal, { MODAL_TITLE_ARIA_LABEL_ID } from '../../common/CropModal';
import ThumbnailWithControls from './ThumbnailWithControls';

const VisuallyHiddenInput = styled('input')({
clip: 'rect(0 0 0 0)',
clipPath: 'inset(50%)',
height: 1,
overflow: 'hidden',
position: 'absolute',
bottom: 0,
left: 0,
whiteSpace: 'nowrap',
width: 1,
});
const THUMBNAIL_SIZE = 120;
const SYNC_STATUS_KEY = 'ThumbnailSetting';

type Props = { item: DiscriminatedItem };
type Props = { item: PackedItem };

const ThumbnailSetting = ({ item }: Props): JSX.Element | null => {
const inputRef = useRef<HTMLInputElement>(null);
const [showCropModal, setShowCropModal] = useState(false);
const [fileSource, setFileSource] = useState<string>();
const { t: translateBuilder } = useBuilderTranslation();
const { mutateAsync: uploadItemThumbnail } =
mutations.useUploadItemThumbnail();
const { update, close: closeNotification } = useUploadWithProgress();
const { mutate: deleteThumbnail } = mutations.useDeleteItemThumbnail();
const { id: itemId } = item;
const { data: thumbnailUrl, isLoading } = hooks.useItemThumbnailUrl({
id: itemId,
size: ThumbnailSize.Medium,
});

const onSelectFile: FormEventHandler<HTMLInputElement> = (e) => {
const t = e.target as HTMLInputElement;
if (t.files && t.files?.length > 0) {
const reader = new FileReader();
reader.addEventListener('load', () =>
setFileSource(reader.result as string),
);
reader.readAsDataURL(t.files?.[0]);
setShowCropModal(true);
}
};

const onClose = () => {
setShowCropModal(false);
if (inputRef.current) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
inputRef.current.value = null;
}
};

const onConfirmCrop = (croppedImage: Blob | null) => {
onClose();

if (!croppedImage) {
return console.error('croppedImage is not defined');
const { t } = useBuilderTranslation();
const [hasThumbnail, setHasThumbnail] = useState(Boolean(item.thumbnails));

const handleChange = (e: EventChanges) => {
switch (e) {
case EventChanges.ON_UPLOADING:
case EventChanges.ON_HAS_THUMBNAIL:
setHasThumbnail(true);
break;
case EventChanges.ON_NO_THUMBNAIL:
setHasThumbnail(false);
break;
default:
// nothing to do
}
// submit cropped image
try {
// remove waiting files
uploadItemThumbnail({
// type: croppedImage.type,
file: croppedImage,
id: item.id,
onUploadProgress: update,
}).then(() => {
closeNotification();
});
} catch (error) {
console.error(error);
}

return true;
};

const onDelete = () => {
deleteThumbnail(itemId);
};

const onEdit = () => {
inputRef.current?.click();
};

const alt = translateBuilder(BUILDER.THUMBNAIL_SETTING_MY_THUMBNAIL_ALT);

let imgUrl = thumbnailUrl;
if (!imgUrl && item.type === ItemType.LINK) {
imgUrl = getLinkThumbnailUrl(item.extra);
}

return (
<>
<Stack spacing={2} mb={3} alignItems="center">
<ThumbnailWithControls
item={item}
alt={alt}
url={imgUrl}
isLoading={isLoading}
onDelete={onDelete}
onEdit={onEdit}
hasThumbnail={item.settings?.hasThumbnail}
/>
<VisuallyHiddenInput
id={THUMBNAIL_SETTING_UPLOAD_INPUT_ID}
type="file"
accept="image/*"
onChange={onSelectFile}
ref={inputRef}
/>
</Stack>
{fileSource && (
<Dialog
open={showCropModal}
onClose={onClose}
aria-labelledby={MODAL_TITLE_ARIA_LABEL_ID}
>
<CropModal
onClose={onClose}
src={fileSource}
onConfirm={onConfirmCrop}
/>
</Dialog>
<Stack alignItems="center" spacing={2}>
<ThumbnailUploader
item={item}
thumbnailSize={THUMBNAIL_SIZE}
syncStatusKey={SYNC_STATUS_KEY}
onChange={handleChange}
/>
{!hasThumbnail && (
<Typography variant="caption">
{t(BUILDER.SETTINGS_THUMBNAIL_SETTINGS_INFORMATIONS)}
</Typography>
)}
</>
</Stack>
);
};

Expand Down
Loading

0 comments on commit 8cedc55

Please sign in to comment.