Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Dec 18, 2024. It is now read-only.
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html

### New features

- The `Image` component now takes an optional prop `allowDelete`, which renders a default delete button that will remove the value from the dataset. It is also possible to pass a `deleteComponent` to render a custom delete button in place of the default.

- The `Image` component now allows for a new image to be saved even if the property is not found in the dataset, by passing a `solidDataset` where the Thing should be set after updating, and a `saveLocation` where the image file should be stored.

### Bugfixes
Expand Down
46,025 changes: 23,021 additions & 23,004 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,8 @@
"react-table": "^7.6.3",
"stream": "0.0.2",
"swr": "^0.5.7"
},
"resolutions": {
"postcss": "8.4.5"
}
}
26 changes: 26 additions & 0 deletions src/components/image/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ exports[`Image component Image snapshots matches snapshot with standard props 1`
</DocumentFragment>
`;

exports[`Image component Image snapshots renders a a custom delete button if passed and allowDelete is true 1`] = `
<DocumentFragment>
<span>
Error: No value found for property.
</span>
<button
type="button"
>
Custom Delete Component
</button>
</DocumentFragment>
`;

exports[`Image component Image snapshots renders a a default delete button if allowDelete is true 1`] = `
<DocumentFragment>
<span>
Error: No value found for property.
</span>
<button
type="button"
>
Delete
</button>
</DocumentFragment>
`;

exports[`Image component Image snapshots renders an error message if an errorComponent is provided 1`] = `
<DocumentFragment>
<span
Expand Down
62 changes: 61 additions & 1 deletion src/components/image/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
import React from "react";
import { render, waitFor, fireEvent } from "@testing-library/react";
import * as SolidFns from "@inrupt/solid-client";
import type {
SolidDataset,
WithChangeLog,
WithServerResourceInfo,
} from "@inrupt/solid-client";
import { Image } from ".";
import * as helpers from "../../helpers";

Expand Down Expand Up @@ -156,6 +161,33 @@ describe("Image component", () => {

expect(asFragment()).toMatchSnapshot();
});
it("renders a a default delete button if allowDelete is true", () => {
const emptyThing = SolidFns.createThing();
const { asFragment } = render(
<Image
thing={emptyThing}
allowDelete
property="https://example.com/url"
/>
);
expect(asFragment()).toMatchSnapshot();
});
it("renders a a custom delete button if passed and allowDelete is true", () => {
const emptyThing = SolidFns.createThing();
const { asFragment } = render(
<Image
thing={emptyThing}
allowDelete
property="https://example.com/url"
deleteComponent={({ onClick }) => (
<button type="button" onClick={onClick}>
Custom Delete Component
</button>
)}
/>
);
expect(asFragment()).toMatchSnapshot();
});
});

describe("Image functional tests", () => {
Expand Down Expand Up @@ -267,7 +299,9 @@ describe("Image component", () => {
jest.spyOn(SolidFns, "getUrl").mockImplementationOnce(() => null);
jest
.spyOn(SolidFns, "saveSolidDatasetAt")
.mockResolvedValueOnce(mockDataset as any);
.mockResolvedValueOnce(
mockDataset as SolidDataset & WithServerResourceInfo & WithChangeLog
);
jest
.spyOn(SolidFns, "saveFileInContainer")
.mockResolvedValueOnce(mockFile);
Expand Down Expand Up @@ -309,6 +343,32 @@ describe("Image component", () => {
);
});
});
it("Should call saveSolidDatasetAt when clicking delete button", async () => {
jest
.spyOn(SolidFns, "saveSolidDatasetAt")
.mockResolvedValue(
mockDataset as SolidDataset & WithServerResourceInfo & WithChangeLog
);
const { getByAltText, getByText } = render(
<Image
thing={mockThing}
solidDataset={mockDataset}
edit
autosave
allowDelete
property={mockProperty}
alt={mockAlt}
/>
);
await waitFor(() =>
expect(getByAltText(mockAlt).getAttribute("src")).toBe(mockObjectUrl)
);
const deleteButton = getByText("Delete");
fireEvent.click(deleteButton);
await waitFor(() => {
expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalled();
});
});

test.skip("Should not call overwriteFile on change if file size > maxSize", async () => {
const { getByAltText } = render(
Expand Down
35 changes: 35 additions & 0 deletions src/components/image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import React, { ReactElement, useState, useEffect, useContext } from "react";
import {
addUrl,
getSourceUrl,
removeUrl,
saveFileInContainer,
saveSolidDatasetAt,
setThing,
Expand All @@ -36,10 +37,12 @@ import useFile from "../../hooks/useFile";
export type Props = {
maxSize?: number;
saveLocation?: string;
allowDelete?: boolean;
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
solidDataset?: SolidDataset;
errorComponent?: React.ComponentType<{ error: Error }>;
loadingComponent?: React.ComponentType | null;
deleteComponent?: React.ComponentType<{ onClick: () => void }> | null;
} & CommonProperties &
React.ImgHTMLAttributes<HTMLImageElement>;

Expand All @@ -52,13 +55,15 @@ export function Image({
properties: propProperties,
edit,
autosave,
allowDelete,
onSave,
onError,
maxSize,
alt,
inputProps,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent,
deleteComponent: DeleteComponent,
saveLocation,
solidDataset,
...imgOptions
Expand Down Expand Up @@ -112,6 +117,28 @@ export function Image({
}
}, [data, fetchingFileInProgress, imgError]);

const handleDelete = async () => {
if (
!propThing ||
!solidDataset ||
!propProperty ||
typeof value !== "string" ||
!autosave
)
return;
try {
const updatedThing = removeUrl(propThing, propProperty, value);
const updatedDataset = setThing(solidDataset, updatedThing);
const datasetSourceUrl = getSourceUrl(solidDataset);
if (!datasetSourceUrl) return;
await saveSolidDatasetAt(datasetSourceUrl, updatedDataset, {
fetch,
});
Comment on lines +134 to +136
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this only happen when autosave is set ? Or is it by design that when deleting an image you're automatically deleting it from the remote storage ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I am not sure but I have added a check for autosave since it is what we do with other operations as well.

} catch (e) {
setError(e as Error);
}
};

const handleChange = async (input: EventTarget & HTMLInputElement) => {
const fileList = input.files;
if (autosave && fileList && fileList.length > 0) {
Expand Down Expand Up @@ -187,6 +214,14 @@ export function Image({
onChange={(e) => handleChange(e.target)}
/>
)}
{allowDelete &&
(DeleteComponent ? (
<DeleteComponent onClick={handleDelete} />
) : (
<button type="button" onClick={handleDelete}>
Delete
</button>
))}
</>
);
}
40 changes: 39 additions & 1 deletion stories/components/image.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@
*/

import React, { ReactElement } from "react";
import { addUrl, createThing } from "@inrupt/solid-client";
import {
addUrl,
createSolidDataset,
createThing,
setThing,
} from "@inrupt/solid-client";
import { Image } from "../../src/components/image";
import CombinedDataProvider from "../../src/context/combinedDataContext";
import config from "../config";
Expand Down Expand Up @@ -84,12 +89,14 @@ interface IWithBasicData {
property: string;
properties: Array<string>;
edit: boolean;
allowDelete: boolean;
maxSize: number;
}
export function BasicExample({
property,
properties,
edit,
allowDelete,
maxSize,
}: IWithBasicData): ReactElement {
const thing = addUrl(createThing(), property, `${host}/example.jpg`);
Expand All @@ -101,13 +108,15 @@ export function BasicExample({
properties={properties}
edit={edit}
maxSize={maxSize}
allowDelete={allowDelete}
/>
);
}

BasicExample.args = {
property: "http://schema.org/contentUrl",
edit: false,
allowDelete: false,
maxSize: 100000000,
};

Expand All @@ -119,6 +128,7 @@ export function PropertyArrayExample({
properties,
edit,
maxSize,
allowDelete,
}: IWithBasicData): ReactElement {
const thing = addUrl(createThing(), property, `${host}/example.jpg`);

Expand All @@ -128,6 +138,7 @@ export function PropertyArrayExample({
properties={properties}
edit={edit}
maxSize={maxSize}
allowDelete={allowDelete}
/>
);
}
Expand Down Expand Up @@ -194,3 +205,30 @@ export function ErrorComponent(): ReactElement {
ErrorComponent.parameters = {
actions: { disable: true },
};

export function DeleteComponent(): ReactElement {
const property = "http://schema.org/contentUrl";
const thing = addUrl(createThing(), property, `${host}/example.jpg`);
const dataset = setThing(createSolidDataset(), thing);

return (
<Image
thing={thing}
solidDataset={dataset}
edit
autosave
saveLocation={`${host}/`}
property={property}
allowDelete
deleteComponent={({ onClick }) => (
<button type="button" onClick={onClick}>
Custom Delete Component
</button>
)}
/>
);
}

DeleteComponent.parameters = {
actions: { disable: true },
};