Skip to content

Commit bc487d0

Browse files
committed
feat: meilleur partage avec le web
1 parent 56212b3 commit bc487d0

5 files changed

Lines changed: 130 additions & 79 deletions

File tree

GaleriePhotos/ClientApp/components/image-view/top-actions.tsx

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ActionMenu, ActionMenuItem } from "../action-menu";
1717
import { useFavoritesStore } from "@/stores/favorites";
1818
import { PhotoMoveModal } from "../modals/photo-move-modal";
1919
import { useAlert } from "../alert";
20+
import { useShare } from "../modals/photo-share";
2021

2122
interface TopActionsProps {
2223
onDetailsToggle: () => void;
@@ -27,28 +28,6 @@ interface TopActionsProps {
2728
store: PhotoContainerStore;
2829
}
2930

30-
function getMimeTypeFromExtension(extension: string) {
31-
switch (extension.toLowerCase()) {
32-
case "jpg":
33-
case "jpeg":
34-
return "image/jpeg";
35-
case "png":
36-
return "image/png";
37-
case "gif":
38-
return "image/gif";
39-
case "mp4":
40-
return "video/mp4";
41-
case "mov":
42-
return "video/quicktime";
43-
case "webm":
44-
return "video/webm";
45-
case "webp":
46-
return "image/webp";
47-
default:
48-
return "application/octet-stream";
49-
}
50-
}
51-
5231
function TopActions({
5332
onDetailsToggle,
5433
onFacesToggle,
@@ -78,42 +57,13 @@ function TopActions({
7857
closeMenu();
7958
store.setCover?.(photo.id);
8059
}, [closeMenu, store, photo.id]);
81-
const [canShare, setCanShare] = useState(false);
82-
useEffect(() => {
83-
async function checkSharingAvailability() {
84-
const available = await Sharing.isAvailableAsync();
85-
setCanShare(available);
86-
}
87-
checkSharingAvailability();
88-
}, []);
60+
61+
const [share, canShare] = useShare();
8962

9063
const handleSystemShareClick = useCallback(async () => {
9164
closeMenu();
92-
if (!canShare) return;
93-
94-
// Construction de l'URL distante de l'image (originale) via store
95-
const imageUrl = photosStore.getImage(photo.publicId);
96-
97-
// Télécharger l'image dans un fichier temporaire (obligatoire pour expo-sharing sur mobile)
98-
if (Platform.OS !== "web") {
99-
const fileName = `shared-${photo.publicId}.jpg`;
100-
const baseDir = Paths.cache;
101-
const tmpPath = `${baseDir}${fileName}`;
102-
const download = await File.downloadFileAsync(
103-
imageUrl,
104-
new Directory(tmpPath)
105-
);
106-
await Sharing.shareAsync(download.uri, {
107-
dialogTitle: "Partager l'image",
108-
mimeType: download.type,
109-
});
110-
} else {
111-
await Sharing.shareAsync(imageUrl, {
112-
dialogTitle: "Partager l'image",
113-
mimeType: getMimeTypeFromExtension(imageUrl.split(".").pop() || "jpg"),
114-
});
115-
}
116-
}, [canShare, closeMenu, photo.publicId, photosStore]);
65+
await share(photo);
66+
}, [closeMenu, photo, share]);
11767

11868
const handleRotate = useCallback(
11969
async (angle: number) => {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export function getMimeTypeFromExtension(extension: string) {
2+
switch (extension.toLowerCase()) {
3+
case "jpg":
4+
case "jpeg":
5+
return "image/jpeg";
6+
case "png":
7+
return "image/png";
8+
case "gif":
9+
return "image/gif";
10+
case "mp4":
11+
return "video/mp4";
12+
case "mov":
13+
return "video/quicktime";
14+
case "webm":
15+
return "video/webm";
16+
case "webp":
17+
return "image/webp";
18+
default:
19+
return "application/octet-stream";
20+
}
21+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useCallback, useState, useEffect } from "react";
2+
import * as Sharing from "expo-sharing";
3+
import { usePhotosStore } from "@/stores/photos";
4+
import { Photo } from "@/services/views";
5+
import { Directory, File, Paths } from "expo-file-system";
6+
7+
export function useShare() {
8+
const [canShare, setCanShare] = useState(false);
9+
const photosStore = usePhotosStore();
10+
11+
useEffect(() => {
12+
async function checkSharingAvailability() {
13+
const available = await Sharing.isAvailableAsync();
14+
setCanShare(available);
15+
}
16+
checkSharingAvailability();
17+
}, []);
18+
19+
const share = useCallback(
20+
async (photo: Photo) => {
21+
if (!canShare) return;
22+
23+
// Construction de l'URL distante de l'image (originale) via store
24+
const imageUrl = photosStore.getImage(photo.publicId);
25+
26+
// Télécharger l'image dans un fichier temporaire (obligatoire pour expo-sharing sur mobile)
27+
const fileName = `shared-${photo.publicId}.jpg`;
28+
const baseDir = Paths.cache;
29+
const tmpPath = `${baseDir}${fileName}`;
30+
const download = await File.downloadFileAsync(
31+
imageUrl,
32+
new Directory(tmpPath)
33+
);
34+
await Sharing.shareAsync(download.uri, {
35+
dialogTitle: "Partager l'image",
36+
mimeType: download.type,
37+
});
38+
},
39+
[canShare, photosStore]
40+
);
41+
return [share, canShare] as const;
42+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useCallback } from "react";
2+
import { usePhotosStore } from "@/stores/photos";
3+
import { Photo } from "@/services/views";
4+
5+
export function useShare() {
6+
const canShare = window.navigator.share !== undefined;
7+
const photosStore = usePhotosStore();
8+
9+
const share = useCallback(
10+
async (photo: Photo) => {
11+
if (!window.navigator.canShare) return;
12+
13+
// Construction de l'URL distante de l'image (originale) via store
14+
const imageUrl = photosStore.getImage(photo.publicId);
15+
16+
const result = await fetch(imageUrl);
17+
if (result.ok) {
18+
const blob = await result.blob();
19+
const file = new File([blob], `shared-${photo.publicId}.jpg`, {
20+
type: blob.type,
21+
});
22+
if (window.navigator.canShare({ files: [file] })) {
23+
await window.navigator.share({
24+
title: "Partager l'image",
25+
files: [file],
26+
});
27+
} else if (window.navigator.canShare({ url: imageUrl })) {
28+
await window.navigator.share({
29+
title: "Partager l'image",
30+
url: imageUrl,
31+
});
32+
}
33+
}
34+
},
35+
[photosStore]
36+
);
37+
return [share, canShare] as const;
38+
}
Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
{
2-
"iisSettings": {
3-
"windowsAuthentication": false,
4-
"anonymousAuthentication": true,
5-
"iisExpress": {
6-
"applicationUrl": "http://localhost:65029",
7-
"sslPort": 44322
8-
}
2+
"iisSettings": {
3+
"windowsAuthentication": false,
4+
"anonymousAuthentication": true,
5+
"iisExpress": {
6+
"applicationUrl": "http://localhost:65029",
7+
"sslPort": 44322
8+
}
9+
},
10+
"profiles": {
11+
"IIS Express": {
12+
"commandName": "IISExpress",
13+
"launchBrowser": true,
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
}
917
},
10-
"profiles": {
11-
"IIS Express": {
12-
"commandName": "IISExpress",
13-
"launchBrowser": true,
14-
"environmentVariables": {
15-
"ASPNETCORE_ENVIRONMENT": "Development"
16-
}
17-
},
18-
"GaleriePhotos": {
19-
"commandName": "Project",
20-
"environmentVariables": {
21-
"Administrator__Login": "[email protected]",
22-
"ASPNETCORE_ENVIRONMENT": "Development",
23-
"Administrator__Password": "tototo"
24-
},
25-
"applicationUrl": "http://*:6009"
26-
}
18+
"GaleriePhotos": {
19+
"commandName": "Project",
20+
"environmentVariables": {
21+
"Administrator__Login": "[email protected]",
22+
"ASPNETCORE_ENVIRONMENT": "Development",
23+
"Administrator__Password": "tototo"
24+
},
25+
"applicationUrl": "http://*:6009;https://*:7009"
2726
}
27+
}
2828
}

0 commit comments

Comments
 (0)