Skip to content
This repository was archived by the owner on Dec 18, 2024. It is now read-only.

Commit 777f6ad

Browse files
Feat: allow image component to save new images (#556)
* Feat: allow image component to save new images * fix package-lock.json
1 parent 8af3718 commit 777f6ad

File tree

4 files changed

+108
-15
lines changed

4 files changed

+108
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
44

55
## Unreleased
66

7+
### New features
8+
9+
- 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.
10+
711
## 2.6.0 ( November 22, 2021 )
812

913
### New features

src/components/image/index.test.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ const mockThing = SolidFns.addUrl(
3333
mockProperty,
3434
mockUrl
3535
);
36+
const mockThingWithoutPhoto = SolidFns.createThing();
37+
const datasetIri = "https://example.org/dataset/";
38+
const mockDataset = SolidFns.mockSolidDatasetFrom(datasetIri);
3639

3740
const mockObjectUrl = "mock object url";
3841
const mockFile = SolidFns.mockFileFrom(mockUrl);
@@ -259,6 +262,53 @@ describe("Image component", () => {
259262
);
260263
expect(SolidFns.overwriteFile).toHaveBeenCalled();
261264
});
265+
it("Should call saveFileInContainer and update dataset on change if value is not available and saveLocation is passed", async () => {
266+
const saveLocation = "https://example.org/container/";
267+
jest.spyOn(SolidFns, "getUrl").mockImplementationOnce(() => null);
268+
jest
269+
.spyOn(SolidFns, "saveSolidDatasetAt")
270+
.mockResolvedValueOnce(mockDataset as any);
271+
jest
272+
.spyOn(SolidFns, "saveFileInContainer")
273+
.mockResolvedValueOnce(mockFile);
274+
const mockUpdatedObjectUrl = "updated mock object url";
275+
const { getByAltText } = render(
276+
<Image
277+
thing={mockThingWithoutPhoto}
278+
property={mockProperty}
279+
alt={mockAlt}
280+
edit
281+
autosave
282+
inputProps={{ alt: "test-input" }}
283+
saveLocation={saveLocation}
284+
solidDataset={mockDataset}
285+
/>
286+
);
287+
await waitFor(() => {
288+
const input = getByAltText("test-input");
289+
expect(input).not.toBeNull();
290+
});
291+
fireEvent.change(getByAltText("test-input"), {
292+
target: {
293+
files: [mockFile],
294+
},
295+
});
296+
(window.URL.createObjectURL as jest.Mock).mockReturnValueOnce(
297+
mockUpdatedObjectUrl
298+
);
299+
expect(SolidFns.saveFileInContainer).toHaveBeenCalledWith(
300+
saveLocation,
301+
mockFile,
302+
{ fetch: expect.any(Function) }
303+
);
304+
await waitFor(() => {
305+
expect(SolidFns.saveSolidDatasetAt).toHaveBeenCalledWith(
306+
datasetIri,
307+
expect.anything(),
308+
{ fetch: expect.any(Function) }
309+
);
310+
});
311+
});
262312

263313
test.skip("Should not call overwriteFile on change if file size > maxSize", async () => {
264314
const { getByAltText } = render(

src/components/image/index.tsx

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,24 @@
2020
*/
2121

2222
import React, { ReactElement, useState, useEffect, useContext } from "react";
23-
23+
import {
24+
addUrl,
25+
getSourceUrl,
26+
saveFileInContainer,
27+
saveSolidDatasetAt,
28+
setThing,
29+
SolidDataset,
30+
} from "@inrupt/solid-client";
2431
import { overwriteFile, CommonProperties, useProperty } from "../../helpers";
2532

2633
import { SessionContext } from "../../context/sessionContext";
2734
import useFile from "../../hooks/useFile";
2835

2936
export type Props = {
3037
maxSize?: number;
38+
saveLocation?: string;
3139
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
40+
solidDataset?: SolidDataset;
3241
errorComponent?: React.ComponentType<{ error: Error }>;
3342
loadingComponent?: React.ComponentType | null;
3443
} & CommonProperties &
@@ -50,6 +59,8 @@ export function Image({
5059
inputProps,
5160
errorComponent: ErrorComponent,
5261
loadingComponent: LoadingComponent,
62+
saveLocation,
63+
solidDataset,
5364
...imgOptions
5465
}: Props): ReactElement | null {
5566
const { fetch } = useContext(SessionContext);
@@ -81,7 +92,6 @@ export function Image({
8192
}, [error, onError, ErrorComponent]);
8293

8394
const [imgObjectUrl, setImgObjectUrl] = useState<string | undefined>();
84-
8595
const {
8696
data,
8797
error: imgError,
@@ -104,19 +114,40 @@ export function Image({
104114

105115
const handleChange = async (input: EventTarget & HTMLInputElement) => {
106116
const fileList = input.files;
107-
if (autosave && fileList && fileList.length > 0 && value) {
108-
const newObjectUrl = await overwriteFile(
109-
value as string,
110-
fileList[0],
111-
input,
112-
fetch,
113-
maxSize,
114-
onSave,
115-
onError
116-
);
117-
118-
if (newObjectUrl) {
119-
setImgObjectUrl(newObjectUrl);
117+
if (autosave && fileList && fileList.length > 0) {
118+
if (value) {
119+
const newObjectUrl = await overwriteFile(
120+
value as string,
121+
fileList[0],
122+
input,
123+
fetch,
124+
maxSize,
125+
onSave,
126+
onError
127+
);
128+
129+
if (newObjectUrl) {
130+
setImgObjectUrl(newObjectUrl);
131+
}
132+
} else if (!value && saveLocation) {
133+
const savedFile = await saveFileInContainer(saveLocation, fileList[0], {
134+
fetch,
135+
});
136+
const savedFileUrl = getSourceUrl(savedFile);
137+
if (savedFileUrl && propThing && propProperty && solidDataset) {
138+
setImgObjectUrl(savedFileUrl);
139+
try {
140+
const updatedThing = addUrl(propThing, propProperty, savedFileUrl);
141+
const updatedDataset = setThing(solidDataset, updatedThing);
142+
const datasetSourceUrl = getSourceUrl(solidDataset);
143+
if (!datasetSourceUrl) return;
144+
await saveSolidDatasetAt(datasetSourceUrl, updatedDataset, {
145+
fetch,
146+
});
147+
} catch (e) {
148+
setError(e as Error);
149+
}
150+
}
120151
}
121152
}
122153
};

stories/components/image.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ export default {
7070
description: `A loading component to show while fetching the dataset. If \`null\` the default loading message won't be displayed`,
7171
control: { type: null },
7272
},
73+
saveLocation: {
74+
description: `A URL for a container where a new image is to be saved in case a value for an image property is not found`,
75+
control: { type: null },
76+
},
77+
solidDataset: {
78+
description: `A Solid Dataset where a new image should be added`,
79+
control: { type: null },
80+
},
7381
},
7482
};
7583
interface IWithBasicData {

0 commit comments

Comments
 (0)