ChatPlus ist eine progressive Web-App, die mit React, NodeJS, Firebase und anderen Diensten entwickelt wurde.
Sie können in Echtzeit mit all Ihren Freunden sprechen 🗣️✨🧑🤝🧑❤️
Sie können Ihre Freunde anrufen und Video- und Audioanrufe mit ihnen führen 🎥🔉🤩
Senden Sie Ihren Freunden Bilder und auch Audionachrichten und Sie verfügen über eine KI, die Ihre Sprache in Text umwandelt, unabhängig davon, ob Sie Französisch, Englisch oder Spanisch sprechen 🤖✨
Die Web-App kann auf allen Geräten installiert werden und Benachrichtigungen empfangen ⬇️🔔🎉
Ich würde mich sehr über Ihre Unterstützung freuen. Hinterlassen Sie uns einen Stern im Github-Repository und teilen Sie es mit Ihren Freunden ⭐✨
Die vollständige Installations- und Bereitstellungsdokumentation finden Sie in diesem Github-Repository: https://github.com/aladinyo/ChatPlus
ChatPlus ist eine der besten Anwendungen, die ich erstellt habe. Es fällt mit seiner lebendigen Benutzeroberfläche und den zahlreichen Messaging- und Anruffunktionen auf, die alle im Web implementiert sind. Es bietet ein plattformübergreifendes Erlebnis, da sein Frontend als PWA-Anwendung konzipiert ist, die überall installiert werden kann und sich wie eine eigenständige App verhält. Mit Funktionen wie Push-Benachrichtigungen ist ChatPlus die Definition einer leichtgewichtigen App, die mit Webtechnologien alle Funktionen einer mobilen App bieten kann.
Unsere App folgt der MVC-Softwarearchitektur. MVC (Model-View-Controller) ist ein Muster im Softwaredesign, das häufig zur Implementierung von Benutzeroberflächen, Daten und Steuerlogik verwendet wird. Es betont eine Trennung zwischen der Geschäftslogik der Software und der Anzeige. Diese „Trennung der Belange“ sorgt für eine bessere Arbeitsteilung und verbesserte Wartung.
Es besteht aus mehreren React-Schnittstellenkomponenten:
MaterialUI: Material UI ist eine Open-Source-React-Komponentenbibliothek, die Googles Material Design implementiert. Sie ist umfassend und kann sofort in der Produktion verwendet werden. Wir werden sie für Symbolschaltflächen und einige Elemente in unserer Benutzeroberfläche wie einen Audio-Schieberegler verwenden.
LoginView: Eine einfache Anmeldeansicht, die es dem Benutzer ermöglicht, seinen Benutzernamen und sein Passwort einzugeben oder sich mit Google anzumelden.
SidebarViews : mehrere Ansichtskomponenten wie „sidebar__header“, „sidebar__search“, „Sidebar-Menü“ und „SidebarChats“, bestehend aus einer Kopfzeile für Benutzerinformationen, einer Suchleiste zum Suchen von Benutzern, einem Sidebar-Menü zum Navigieren zwischen Ihren Chats, Ihren Gruppen und Benutzern und einer SidebarChats-Komponente zum Anzeigen aller Ihrer letzten Chats, denen Sie Nachrichten gesendet haben. Sie zeigt den Benutzernamen und das Foto sowie die letzte Nachricht.
ChatViews: Es besteht aus den folgenden vielen Komponenten:
1 .'chat__header' enthält die Informationen des Benutzers, mit dem Sie sprechen, seinen Online-Status, sein Profilbild und zeigt Schaltflächen für Audio- und Videoanrufe an. Außerdem wird angezeigt, ob der Benutzer gerade tippt.
2 .'chat__body--container' enthält die Informationen zu unseren Nachrichten an die anderen Benutzer. Es verfügt über eine Nachrichtenkomponente, die Textnachrichten, Bilder mit ihren Nachrichten sowie Audionachrichten mit ihren Informationen wie Audiodauer und Angabe, ob das Audio abgespielt wurde, anzeigt. Am Ende dieser Komponente haben wir das Element „gesehen“, das anzeigt, ob Nachrichten gesehen wurden.
3. „ AudioPlayer“: eine React-Komponente, die uns Audio mit einem Schieberegler zur Navigation anzeigen kann und die vollständige und aktuelle Zeit des Audios anzeigt. Diese Ansichtskomponente wird im „chat__body-- container“ geladen.
4. „ChatFooter“: enthält ein Eingabefeld zum Eingeben einer Nachricht, eine Schaltfläche zum Senden einer Nachricht beim Tippen in das Eingabefeld, andernfalls können Sie mit der Schaltfläche Audio aufnehmen und eine Schaltfläche zum Importieren von Bildern und Dateien.
5. „MediaPreview“: eine React-Komponente, die eine Vorschau der Bilder oder Dateien ermöglicht, die wir zum Senden in unserem Chat ausgewählt haben. Sie werden in einem Karussell angezeigt und der Benutzer kann die Bilder oder Dateien verschieben und für jedes eine bestimmte Nachricht eingeben.
6. „Bildvorschau“: Wenn wir Bilder in unserem Chat senden, zeigt diese Komponente die Bilder im Vollbildmodus mit einer flüssigen Animation an. Die Komponente wird nach dem Klicken auf ein Bild bereitgestellt.
scalePage: eine Ansichtsfunktion, die die Größe unserer Web-App bei der Anzeige auf großen Bildschirmen wie Full-HD-Bildschirmen und 4K-Bildschirmen vergrößert.
CallViews: eine Reihe von Reaktionskomponenten, die alle Call-View-Elemente enthalten. Sie können über den gesamten Bildschirm gezogen werden und bestehen aus:
1 „Tasten“: eine Anruftaste in roter Ausführung und eine grüne Videoanruftaste.
2 „AudioCallView“: eine Ansichtskomponente, die es ermöglicht, eingehende Audioanrufe anzunehmen, den Anruf mit einem Timer anzuzeigen und den Anruf abzubrechen.
3 „StartVideoCallView“: eine Ansichtskomponente, die durch Verbindung mit der lokalen MediaAPI ein Video von uns anzeigt und darauf wartet, dass der andere Benutzer den Anruf annimmt, oder eine Schaltfläche anzeigt, mit der wir einen eingehenden Videoanruf annehmen können.
4 „VideoCallView“: eine Ansichtskomponente, die ein Video von uns und dem anderen Benutzer anzeigt. Sie ermöglicht das Wechseln der Kamera, das Deaktivieren von Kamera und Audio und kann auch in den Vollbildmodus wechseln.
RouteViews: React-Komponenten, die alle unsere Ansichten enthalten, um eine lokale Komponentennavigation zu erstellen. Wir haben „VideoCallRoute“, „SideBarMenuRoute“ und „ChatsRoute“.
Clientseitige Modelle stellen die Logik dar, die unserem Frontend die Interaktion mit Datenbanken und mehreren lokalen und serverseitigen APIs ermöglicht. Sie bestehen aus:
Es besteht aus React-Komponenten, die unsere Ansichten mit ihren spezifischen Modellen verknüpfen:
Nicht alle Funktionen werden auf dem Frontend ausgeführt, da die von uns verwendeten SDKs einige serverseitige Funktionen erfordern. Dazu gehören:
Unsere Webanwendung verwendet Firestore zum Speichern unserer Datenbank, bei der es sich um eine Firebase NoSql-Datenbank handelt. Wir speichern Benutzerinformationen, eine Liste aller Nachrichten, eine Liste der Chats und auch Chats in Räumen. Dies sind die in unserer Datenbank gespeicherten Daten:
In den nächsten Kapiteln werde ich eine kurze Erklärung und Tutorials zu bestimmten Funktionen in ChatPlus geben, ich werde Ihnen den JS-Code zeigen und den dahinter stehenden Algorithmus erklären und außerdem das richtige Integrationstool bereitstellen, um Ihren Code mit der Datenbank zu verknüpfen.
Der Onlinestatus der Benutzer wurde mithilfe der Firebase-Datenbankkonnektivitätsfunktion implementiert, indem eine Verbindung zu „.info/connected“ im Frontend hergestellt und sowohl Firestore als auch Datenbank entsprechend aktualisiert wurden:
var disconnectRef; function setOnlineStatus(uid) { try { console.log("setting up online status"); const isOfflineForDatabase = { state: 'offline', last_changed: createTimestamp2, id: uid, }; const isOnlineForDatabase = { state: 'online', last_changed: createTimestamp2, id: uid }; const userStatusFirestoreRef = db.collection("users").doc(uid); const userStatusDatabaseRef = db2.ref('/status/' + uid); // Firestore uses a different server timestamp value, so we'll // create two more constants for Firestore state. const isOfflineForFirestore = { state: 'offline', last_changed: createTimestamp(), }; const isOnlineForFirestore = { state: 'online', last_changed: createTimestamp(), }; disconnectRef = db2.ref('.info/connected').on('value', function (snapshot) { console.log("listening to database connected info") if (snapshot.val() === false) { // Instead of simply returning, we'll also set Firestore's state // to 'offline'. This ensures that our Firestore cache is aware // of the switch to 'offline.' userStatusFirestoreRef.set(isOfflineForFirestore, { merge: true }); return; }; userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function () { userStatusDatabaseRef.set(isOnlineForDatabase); // We'll also add Firestore set here for when we come online. userStatusFirestoreRef.set(isOnlineForFirestore, { merge: true }); }); }); } catch (error) { console.log("error setting onlins status: ", error); } };
Und auf unserem Backend haben wir auch einen Listener eingerichtet, der auf Änderungen in unserer Datenbank wartet und den Firestore entsprechend aktualisiert. Diese Funktion kann uns auch den Onlinestatus des Benutzers in Echtzeit mitteilen, sodass wir darin auch andere Funktionen ausführen können:
async function handleOnlineStatus(data, event) { try { console.log("setting online status with event: ", event); // Get the data written to Realtime Database const eventStatus = data.val(); // Then use other event data to create a reference to the // corresponding Firestore document. const userStatusFirestoreRef = db.doc(`users/${eventStatus.id}`); // It is likely that the Realtime Database change that triggered // this event has already been overwritten by a fast change in // online / offline status, so we'll re-read the current data // and compare the timestamps. const statusSnapshot = await data.ref.once('value'); const status = statusSnapshot.val(); // If the current timestamp for this data is newer than // the data that triggered this event, we exit this function. if (eventStatus.state === "online") { console.log("event status: ", eventStatus) console.log("status: ", status) } if (status.last_changed <= eventStatus.last_changed) { // Otherwise, we convert the last_changed field to a Date eventStatus.last_changed = new Date(eventStatus.last_changed); //handle the call delete handleCallDelete(eventStatus); // ... and write it to Firestore. await userStatusFirestoreRef.set(eventStatus, { merge: true }); console.log("user: " + eventStatus.id + " online status was succesfully updated with data: " + eventStatus.state); } else { console.log("next status timestamp is newer for user: ", eventStatus.id); } } catch (error) { console.log("handle online status crashed with error :", error) } }
Benachrichtigungen sind eine tolle Funktion und werden mithilfe von Firebase Messaging implementiert. Wenn der Browser des Benutzers auf unserem Frontend Benachrichtigungen unterstützt, konfigurieren wir es und rufen das Firebase-Messaging-Token des Benutzers ab:
const configureNotif = (docID) => { messaging.getToken().then((token) => { console.log(token); db.collection("users").doc(docID).set({ token: token }, { merge: true }) }).catch(e => { console.log(e.message); db.collection("users").doc(docID).set({ token: "" }, { merge: true }); }); }
Immer wenn ein Benutzer eine Nachricht sendet, fügen wir unserer Datenbank eine Benachrichtigung hinzu:
db.collection("notifications").add({ userID: user.uid, title: user.displayName, body: inputText, photoURL: user.photoURL, token: token, });
und auf unserem Backend hören wir uns die Benachrichtigungssammlung an und verwenden die Firebase-Nachrichtenübermittlung, um sie an den Benutzer zu senden
let listening = false; db.collection("notifications").onSnapshot(snap => { if (!listening) { console.log("listening for notifications..."); listening = true; } const docs = snap.docChanges(); if (docs.length > 0) { docs.forEach(async change => { if (change.type === "added") { const data = change.doc.data(); if (data) { const message = { data: data, token: data.token }; await db.collection("notifications").doc(change.doc.id).delete(); try { const response = await messaging.send(message); console.log("notification successfully sent :", data); } catch (error) { console.log("error sending notification ", error); }; }; }; }); }; });
Unsere Webanwendung ermöglicht es Benutzern, einander Audionachrichten zu senden. Eine ihrer Funktionen besteht darin, diese Audionachrichten in Text umzuwandeln, z. B. in Englisch, Französisch und Spanisch. Diese Funktion wurde mit der Funktion „Sprache in Text“ von Google Cloud implementiert. Unser Backend hört sich neue Transkripte an, die zu Firestore hinzugefügt werden, transkribiert sie und schreibt sie dann in die Datenbank:
db.collection("transcripts").onSnapshot(snap => { const docs = snap.docChanges(); if (docs.length > 0) { docs.forEach(async change => { if (change.type === "added") { const data = change.doc.data(); if (data) { db.collection("transcripts").doc(change.doc.id).delete(); try { const text = await textToAudio(data.audioName, data.short, data.supportWebM); const roomRef = db.collection("rooms").doc(data.roomID).collection("messages").doc(data.messageID); db.runTransaction(async transaction => { const roomDoc = await transaction.get(roomRef); if (roomDoc.exists && !roomDoc.data()?.delete) { transaction.update(roomRef, { transcript: text }); console.log("transcript added with text: ", text); return; } else { console.log("room is deleted"); return; } }) db.collection("rooms").doc(data.roomID).collection("messages").doc(data.messageID).update({ transcript: text }); } catch (error) { console.log("error transcripting audio: ", error); }; }; }; }); }; });
Offensichtlich haben Sie die Text-zu-Audio-Funktion im Blick und fragen sich, wie ich sie gemacht habe. Keine Sorge, ich habe Sie verstanden:
// Imports the Google Cloud client library const speech = require('@google-cloud/speech').v1p1beta1; const { gcsUriLink } = require("./configKeys") // Creates a client const client = new speech.SpeechClient({ keyFilename: "./audio_transcript.json" }); async function textToAudio(audioName, isShort) { // The path to the remote LINEAR16 file const gcsUri = gcsUriLink + "/audios/" + audioName; // The audio file's encoding, sample rate in hertz, and BCP-47 language code const audio = { uri: gcsUri, }; const config = { encoding: "MP3", sampleRateHertz: 48000, languageCode: 'en-US', alternativeLanguageCodes: ['es-ES', 'fr-FR'] }; console.log("audio config: ", config); const request = { audio: audio, config: config, }; // Detects speech in the audio file if (isShort) { const [response] = await client.recognize(request); return response.results.map(result => result.alternatives[0].transcript).join('\n'); } const [operation] = await client.longRunningRecognize(request); const [response] = await operation.promise().catch(e => console.log("response promise error: ", e)); return response.results.map(result => result.alternatives[0].transcript).join('\n'); }; module.exports = textToAudio;
Unsere Webanwendung verwendet die Daily API, um Web-RTC-Verbindungen in Echtzeit zu implementieren. Sie ermöglicht Benutzern, Videoanrufe miteinander zu tätigen. Daher richten wir zunächst einen Backend-Anrufserver ein, der über viele API-Einstiegspunkte zum Erstellen und Löschen von Räumen in Daily verfügt:
const app = express(); app.use(cors()); app.use(express.json()); app.delete("/delete-call", async (req, res) => { console.log("delete call data: ", req.body); deleteCallFromUser(req.body.id1); deleteCallFromUser(req.body.id2); try { fetch("https://api.daily.co/v1/rooms/" + req.body.roomName, { headers: { Authorization: `Bearer ${dailyApiKey}`, "Content-Type": "application/json" }, method: "DELETE" }); } catch(e) { console.log("error deleting room for call delete!!"); console.log(e); } res.status(200).send("delete-call success !!"); }); app.post("/create-room/:roomName", async (req, res) => { var room = await fetch("https://api.daily.co/v1/rooms/", { headers: { Authorization: `Bearer ${dailyApiKey}`, "Content-Type": "application/json" }, method: "POST", body: JSON.stringify({ name: req.params.roomName }) }); room = await room.json(); console.log(room); res.json(room); }); app.delete("/delete-room/:roomName", async (req, res) => { var deleteResponse = await fetch("https://api.daily.co/v1/rooms/" + req.params.roomName, { headers: { Authorization: `Bearer ${dailyApiKey}`, "Content-Type": "application/json" }, method: "DELETE" }); deleteResponse = await deleteResponse.json(); console.log(deleteResponse); res.json(deleteResponse); }) app.listen(process.env.PORT || 7000, () => { console.log("call server is running"); }); const deleteCallFromUser = userID => db.collection("users").doc(userID).collection("call").doc("call").delete();
und auf unserem Frontend haben wir mehrere Funktionen zum Aufrufen dieser API:
import { callAPI as api } from "../../configKeys"; //const api = "http://localhost:7000" export async function createRoom(roomName) { var room = await fetch(`${api}/create-room/${roomName}`, { method: "POST", }); room = await room.json(); console.log(room); return room; } export async function deleteRoom(roomName) { var deletedRoom = await fetch(`${api}/delete-room/${roomName}`, { method: "DELETE", }); deletedRoom = await deletedRoom.json(); console.log(deletedRoom); console.log("deleted"); }; export function deleteCall() { window.callDelete && fetch(`${api}/delete-call`, { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify(window.callDelete) }); };
Großartig, jetzt ist es nur noch an der Zeit, Anrufräume zu erstellen und das tägliche JS SDK zu verwenden, um eine Verbindung zu diesen Räumen herzustellen und Daten von ihnen zu senden und zu empfangen:
export default async function startVideoCall(dispatch, receiverQuery, userQuery, id, otherID, userName, otherUserName, sendNotif, userPhoto, otherPhoto, audio) { var room = null; const call = new DailyIframe.createCallObject(); const roomName = nanoid(); window.callDelete = { id1: id, id2: otherID, roomName } dispatch({ type: "set_other_user_name", otherUserName }); console.log("audio: ", audio); if (audio) { dispatch({ type: "set_other_user_photo", photo: otherPhoto }); dispatch({ type: "set_call_type", callType: "audio" }); } else { dispatch({ type: "set_other_user_photo", photo: null }); dispatch({ type: "set_call_type", callType: "video" }); } dispatch({ type: "set_caller", caller: true }); dispatch({ type: "set_call", call }); dispatch({ type: "set_call_state", callState: "state_creating" }); try { room = await createRoom(roomName); console.log("created room: ", room); dispatch({ type: "set_call_room", callRoom: room }); } catch (error) { room = null; console.log('Error creating room', error); await call.destroy(); dispatch({ type: "set_call_room", callRoom: null }); dispatch({ type: "set_call", call: null }); dispatch({ type: "set_call_state", callState: "state_idle" }); window.callDelete = null; //destroy the call object; }; if (room) { dispatch({ type: "set_call_state", callState: "state_joining" }); dispatch({ type: "set_call_queries", callQueries: { userQuery, receiverQuery } }); try { await db.runTransaction(async transaction => { console.log("runing transaction"); var userData = (await transaction.get(receiverQuery)).data(); //console.log("user data: ", userData); if (!userData || !userData?.callerID || userData?.otherUserLeft) { console.log("runing set"); transaction.set(receiverQuery, { room, callType: audio ? "audio" : "video", isCaller: false, otherUserLeft: false, callerID: id, otherID, otherUserName: userName, otherUserRatio: window.screen.width / window.screen.height, photo: audio ? userPhoto : "" }); transaction.set(userQuery, { room, callType: audio ? "audio" : "video", isCaller: true, otherUserLeft: false, otherUserJoined: false, callerID: id, otherID }); } else { console.log('transaction failed'); throw userData; } }); if (sendNotif) { sendNotif(); const notifTimeout = setInterval(() => { sendNotif(); }, 1500); dispatch({ type: "set_notif_tiemout", notifTimeout }); } call.join({ url: room.url, videoSource: !audio }); } catch (userData) { //delete the room we made deleteRoom(roomName); await call.destroy(); if (userData.otherID === id) { console.log("you and the other user are calling each other at the same time"); joinCall(dispatch, receiverQuery, userQuery, userData.room, userName, audio ? userPhoto : "", userData.callType); } else { console.log("other user already in a call"); dispatch({ type: "set_call_room", callRoom: null }); dispatch({ type: "set_call", call: null }); dispatch({ type: "set_call_state", callState: "state_otherUser_calling" }); } }; }; };
OtherUserQuery und UserQuery sind lediglich Firebase-Firestore-Dokumentpfade. Der Rest der App verfügt nun über Ansichtskomponenten, die auf die Statusänderungen reagieren, die durch die obige Funktion ausgelöst werden, und unsere Call-UI-Elemente werden entsprechend angezeigt.
Mit der nächsten Funktion können Sie das Anrufelement über die gesamte Seite ziehen:
export function dragElement(elmnt, page) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0, top, left, prevTop = 0, prevLeft = 0, x, y, maxTop, maxLeft; const widthRatio = page.width / window.innerWidth; const heightRatio = page.height / window.innerHeight; //clear element's mouse listeners closeDragElement(); // setthe listener elmnt.addEventListener("mousedown", dragMouseDown); elmnt.addEventListener("touchstart", dragMouseDown, { passive: false }); function dragMouseDown(e) { e = e || window.event; // get the mouse cursor position at startup: if (e.type === "touchstart") { if (typeof(e.target.className) === "string") { if (!e.target.className.includes("btn")) { e.preventDefault(); } } else if (!typeof(e.target.className) === "function") { e.stopPropagation(); } pos3 = e.touches[0].clientX * widthRatio; pos4 = e.touches[0].clientY * heightRatio; } else { e.preventDefault(); pos3 = e.clientX * widthRatio; pos4 = e.clientY * heightRatio; }; maxTop = elmnt.offsetParent.offsetHeight - elmnt.offsetHeight; maxLeft = elmnt.offsetParent.offsetWidth - elmnt.offsetWidth; document.addEventListener("mouseup", closeDragElement); document.addEventListener("touchend", closeDragElement, { passive: false }); // call a function whenever the cursor moves: document.addEventListener("mousemove", elementDrag); document.addEventListener("touchmove", elementDrag, { passive: false }); } function elementDrag(e) { e = e || window.event; e.preventDefault(); // calculate the new cursor position: if (e.type === "touchmove") { x = e.touches[0].clientX * widthRatio; y = e.touches[0].clientY * heightRatio; } else { e.preventDefault(); x = e.clientX * widthRatio; y = e.clientY * heightRatio; }; pos1 = pos3 - x; pos2 = pos4 - y; pos3 = x pos4 = y; // set the element's new position: top = elmnt.offsetTop - pos2; left = elmnt.offsetLeft - pos1; //prevent the element from overflowing the viewport if (top >= 0 && top <= maxTop) { elmnt.style.top = top + "px"; } else if ((top > maxTop && pos4 < prevTop) || (top < 0 && pos4 > prevTop)) { elmnt.style.top = top + "px"; }; if (left >= 0 && left <= maxLeft) { elmnt.style.left = left + "px"; } else if ((left > maxLeft && pos3 < prevLeft) || (left < 0 && pos3 > prevLeft)) { elmnt.style.left = left + "px"; }; prevTop = y; prevLeft = x; } function closeDragElement() { // stop moving when mouse button is released: document.removeEventListener("mouseup", closeDragElement); document.removeEventListener("touchend", closeDragElement); document.removeEventListener("mousemove", elementDrag); document.removeEventListener("touchmove", elementDrag); }; return function() { elmnt.removeEventListener("mousedown", dragMouseDown); elmnt.removeEventListener("touchstart", dragMouseDown); closeDragElement(); }; };
Sie können Bilder per Drag & Drop in Ihren Chat ziehen und sie an andere Benutzer senden. Diese Funktion wird durch Ausführen des Folgenden ermöglicht:
useEffect(() => { const dropArea = document.querySelector(".chat"); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropArea.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }, false); }); ['dragenter', 'dragover'].forEach(eventName => { dropArea.addEventListener(eventName, () => setShowDrag(true), false) }); ['dragleave', 'drop'].forEach(eventName => { dropArea.addEventListener(eventName, () => setShowDrag(false), false) }); dropArea.addEventListener('drop', e => { if (window.navigator.onLine) { if (e.dataTransfer?.files) { const dropedFile = e.dataTransfer.files; console.log("dropped file: ", dropedFile); const { imageFiles, imagesSrc } = mediaIndexer(dropedFile); setSRC(prevImages => [...prevImages, ...imagesSrc]); setImage(prevFiles => [...prevFiles, ...imageFiles]); setIsMedia("images_dropped"); }; }; }, false); }, []);
Der mediaIndexer ist eine einfache Funktion, die den Bild-Blob indiziert, den wir ihr bereitstellen:
function mediaIndexer(files) { const imagesSrc = []; const filesArray = Array.from(files); filesArray.forEach((file, index) => { imagesSrc[index] = URL.createObjectURL(file); }); return { imagesSrc, imageFiles: filesArray }; }