How to avoid two sequential alerts (one for read and one for edit) when using `window.showDirectoryPicker()` - native-file-system-api-js

const dirHandle = await window.showDirectoryPicker();
await dirHandle.requestPermission({ mode: "readwrite" });
I'm using the File System Access API in chrome. I'd like to let the user pick a folder, then write into the folder.
My code works, but two alerts are shown sequentially, one for read and one for write:
The first one is unnecessary. How can I avoid it?
Interestingly, if the user uses drag and drop, only the 2nd alert will appear after the folder is dropped, which is the desired behavior. The first alert seems to come from showDirectoryPicker. In the ideal world, I imagine being able to pass in an option like showDirectoryPicker({ permission: 'readwrite' }), which will request the 2 permissions together.

I agree it feels suboptimal, but it's a one-time thing. When you run the same code again and pick the same folder (or a nested folder), there will be no prompts at all.
This design was chosen because there are two different things that are being asked here:
First, for the app to read all files (which, recursively for subfolders can be a lot).
Second, for the app to be allowed to write (anywhere) into the folder.

As of Chrome 105, you can get a writable directory with just one prompt
const dirHandle = await window.showDirectoryPicker({ mode: "readwrite" });
Or be explicit in asking for a read-only directory (which is the default).
const dirHandle = await window.showDirectoryPicker({ mode: "read" });

Related

Filtering out assets from precaching in create-react-app

I'm using React 17 with cra-template-pwa to create a PWA. One of my UI libraries has several hundred static image resources that all get preloaded in the PWA (and I don't use most of them). This causes a long delay in enabling the PWA, and even causes Lighthouse to crash. I'm looking at various approaches to fixing the problem, but for a quick fix just to run lighthouse, I'd like to just disable precaching. I haven't been able to find concrete info how to do this. Any advice?
The cleanest solution would entail using the exclude option in the workbox-webpack-plugin configuration, but that requires ejecting in create-react-app.
Something you can do without ejecting, though, is to explicitly filter out entries from the injected self.__WB_MANIFEST array before passing the value to precacheAndRoute().
Your service-worker.js could look something like:
import {precacheAndRoute} from 'workbox-precaching';
// self.__WB_MANIFEST will be replaced with an
// Array<{url: string, revision: string}> during the build process.
// This will filter out all manifest entries with URLs ending in .jpg
// Adjust the criteria as needed.
const filteredManifest = self.__WB_MANIFEST.filter((entry) => {
return !entry.url.endsWith('.jpg');
});
precacheAndRoute(filteredManifest);
The downsides of this approach is that your service-worker.js file will be a bit larger than necessary (since it will include inline {url, revision} entries that aren't needed), and that you'll end up triggering the service worker update flow more than strictly necessary, if the contents of one of your images changes. Those unnecessary service worker updates won't actually harm anything or change the behavior of your web app, though.

How would you redirect calls to the top object in Cypress?

In my application code, there are a lot of calls (like 100+) to the "top object" referring to window.top such as top.$("title") and so forth. Now, I've run into the problem using Cypress to perform end-to-end testing. When trying to log into the application, there are some calls to top.$(...) but the DevTools shows a Uncaught TypeError: top.$ is not a function. This resulted in my team and I discovering that the "top" our application is trying to reach is the Cypress environment itself.
The things I've tried before coming here are:
1) Trying to stub the window.top with the window object referencing our app. This resulted in us being told window.top is a read-only object.
2) Researching if Cypress has some kind of configuration that would smartly redirect calls to top in our code to be the top-most environment within our app. We figured we probably weren't the only ones coming across this issue.
If there were articles, I couldn't find any, so I came to ask if there was a way to do that, or if anyone would know of an alternate solution?
Another solution we considered: Looking into naming window objects so we can reference them by name instead of "window" or "top". If there isn't a way to do what I'm trying to do through Cypress, I think we're willing to do this as a last resort, but hopefully, we don't have to change that, since we're not sure how much of the app it will break upfront.
#Mikkel Not really sure what code I can provide to be useful, but here's the code that causes Cypress to throw the uncaught exception
if (sample_condition) {
top.$('title').text(...).find('content') // Our iframe
} else {
top.$('title').text(page_title)
}
And there are more instances in our code where we access the top object, but they are generally similar. We found out the root cause of the issue is that within Cypress calls to "top" actually interface with Cypress instead of their intended environment which is our app.
This may not be a direct answer to your question, it's just expanding on your request for more information about the technique that I used to pass info from one script to another. I tried to do it within the same script without success - basically because the async nature of .then() stopped it from working.
This snippet is where I read a couple of id's from sessionStorage, and save them to a json file.
//
// At this point the cart is set up, and in sessionStorage
// So we save the details to a fixtures file, which is read
// by another test script (e2e-purchase.js)
//
cy.window().then(window => {
const contents = {
memberId: window.sessionStorage.getItem('memberId'),
cartId: window.sessionStorage.getItem('mycart')
}
cy.writeFile(`tests/cypress/fixtures/cart.json`, contents)
})
In another script, it loads the file as a fixture (fixtures/cart.json) to pull in a couple of id's
cy.fixture(`cart`).then(cart => {
cy.visit(`/${cart.memberId}/${cart.cartId}`)
})

Quickly-editable config file, not visible to public visitors?

I am in the middle of working with, and getting a handle on Vuejs. I would like to code my app in a way that it has some configurable behaviors, so that I could play with parameter values, like you do when you edit your Sublime preferences, but without having to compile my app again. Ideally, I would want a situation where I could have my colleagues be able to fiddle with settings all day long, by editing a file over FTP maybe, or over some interface....
The only way I know how to do it now, is to place those settings in a separate file, but as the app runs in the client, that file would have to be fetched via another HTTP request, meaning it's a publicly readable file. Even though there isn't any sensitive information in such a configuration file, I still feel a little wonky about having it public like that, if it can be avoided in any way...
Can it be avoided?
I dont think you can avoid this. One way or another your config file will be loaded into the vuejs application, therefore being visible to the end user (with some effort).
Even putting the file outside of the public folder wouldnt help you much, because then it is unavailable for HTTP to request the file. It would only be available to your compile process in this case.
So a possible solution could be to have some sort of HTTP request that requests GET example.com/settings and returns you a JSON object. Then you could have your app make a cookie like config_key = H47DXHJK12 (or better a UUID https://en.wikipedia.org/wiki/Universally_unique_identifier) which would be the access key for a specific config object.
Your application must then request GET example.com/settings (which should send the config_key cookie), and your config object for this secret key will be returned. If the user clears his cookies, a new request will return an empty config.

How to add new fields to a list in KeystoneJS

I want to extend the Blog in KeystoneJS by adding a boolean "frontPage" field to the Post schema, which I want to use to show selected posts on the homepage.
I came up with this code that I put in the updates folder:
var keystone = require('keystone');
var async = require('async');
exports = module.exports = function (done) {
let post = keystone.list('Post');
post.add({
frontPage: Boolean
});
done();
};
it seems to work, but the change does not persist when I restart the server. All docs describe the process of creating new Lists, but none tells how to modify an existing one. Also tried to add a post.register()at the end but no luck.
Is there a function to persist the new schema, or I should write a shell script outside Keystone for that?
Thank you
It seems to work, but the change does not persist when I restart the server.
Scripts in the application updates folder are intended for data import or migration, and intentionally are only applied once to a given deployment.
All docs describe the process of creating new Lists, but none tells how to modify an existing one.
To add, remove, or change fields in your model you should modify the file in your Keystone project (eg: models/Post.js) and then restart your application to pick up the changes.
There generally is no need to create a corresponding update script unless you wanted to include associated data changes (for example, setting values for the existing documents).

CasperJS - How to open up in parallel all links from an array of links

I need to open all the links of the array in parallel.
How to make it?
In my code, all links will be open one by one, instead of parallel.
Here is my code:
casper.then(function(){
links = this.evaluate(function(){
var links = document.getElementsByTagName('a');
links = Array.prototype.map.call(links,function(link){
return link.getAttribute('href');
});
return links;
});
});
casper.then(function(){
this.each(links,function(self,link){
self.thenOpen(link,function(a){
this.echo(this.getCurrentUrl());
});
});
});
casper.run(function(){this.exit()});
The casper object represents a single browser window.
One approach is to create multiple casper objects, one per URL you want to get in parallel. Note that this is not officially supported, and so may be fragile.
Another approach is to use a bash script to start multiple instances of casperjs, and give each of them a set of the URLs to fetch. This is nice and clean (if using persistent cookies, you might want to make sure they each have their own cookie file: --cookies-file=/path/to/cookies.txt), but might be harder for you to script, depending on how you were getting your initial list of URLs.