FS access api & React: duplicate entries - reactjs

I'm trying to build a movie dashboard (something like Plex); the user selects a folder and all the movies he has in the folder show up. The use of the new File System Access API allows me to create file handlers on the fly and be able to display movies using the browser video player.
The problem I'm facing is with duplicated entries, for instance "Ghostbusters" (can't really understand why, but that's the only one causing the issue)
This is the basic implementation of the file system:
try {
const folderHandle = await window.showDirectoryPicker();
const addedFilms = [];
history.push('/list');
// const entries = await folderHandle.values()
const entries = await folderHandle.values();
for await (const entry of entries) {
const movie = await readMoviesonDisk(folderHandle, entry);
console.log(addedFilms);
if (addedFilms.includes(entry.name)) continue;
addedFilms.push(entry.name);
setMovies((movies) => [...movies, movie]);
}
} catch (error) {
alert('Alert from reading files: ' + error);
}
setMovies just sets a Context with a movies array and readMoviesOnDisk is the following:
const readMoviesonDisk = async (folderHandle, entry) => {
if (entry.kind === 'file' && entry.name.endsWith('.mp4')) {
const path = await folderHandle.resolve(entry);
const handle = await folderHandle.getFileHandle(path);
const movie = await getMovie(entry.name);
if (movie) {
return { ...movie.data, file: handle, name: entry.name };
}
const movieData = await searchMovie(entry.name);
if (movieData) {
const actualData = await getMovieDetails(movieData.id);
if (actualData !== undefined) {
await insertMovie(entry.name, actualData, handle);
} else {
await insertMovie(entry.name, actualData, handle);
}
return { ...actualData, file: handle, name: entry.name };
}
return { name: entry.name, file: handle };
}
};
searchMovie and insertMovie only interact with IndexedDB to store movie info for offline use. getMovieDetails does API calls to TMDB to get movie info.
The key I use for displaying the movies is their TMDB id. Ghostbusters' id is "620".
Can anyone help me?

Without additional background it seems impossible to answer this properly. Can you iterate over all files in the folder and just log the names and kinds? This should work and show no duplicate entries.
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}

Related

how to check if nested docs and collection in firebase exist and if don't how to create, in react native

I am new to Firebase and I have to create a chat system. I found that the doc structure should be nested
e.g if a person sends a message, a new doc with its id will be created in the main collection and then a new collection will be added to the doc. now each doc in that nested collection will be considered as a message obj.
a rough sketch of how the new message in the nested document will be added
but the problem is when there is no doc with UI exist or no collection in that doc exist
firestore().collection("chatBox").doc(uid).collection("message").add(
{
text: "this is my first message",
user: {_id:356},
avatar: "link of avatar",
name: "john",
createdAt: new Date().getTime()
}
)
const sendMessage = async (messages = []) => {
const msg = messages[0];
const id = msg.user?._id?.toString();
const collectionRef = firestore().collection(CHATBOX);
const doc = collectionRef.doc(id);
const docExists = await doc.get().then(function (doc) {
return doc.exists;
});
if (docExists) {
const collection = doc.collection(MESSAGES);
const isCollectionEmpty = collection.get().then(col => {
return col.empty;
});
if (isCollectionEmpty) doc.set({id: MESSAGES});//creating new collection
else collection.add({...msg, createdAt: new Date().getTime()});//inserting doc if collection exist
} else {
collectionRef.add(id);// creating doc in main collection
}
};
The ability to create a document only if it does not exist can be done using the following Transaction. Here, the createDocIfNotExist method creates the document with the given data, only if it does not already exist. It returns a Promise<boolean> indicating whether the document was freshly created or not.
async createDocIfNotExist(docRef, initData) {
return docRef
.firestore
.runTransaction((transaction) => {
const docSnap = await transaction.get(docRef);
if (docSnap.exists)
return false; // exists already
transaction.set(docRef, initData);
return true; // was created
});
}
Applying this to your code then gives:
const sendMessage = async (messages = []) => {
const msg = messages[0];
const msgUserId = msg.user!._id!.toString(); // id renamed, consider using senderId/recipientId instead
const chatboxColRef = firestore().collection(CHATBOX); // collectionRef renamed
const userChatboxDocRef = chatboxColRef.doc(msgUserId); // doc renamed
const isNewChatbox = await createDocIfNotExist(
userChatboxDocRef,
{ id: msgUserId }
);
const userChatboxMessagesColRef = userChatboxDocRef.collection(MESSAGES); // collection renamed
return userChatboxMessagesColRef
.add({
...msg,
createdAt: new Date().getTime() // consider using firebase.firestore.FieldValue.serverTimestamp() instead
});
};
This can be further reduced to:
const sendMessage = async (messages = []) => {
const msg = messages[0];
const msgUserId = msg.user!._id!.toString();
const userChatboxDocRef = firestore()
.collection(CHATBOX);
.doc(msgUserId);
await createDocIfNotExist(
userChatboxDocRef,
{ id: msgUserId }
);
return userChatboxDocRef
.collection(MESSAGES)
.add({
...msg,
createdAt: new Date().getTime()
});
};
Note: Avoid using the variable name doc as it is ambiguous and could be an instance of DocumentData, DocumentReference, or DocumentSnapshot (at minimum, use docData, docRef and docSnap/docSnapshot respectively). Similarly, use colRef for a CollectionReference and qSnap/querySnap for QuerySnapshot objects.

Imgly plugin is not exporting captured image

I have an Ionic app built with angular. I have integrated Imgly in my app. But it is not working properly. After editing an image the plugin is not exporting that image. I have looked into many documentation's but haven't found any solution on this.
Method capturing image and opening Imgly plugin
captureImage2() {
if (this.usepicker) {
this.filePickerRef.nativeElement;
// this.filePickerRef.nativeElement.click();
// return;
}
const options: CaptureImageOptions = { limit: 1 };
this.mediaCapture.captureImage(options).then(
async (data: MediaFile[]) => {
const fullPath = data[0].fullPath;
const directoryPath = fullPath.substring(0, fullPath.lastIndexOf("/"));
const fileName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
console.log(
"File Details ======>>>>>>",
fullPath,
directoryPath,
fileName
);
PESDK.openEditor(
async (data1) => {
console.log("REEEEEE =====> ", JSON.stringify(data1));
await this.toProcessImage(data1.image);
const check = await this.displayImage(data1.image);
// console.log(check);
this.onButtonClick(data1.image);
// const dataURL = await this.file.readAsDataURL(data1.image, fileName);
// console.log("DATA URL ======>>>>>>" + dataURL);
data1
.save()
.then(async (sceneAsString) => {
const file = {
dataURI: sceneAsString,
directory: this.dic,
};
const fileResponse = await UriFileUpload(file);
if (fileResponse) {
console.log("image UPLOADED", JSON.stringify(fileResponse));
this.toastSer.success("uploaded");
// this.uploadedList = [...this.uploadedList , fileResponse1];
// console.log(this.uploadedList);
this.updateFiles.next(fileResponse);
} else {
this.toastSer.error("Somthing went wrong!");
}
})
.catch((error) => {
console.error("Save failed", error);
});
},
(error) => console.log(error),
fullPath
);
},
(err: CaptureError) => console.error(err)
);
}
IMG.LY team here!
We would like to provide a correct answer, but keep in mind that for this type of issue, a minimal example project is required in order to test what the different outputs are from the used camera and image picker plugin.
You can find Stack Overflow's suggestions here for a minimal project.
Feel free to submit your further queries to our Support Desk, we're always happy to help.

Saving an ID value from an API to a User with GraphQL

I'm working on a video game website where a user can save a game to a list. How this is supposed to work is when the user clicks "Complete Game", the ID of the game is saved to a state that holds the value. The value is then passed into the mutation, then the mutation runs, saving the ID of the game to the users list of completed games. However, all I'm seeing in the console is this:
"GraphQLError: Variable \"$addGame\" got invalid value { gameId: 740, name: \"Halo: Combat Evolved\",
The above error continues, listing the entirety of the API response, instead of just the gameId.
I was able to successfully add the game to the list in the explorer with the following mutation:
mutation completeGame($addGame: AddNewGame!) {
completeGame(addGame: $addGame) {
_id
completedGameCount
completedGames {
gameId
}
}
}
with the following variable:
{
"addGame": {"gameId": 740}
}
How can I trim down what is being passed into the mutation to just be the gameId?
Below is the entirety of the page, except the return statement at the bottom.
const [selectedGame, setSelectedGame] = useState([]);
const [savedGameIds, setSavedGameIds] = useState(getSavedGameIds());
const [completeGame, { error }] = useMutation(COMPLETE_GAME);
const { id: gameId } = useParams();
useEffect(() => {
return () => saveGameIds(savedGameIds);
});
useEffect(() => {
async function getGameId(gameId) {
const response = await getSpecificGame(gameId);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const result = await response.json();
const gameData = result.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover,
summary: game.summary,
platforms: game.platforms,
platformId: game.platforms,
genres: game.genres,
genreId: game.genres,
}));
setSelectedGame(gameData);
}
getGameId(gameId);
}, [])
const handleCompleteGame = async (gameId) => {
const gameToComplete = selectedGame.find((game) => game.gameId === gameId);
const token = Auth.loggedIn() ? Auth.getToken() : null;
if (!token) {
return false;
}
try {
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
console.log(data);
setSavedGameIds([...savedGameIds, gameToComplete]);
} catch (err) {
console.error(err);
}
};
With the mutation working in the explorer when I'm able to explicitly define the variable, I am led to believe that the issue is not with the resolver or the typedef, so I'm going to omit those from this post because I don't want it to get too long.
However, I'd be happy to attach any extra code (resolver, typeDef, getSavedGameIds function, etc) if it would allow anyone to assist. The issue (I think) lies in getting my response to match the syntax I used in the explorer, which means trimming down everything except the gameId.
I specifically am extremely suspicious of this line
const gameToComplete = selectedGame.find((game) => game.gameId === gameId)
but I have fiddled around with that for awhile to no avail.
Thank you to anyone who is able to help!
It sounds like you're trying to pass more into your mutation then your schema is defined to allow. In this part:
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
You're spreading gameToComplete here which means everything in the gameToComplete object is going to be sent as a variable. If your schema is setup to just expect gameId to be passed in, but your error message is showing that name is also being passed in, you just need to adjust your variables to exclude everything you can't accept. Try:
const { data } = await completeGame({
variables: { addGame: { gameId } },
});

React native: how do I wait for a state to be set, before I call another state related operation?

I am writing a chat app. Users can search for other users, and then press the "Message" button. Then I navigate to ChatScreen.js. If both users have been messaging each other, I set the chatId variable accordingly. If they have not messaged each other before I dont create chatId, until the ery first message has been sent. When the first message is sent, I first, create new chat, store its properties (user ids, chatId, etc) in my db and then I sent the first message. The problem is that I store chatId as a state variable, and when I create the chat I call setChatId(id). setChatId() is not synchronous call, so by the time when I need to send message with sendText(text, chatId); my chatId is undefined even though I have already created a chat and I have called setChatId.
How can I avoid this error? Ofc, I can check if chatId == undefined then calling sendText(text, id), otherwise calling sendText(text, chatId). Is there a better/neath way to avoid the undefined check?
Here is part of my code:
...
import {
createChat,
} from "./actions";
...
function ChatScreen(props) {
...
const [chatId, setChatId] = useState(props.route.params.chatId);
...
const setupChat = async () => {
try {
await createChat(user.id, setChatId);
props.fetchUserChats();
} catch (error) {
console.error("Error creating chat: ", error);
}
};
async function handleSend(messages) {
if (!chatId) {
// creating chat
await setupChat();
}
const text = messages[0].text ? messages[0].text : null;
const imageUrl = messages[0].image ? messages[0].image : null;
const videoUrl = messages[0].video ? messages[0].video : null;
const location = messages[0].location ? messages[0].location : null;
//assuming chatId is already setup but it is not
if (imageUrl) {
sendImage(imageUrl, chatId, setSendImageError);
} else if (location) {
sendLocation(location, chatId, setLocationError);
} else if (videoUrl) {
sendVideo(videoUrl, chatId, setSendImageError);
} else {
sendText(text, chatId);
}
}
...
}
My createChat function from actions.js file
export async function createChat(otherUid, setChatId) {
let chatId = firebase.auth().currentUser.uid + "_" + otherUid;
await firebase
.firestore()
.collection("Chats")
.doc(chatId)
.set({
users: [firebase.auth().currentUser.uid, otherUid],
lastMessage: "Send the first message",
lastMessageTimestamp: firebase.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log("doc ref for creatign new chat: ", chatId);
setChatId(chatId);
})
.catch((error) => {
console.error("Error creating chat: ", error);
});
}
Instead of using a state variable, I would advise you to use useRef(). This would be a good solution to your problem.Eg Define it this way
const chatId = useRef(null),
then set it this way chatId.current = yourChatId
and get it this way chatId.current. I hope this solves your problem

Rendering elements of array fetched with React does not work

I'm successfully fetching an array of objects from a local ethereum blockchain (successful as in, I've logged the data in componentDidMount and it is what its supposed to be). Its a dynamically sized array
(inventory), so I gotta fetch it element by element. Here are the relevant parts:
async componentDidMount() {
this.setState({fetching: true}, () => {
this.loadData()
.then(result => this.setState({fetching: false}))
})
}
async loadData() {
if (typeof window.ethereum !== 'undefined') {
const web3 = new Web3(window.ethereum)
const netId = await web3.eth.net.getId()
const accounts = await web3.eth.getAccounts()
this.setState({account: accounts[0]})
if(typeof accounts[0] !== 'undefined'){
const balance = await web3.eth.getBalance(accounts[0])
this.setState({balance: balance})
}
else {
window.alert('Please login with MetaMask')
}
try {
const plotRepository = new web3.eth.Contract(PlotRepository.abi, PlotRepository.networks[netId].address)
const plot = await plotRepository.methods.claimPlot(this.state.account).send({from: this.state.account})
this.setState({plot: new web3.eth.Contract(Plot.abi, _plot.plot)})
const size = await this.state.plot.methods.size().call()
const tiles = await this.state.plot.methods.getTiles().call()
await this.loadInventory() // this is where I'm loading the array in question
this.setState({size: size})
this.setState({tiles: tiles})
// logging the array here works
} catch (e) {
window.alert(e)
}
} else {
window.alert('Please install MetaMask')
}
}
async loadInventory() {
for (const [key, value] of Object.entries(inventoryinfo)) {
const item = await this.state.plot.methods.getInventoryItem(key).call()
this.setState({inventory: [...this.state.inventory, {
name: key,
value: parseInt(item[0][1]),
count: item[1]
}]})
}
}
Again, logging the array in the fetching functions works just fine. I'm also using a flag (fetching) which denotes whether all data has been successfully loaded or not, and I only try to render data once everything is loaded:
render() {
const { plot, account, fetching, tiles, inventory } = this.state
return (
<div className="App">
<p>Currently logged in with account: { this.state.account }</p>
<p>Balance: { this.state.balance } </p>
{ fetching ? // make sure things are fetched before rendering them
<p>Loading</p> :
<div>
<UserPlot tileData={tiles} plot={plot} account={account}/>
{
inventory[0].name // inventory[0] is undefined
// inventory.length is not
}
</div>
}
</div>
);
}
Trying to display any element of the array gives me an undefined error. Rendering the array length works though ({inventory.length}). This is weird since I'm doing the same thing with another array that I'm fetching and displaying in the UserPlot component and that works just fine (only difference is that in this case the array is static and I can load it all in one go but I don't think that has anything to do with it)

Resources