What's a good way to get and display all the folders and files of a directory in a Vue.js + Electron app? - vue.js

I have a Vue.js + Electron app (electron-vue boilerplate)
What are the best practices of getting and displaying directory's folders and files?
Basically I want it to behave like a File Explorer (Windows) or Finder (Mac). You open a folder you get all the content displayed.
Let's say we have:
the main view called App.vue with a router inside of it.
there's an open folder button in App.vue.
And we also have FileExplorer.vue component that is getting displayed as a route inside that App.vue
How do I make this FileExplorer.vue component to get and display files and folders of, let's say, directory C:\test folder (on Windows) when we hit that open folder button in App.vue?
I know that I should use Node for that, but how exactly? What do I import and how do I use Vue to make it all work together?
All the projects on Github are too complicated for a newbie to understand how they work. For example this one is quite simple looking, but there's so much code in there that you don't even know where to start
Update:
I managed to get the content of a folder doing this:
<v-btn #click="readDirectory"></v-btn>
[...]
const fs = require('fs-extra')
export default {
name: "FileExplorer",
data () {
return {
dir:'C:/test',
files:[],
file:""
}
},
methods: {
readDirectory() {
fs.readdir(this.dir, (err, dir) => {
console.log(dir);
for(let filePath of dir)
console.log(filePath)
this.files = dir
})
},
}
}
And I displayed it like this:
<v-btn v-for="file in files" :key="file.id">
{{file}}
</v-btn>
But it doesn't really behave like a real file explorer...I get the folders and files on button click but I can't do anything with all those folders and files.
How do I make all the folders that it gets on click to behave in a similar way (to get its content) ?
And how do I display folders and files differently?

If you simply want to allow a user to select a folder in Electron I would suggest using the showOpenDialog API.
In the renderer process, you need to use the remote API to access the dialog API.
const { remote } = require('electron')
remote.dialog.showOpenDialog({
properties: ['openDirectory'],
defaultPath: current
}, names => {
console.log('selected directory:' + names[0]);
});
If you want to display the contents of a directory within your app you're going to have to use the node fs module and read the directory contents.
fs.readdir(path[, options], callback)
Will callback with all the file and directory paths which you'll then have to iterate over to get more info or to traverse recursively to get their contents. There are are node modules which make this process a little easier than writing it all yourself.
Once you've got an object tree containing all your files and directories you can use this to create a UI using vue.js.

Related

How can I get globEager to return directory paths?

The code below works fine for scanning directories and returning image paths for example, but for some reason it doesn't return directory paths? Does globEager just completely ignore directories? Is there an alternative I can use for this purpose? Using Vue.js mind you.
Here's my code:
const project = import.meta.globEager("/src/assets/projects/design/*");
console.log(project)
for (let key in project) {
console.log(key);
}

Ignore specific components and assets from being added to dist folder in Nuxt

I have a multiplatform Nuxt 2.15 project with the following structure:
assets/platform1/... //contains fonts, images and config files
assets/platform2/...
components/platform1SpecificComponent.vue
components/platform2SpecificComponent.vue
components/sharedComponent.vue
layouts/platform1/...
layouts/platform2/...
components/platform1SpecificPage.vue
components/platform2SpecificPage.vue
components/sharedPage.vue
I've wrote the following plugin to dynamically register different platform's components unders the same name to be able to reuse most of my pages:
export default ({ store }) => {
const config = store.state.platform.config
Vue.component('NewsCard', () => import(`#/components/pages/news/${config.news.cardComponent.name}`))
}
The problem that after platform1 build, output dir contain assets from platform2 which increases size. At the same time sass or vue loader tries to parse style of vue template's part for components that are not used and not referenced in platform1, throwing errors about missing sass variables...
I know that nuxt allows to define ignore property in nuxt.config.json (or .nuxtignore) but that only works for pages and layouts...
I tried to configure webpack rules by modifiying nuxt.config.js extend property:
const fontsLoader = config.module.rules.find(i => String(i.test) === String('/\\.(woff2?|eot|ttf|otf)(\\?.*)?$/i'))
fontsLoader['exclude'] = new RegExp(`assets/(?!${platform})`)
and actually that removed fonts from dist folder but it started to throw warnings: that these files doent have appropriate webpack loader. The same behaviour I observed when tried to exclude specific vue components from vue-loader.
So doesnt seem like a good solution after all..
Becides, the same approach didnt work for images inside assets folder:
const imgLoader = config.module.rules.find(i => String(i.test) === String('/\\.(png|jpe?g|gif|svg|webp|avif)$/i'))
imgLoader['exclude'] = new RegExp(`assets/(?!${platform})`)
all the images are still added to dist...
Is there a way to tell nuxt (or webpack via nuxt config) to ignore specific folders with fonts and images under the /assets folder and specific vue components that are not pages or layouts?

Image require() in nuxt with hot reload by HRM webpack

I use the dynamic source for vue-webpack images in nuxt :src="require('path/to/image' + dynamic.variable)" in my project navbar. If the users substitute their image through a form which refetches their information and deletes their previous image I get a webpack error module (img) not found (it does not find the new one): is there a way to solve this, like wait for webpack HRM to finish?
I tried setting up a setTimeout() of one second before user re-fetch and it works, but I don't like a random waiting, I'd use a promise or a sync dynamic, the point is webpack hot reload is not controlled by my functions.. I also tried with setting the dynamic path as a computed: but it doesn't fix.
My image tag:
<img v-if="this.$auth.user.image" class="userlogo m-2 rounded-circle" :src="require('#assets/images/users/' + this.$auth.user.image)" alt="usrimg">
My Useredit page methods:
...
methods: {
userEdit() {
//uploads the image
if (this.formImageFilename.name) {
let formImageData = new FormData()
formImageData.append('file', this.formImageFilename)
axios.post('/db/userimage', formImageData, { headers: { 'Content-Type': 'multipart/form-data' } })
// once it has uploaded the new image, it deletes the old one
.then(res=>{this.deleteOldImage()})
.catch(err=>{console.log(err)})
}else{
this.userUpdate() //if no new image has to be inserted, it proceeds to update the user information
}
},
deleteOldImage(){
if(this.$auth.user.image){axios.delete('/db/userimage', {data: {delimage: this.$auth.user.image}} )}
console.log(this.$auth.user.image + ' deleted')
this.userUpdate() // it has deleted the old image so it proceeds to update the user information
},
userUpdate(){
axios.put(
'/db/user', {
id: this.id,
name: this.formName,
surname: this.formSurname,
email: this.formEmail,
password: this.formPassword,
image: this.formImageFilename.name,
})
.then(() => { console.log('User updated'); this.userReload()}) // reloads the updated user information
.catch(err => {console.log(err)} )
},
userReload(){
console.log('User reloading..')
this.$auth.fetchUser()
.then(() => { console.log('User reloaded')})
.catch(err => {console.log(err)} )
},
}
...
the problem happens after "console.log('User reloading..')" and before "console.log('User reloaded');", it is not related to the file upload nor the server response. I broke a single function in many little ones just to check the function progression and its asynchronous dynamics but the only one that is not manageable is the webpack hot reload :/
I'd like the users to upload their images and see their logo in the Navbar appear updated after submitting the form.
First of all, as somebody told you in the comments, webpack hmr shouldn't be used for production.
In Nuxt, everything that you reference from the assets folder will be optimized and bundled into the project package. So the ideal use case for this folder is all assets that can be packaged and optimized, and most likely won't change like fonts, css, background images, icons, etc.
Then, require is called only once by webpack when it is either building the site for local development or building the site for generating a production package. The problem in your case is that you delete the original file while you're in development and webpack tries to read it and fails.
In the case of these images that the user uploads, I think you should use the static folder instead and instead of using require you'll have to change the :src with
:src="'/images/users/' + this.$auth.user.image"
Let me know if this helps.
Okay, I probably solved it.
HMR: you are of course right. Thank you for pointing out, I am sorry, I am a beginner and I try to understand stuff along the way.
Aldarund, thank you, your idea of not changing the path and cache it client side.. I am too noob to understand how I could implement it ( :) ) but it gave me a good hint: the solution was to keep the image name as the user id + the '.png' extension and to manage the image with jimp so that the image name, extension and file type are always the same, and with or without webpack compiling the new path, I always have the correct require().
Jair, thank you for the help, I didn't follow that road, but I will keep it as a second chance if my way creates errors. Just to be specific: the error comes when it does not find -and asks for the name of- the NEW image, not the OLD one, as I wrote in my question: it happens because the fetchUser() functions reloads the user information including the new image name.
Do you guys see any future problems in my methodology?
Really thank you for your answers. I am learning alone and it's great to receive support.

Multiple Aurelia Instances - Aurelia Webpack Plugin - aureliaApp option - "module not found"

I am composing my web app as a number of Aurelia "feature" apps - although I'm not using Aurelia features as such. Consequently in my html markup I have two entry points pointing to different apps:
<!-- Top Navigation Bar -->
<div aurelia-app="topnav"></div>
<!-- Main App-->
<div aurelia-app="main"></div>
I am using webpack and everything works perfectly using the single "main" app. Webpack generates a JS file "main.bundle.js" which I include in the src tag.
Things are not so straightforward when I added the "topnav" app. In webpack I tell the plugin to use a different aureliaApp name:
new AureliaPlugin({ aureliaApp: "topnav"}),
and, as you can see my HTML entrypoint also calls "topnav". Webpack generates a JS file "topnav.bundle.js" which I also include. I have a file called "topnav.ts" which contains the aurelia Cionfigure function which ends:
aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName("nav")));
And a pair of files "nav.ts", "nav.html" which constitute my viewmodel and view.
When I run the app aurelia loads and the "nav" module code executes. But I then get an error - see below.
The module which it reports that it cannot find is the one entered into the HTML markup.
Should this work? Have I missed something?
I should add, everything seems to work. I can create and update properties in the viewmodel and these are bound to the view. It's just that this error is thrown.
You are doing nothing wrong, just unsupported scenario. Per official doc-wiki: https://github.com/aurelia/webpack-plugin/wiki/AureliaPlugin-options#aureliaapp
You can have only 1 auto entry module with aureliaApp configuration. To solve this, you just need to add PLATFORM.moduleName('topnav') to your main.ts (and put it on root level)
Another way to do is to bootstrap manually:
// in your index.ts
import { bootstrap } from 'aurelia-bootstrapper';
// bootstrap top nav application, with one instance of Aurelia
bootstrap(aurelia => {
// do your configuration
aurelia
.start()
.then(() => aurelia.setRoot(
PLATFORM.moduleName('topnav'),
document.querySelector('#topnav')
);
});
// bootstrap main application, with another instance of Aurelia
bootstrap(aurelia => {
// aurelia.use.standardConfiguration();
// ...
aurelia
.start()
.then(() => aurelia.setRoot(
PLATFORM.moduleName('app'),
document.querySelector('app')
)
});

React-native packager configuration - How to include .zip file in bundle?

My problem:
I have a zip file that contains a firmware update for my company's device
I want to be able to access it using react-native-fs with the code below
.
export function readAssetFile(name) {
if(Platform.OS === 'ios') {
return RNFS.readFile(`${RNFS.MainBundlePath}/assets/data/${name}`);
} else {
return RNFS.readFileAssets(`raw/${name}`, 'base64');
}
}
My project structure looks like:
ProjectDir
android
data
image1.png
image2.png
firmwarefile.zip
ios
The android branch works, because I added a build step in my .gradle to copy firmwarefile.zip into ProjectDir/android/app/src/main/assets/raw. So I can call readAssetFile('firmwarefile.zip'), and it returns the data.
On iOS, all the image files (Image1.png, Image2.png) are included in MyProject.app/assets/data/ without me having to do anything, but the zip file that sits beside them is not.
Looking into the actual packager code (from the metro project), it seems (based on metro/src/defaults.js) that zip files aren't included by default by the packager, but the packager can be configured to include other file types. But I can't find any documentation for how I'd go about doing that configuring.
Sorry for what feels like a really simple question, but I've been trying to get this zip included in my bundle for ~4 hours now. I'm resorting to manually putting in console.logs and error-throws to trace things inside metro to try and find where I should be sending in my config.
Versions:
React-native: 0.55.3
Metro: 0.30.2
This is a hack, but it gets it done:
Convert your zip binary to a base64 string
Stick it in a .js file, a la module.exports = "<your base64 data goes here>"
In your file that needs the zip file, use import myZipFileAsBase64 from './hacky-base64-file.js';
Here's a quick script to make your base64 files:
var fs = require('fs');
function prepareZip(file, outJs) {
const b64 = fs.readFileSync(file, 'base64');
fs.writeFileSync(outJs, `module.exports = ${JSON.stringify(b64)};`);
}
prepareZip('./data/myFirmware.zip', './hacky-base64-file.js');