Dynamically import all images from a folder in Astro - javascript

I am working with Astro. The project uses quite a few images, and I want to simplify the way I currently add new images. My routes are like:
example.com/pictures/[collection]
( "[" and "]" stands for a dynamic route )
Allowing for example:
example.com/pictures/mixed-tecnique
example.com/pictures/graphite
example.com/pictures/acrylic
In the file pages/pictures/[collection].astro I want to do the following (or something similar):
---
import * as collections from "public/img/collections"
const { collection } = Astro.props
---
{collections[collection].map(imgSrc => <img src={imgSrc} />)}
So now, to have a new Collection route, I just have to create a new folder and drop the images there.
Is there any way to do something to reach the same result? Thanks in advance!!

There are a bunch of different ways to implement a feature like this but here is a simple example making use of the fast-glob library
public
pictures
mixed-technique
example.png
example.png
example.png
graphite
example.png
example.png
example.png
arcylic
example.png
example.png
example.png
// src/pages/pictures/[collection].astro
---
import fg from 'fast-glob';
export async function getStaticPaths() {
// get all collection folder paths: 'public/pictures/[collection]'
const collections: string[] = fg.sync('public/pictures/*', { onlyDirectories: true })
// Create a new route for every collection
return collections.map(collection => {
// Create Route
return {
params: {
// Return folder name of collection as dynamic parameter [collection]
collection: collection.split('/').pop()
},
props: {
// Return array of all image srcs in collection as prop 'images'
images: fg.sync(`${collection}/**/*.{png,jpg}`).map(img => img.replace('public/', '/'))
}
}
})
}
export interface Props {
images: string[];
}
const { collection } = Astro.params
const { images } = Astro.props
---
<html lang="en">
<head>
<!-- ... -->
</head>
<body>
{ images.map(img => <img src={img}/>) }
</body>
</html>
Note: I used fast-glob instead of Astro.glob or import.meta.glob() because it can take a variable as an argument (makes this logic easier/more dynamic) and because it only returns an array of file/folder paths instead of also attempting to return file content

Related

SvelteKit: how do I do slug-based dynamic routing?

I am a newbie on Svelte and in coding in general. I'd prefer to learn SvelteKit (Svelte#Next) rather than sapper since that seems to be where Svelte is heading.
For my personal project, I need to support dynamic routing based on url slugs. How do I do that in SvelteKit? For example, if I have /blog directory and need to pull content based on its "id", how would I do that?
The part that I am having difficulty with is accessing the URL slug parameter.
Thanks in advance.
you can create a file with the [brackets] : touch src/routes/blog/[slug].svelte
And paste the following code
<script>
import { page } from '$app/stores';
</script>
{$page.params.slug}
Then navigate to your app http://localhost:3000/blog/123
You should see your result
In order to create content for the http://localhost:3000/blog page, you can modify src/routes/blog/index.svelte
As of SvelteKit 1.0 the path should be a directory in brackets, e.g. for /blog/<slug> you will add the following:
src/routes/blog/[slug]
|_ +page.js
|_ +page.svelte
Then in src/routes/blog/[slug]/+page.js you can add something like
export const load = ({ params }) => {
return {
slug: params.slug
}
}
which will return it as a data property to +page.svelte, so you can write something like:
<script>
export let data;
</script>
<h1>{data.slug}</h1>
Reference: https://kit.svelte.dev/docs/routing
Caveat - the info in my reply probably may not be valid as SvelteKit matures, but from experiments I have done thus far, this works:
Parameter based routing is similar to the sveltejs/sapper-template. You should learn how Sapper does it first. Lets say I have a route blog with a single param slug (/blog/page-1 & /blog/page-2)
Create a route component in routes/blog named [slug].svelte
Copy the content from the sveltejs/sapper-template example.
Rename the preload function to load with a single parameter such as ctx
Alter the return object to append your slug property to props
export async function load(ctx) {
let slug = ctx.page.params.slug
return { props: { slug }}
}
If your blog has multiple params for the slug (/blog/2021/01/29/this-is-a-slug), you can remove [slug].svelte and create a file name [...data].svelte and change your load method to:
export async function load(ctx) {
let [year, month, day, slug] = ctx.page.params.data;
return { props: { data: { year, month, day, slug }}}
}
Don't forget to define your data property as an object. Here's a typescript example:
<script lang="ts">
export let data: { slug: string, year: number, month: number, day: number };
</script>
From there use the values as {data.slug}, etc
Happy hacking
I also had some issues with the Typescript import so here is a complete Typescript example.
Structure:
src/routes/coins/[coin_name]
|_ +page.ts
|_ +page.svelte
+page.ts:
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
return {
name: params.coin_name
}
}
export interface CoinPage{
name: string
}
+page.svelte and the use of the data:
<script lang="ts">
import type { CoinPage } from "./+page";
export let data:CoinPage;
</script>
<h1>{data.name}</h1>

How to delete child object in Strapi

I used this topic to delete upload related to articles and it work :
How to delete file from upload folder in strapi?
Now i want a cascade deletion. For when i delete a project, articles and upload will deleted also.
Somebody know how to delete a project which will delete an article & upload file ?
This code work to delete a project and children but not the file. And if i try to delete just an article, the controller is called and the file is deleted.
/api/projects/controllers/projects.js
module.exports = {
async delete(ctx) {
const { id } = ctx.params;
const project = await strapi.services.projects.delete({ id });
if (project){
for (let article of project.articles) {
strapi.services.articles.delete({ 'id' : article._id } );
}
}
return sanitizeEntity(project, { model: strapi.models.projects });
}
/api/articles/services/articles.js
delete(params) {
return strapi.query('articles').delete(params);
}
/api/articles/controllers/article.js
module.exports = {
async delete(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.articles.delete({ id });
if (entity) {
strapi.plugins.upload.services.upload.remove(entity.articleFile);
}
return sanitizeEntity(entity, { model: strapi.models.articles });
}
}
The controller is called when i used the url in API like that :
DELETE http://localhost:port/banners/<project_ID>
The strapi.services.xxx.delete is called in the code
Actually you remove articles files only when you pass into your article controller. But in project controller context, you never remove files.
To fix it, remove article files into the article service.
/api/articles/services/articles.js
async delete(params) {
const entity = await strapi.query('articles').delete(params);
if (entity) {
await strapi.plugins.upload.services.upload.remove(entity.articleFile);
}
return entity;
}
/api/articles/controllers/article.js
module.exports = {
async delete(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.articles.delete({ id });
// here articles files have been deleted by services.articles.delete
return sanitizeEntity(entity, { model: strapi.models.articles });
}
}
According to strapi concepts :
Services are a set of reusable functions. They are particularly useful to respect the DRY (don’t repeat yourself) programming concept and to simplify controllers logic.
https://strapi.io/documentation/v3.x/concepts/services.html#concept
Controllers are JavaScript files which contain a set of methods called actions reached by the client according to the requested route. It means that every time a client requests the route, the action performs the business logic coded and sends back the response. They represent the C in the MVC pattern. In most cases, the controllers will contain the bulk of a project's business logic.
https://strapi.io/documentation/v3.x/concepts/controllers.htm

NextJS import images in MDX

I tried a official NextJS MDX-Blog example.
https://github.com/mdx-js/mdx/tree/master/examples/next
But what I'm not able to figure out is how do I setup the NextJS config to load images via webpack?
import img from "./image.jpg"
## Hallo Blogwelt
![My own Image]({img})
You can also use the /public directory to hold your images. For example, if you add an image at /public/image.jpg, you can reference the image in your blog post like this:
![Alt Text](/image.jpg)
Edit: https://nextjs.org/docs/basic-features/image-optimization#local-images
Imagine your next.config.js as something to append to an existing webpack.config behind the scenes. You won't have direct access to the webpack but you can extend it.
So in order to load images you'll need an appropriate image loader.
I found next-images easiest to use:
const withImages = require('next-images')
module.exports = withImages({
webpack(config, options) {
return config
}
})
then you can import:
import Img from "./image.jpg"
Hey thanks for the tip!
It's been a while since June and a gave it another try today and now it's working like expected from me.
I took the MDX/Next Example
Edited the next.config.js like so:
const withPlugins = require('next-compose-plugins');
const images = require('remark-images');
const emoji = require('remark-emoji');
const optimizedImages = require('next-optimized-images');
const withMDX = require('#zeit/next-mdx')({
extension: /\.mdx?$/,
options: {
mdPlugins: [images, emoji]
}
});
module.exports = withPlugins([
[
withMDX,
{
pageExtensions: ['js', 'jsx', 'md', 'mdx']
}
],
[optimizedImages]
]);
Now it works exactly like expected and in a Markdown file within the pages folder I'm able to do something like this:
import Layout from '../../components/Layout'
import Image from './catimage.jpg'
# Hey guys this is the heading of my post!
<img src={Image} alt="Image of a cat" />
Sorry I'm late, but with Next v11 you can directly import images.
That being said, you can add custom loaders for Webpack to modify your mdx files and use a custom component to process the image. e.g.:
// save this somewhere such as mdxLoader and
// add it to your webpack configuration
const replaceAll = require("string.prototype.replaceall");
module.exports = function (content, map, meta) {
return replaceAll(
map
content,
/\!\[(.*)\]\((.+)\)/g,
`<NextImage alt="$1" src={require('$2').default} />`
);
};
and process it:
// and reference this in your MDX provider
const components = {
NextImage: (props: any) => {
return <Image alt={props.alt || "Image"} {...props} />;
},
};
Now you can use markdown flavored images in your posts!
![my image](./image.png)
Include the ./ relative prefix, however.
I'm building a Next.js blog with MDX-Bundler. It allows you to use a remark plugin called remark-mdx-images which converts markdown flavored images into JSX images.
Below is an example configuration to get it to work
const {code} = await bundleMDX(source, {
cwd: '/posts/directory/on/disk',
xdmOptions: options => {
options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkMdxImages]
return options
},
esbuildOptions: options => {
options.outdir = '/public/directory/on/disk/img'
options.loader = {
...options.loader,
'.jpg': 'file'
}
options.publicPath = '/img/'
options.write = true
return options
}
})
You can check out the following resources for detailed explanation on how to do it.
Images with MDX-Bundler
How I built the new notjust.dev platform with NextJS
If you installed the #next/mdx package you can use the <Image /> component Next.js provides:
// pages/cute-cat.mdx
import Image from "next/image";
import cuteCat from "./cute-cat.jpg";
# Cute cat
This is a picture of a cute cat
<Image src={cuteCat} />

Vue.js import images

So I am making an app with a control panel and i want to be able to change a few images inside the app dynamically and my problem is, it just won't work
This is the div I am trying to change
<div class="bg-image bg-parallax overlay" :style="`background-image:url(${bg1url})`"></div>
this is the script part of the Home.vue file
<script>
import axios from 'axios';
export default {
name: 'Home', // this is the name of the component
data () {
return{
page_data: {},
bg1url: null,
};
},
created() {
axios.get("http://localhost:5001/api/v1/pages")
.then((result) => {this.page_data = result.data.page_data});
this.bg1url = require('#/assets/img/' + this.page_data.background1);
alert(page_data.background1);
},
};
</script>
I have tried most of the suggestions on stack overflow but nothing seems to work.
I use the default webpack configurations and generated structure
Note: the parts with axios fetching from the backend work correctly. The only problem is adding the image to the style.
I think could be because you are setting the value for bg1url outsite of promise (calback function of axios), and so this make the code sync and not async
so please try to update, use this instead
created() {
axios.get("http://localhost:5001/api/v1/pages").then(result => {
this.page_data = result.data.page_data
this.bg1url = require('#/assets/img/' + this.page_data.background1);
});
},

How to add authorization header when runtime import webpack chunks of Vue components

The purpose of this task is to make it impossible to download the Vue-component package (*.js file) knowing the address of the component, but not having an access token.
I'm developing an access control system and a user interface in which the set of available components depends on the user's access level.
The system uses the JSON API and JWT authorization. For this, Axios is used on the client side. To build the application, we use Webpack 4, to load the components, we use the vue-loader.
After the user is authorized, the application requests an array of available routes and metadata from the server, then a dynamically constructed menu and routes are added to the VueRouter object.
Below I gave a simplified code.
import axios from 'axios'
import router from 'router'
let API = axios.create({
baseURL: '/api/v1/',
headers: {
Authorization: 'Bearer mySecretToken12345'
}
})
let buildRoutesRecursive = jsonRoutes => {
let routes = []
jsonRoutes.forEach(r => {
let path = r.path.slice(1)
let route = {
path: r.path,
component: () => import(/* webpackChunkName: "restricted/[request]" */ 'views/restricted/' + path)
//example path: 'dashboard/users.vue', 'dashboard/reports.vue', etc...
}
if (r.children)
route.children = buildRoutesRecursive(r.children)
routes.push(route)
})
return routes
}
API.get('user/routes').then(
response => {
/*
response.data =
[{
"path": "/dashboard",
"icon": "fas fa-sliders-h",
"children": [{
"path": "/dashboard/users",
"icon": "fa fa-users",
}, {
"path": "/dashboard/reports",
"icon": "fa fa-indent"
}
]
}
]
*/
let vueRoutes = buildRoutesRecursive(response.data)
router.addRoutes(vueRoutes)
},
error => console.log(error)
)
The problem I'm having is because Webpack loads the components, by adding the 'script' element, and not through the AJAX request. Therefore, I do not know how to add an authorization header to this download. As a result, any user who does not have a token can download the code of the private component by simply inserting his address into the navigation bar of the browser.
Ideally, I would like to know how to import a vue component using Axios.
Or, how to add an authorization header to an HTTP request.
I needed something similar and came up with the following solution. First, we introduce a webpack plugin that gives us access to the script element before it's added to the DOM. Then we can munge the element to use fetch() to get the script source, and you can craft the fetch as needed (e.g. add request headers).
In webpack.config.js:
/*
* This plugin will call dynamicImportScriptHook() just before
* the script element is added to the DOM. The script object is
* passed to dynamicImportScriptHook(), and it should return
* the script object or a replacement.
*/
class DynamicImportScriptHookPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"DynamicImportScriptHookPlugin", (compilation) =>
compilation.mainTemplate.hooks.jsonpScript.tap(
"DynamicImportScriptHookPlugin", (source) => [
source,
"if (typeof dynamicImportScriptHook === 'function') {",
" script = dynamicImportScriptHook(script);",
"}"
].join("\n")
)
);
}
}
/* now add the plugin to the existing config: */
module.exports = {
...
plugins: [
new DynamicImportScriptHookPlugin()
]
}
Now, somewhere convenient in your application js:
/*
* With the above plugin, this function will get called just
* before the script element is added to the DOM. It is passed
* the script element object and should return either the same
* script element object or a replacement (which is what we do
* here).
*/
window.dynamicImportScriptHook = (script) => {
const {onerror, onload} = script;
var emptyScript = document.createElement('script');
/*
* Here is the fetch(). You can control the fetch as needed,
* add request headers, etc. We wrap webpack's original
* onerror and onload handlers so that we can clean up the
* object URL.
*
* Note that you'll probably want to handle errors from fetch()
* in some way (invoke webpack's onerror or some such).
*/
fetch(script.src)
.then(response => response.blob())
.then(blob => {
script.src = URL.createObjectURL(blob);
script.onerror = (event) => {
URL.revokeObjectURL(script.src);
onerror(event);
};
script.onload = (event) => {
URL.revokeObjectURL(script.src);
onload(event);
};
emptyScript.remove();
document.head.appendChild(script);
});
/* Here we return an empty script element back to webpack.
* webpack will add this to document.head immediately. We
* can't let webpack add the real script object because the
* fetch isn't done yet. We add it ourselves above after
* the fetch is done.
*/
return emptyScript;
};
Although sspiff's answer looks quite promising, it did not work directly for me.
After some investigation this was mainly due to me using Vue CLI 3 and thus a newer version of webpack. (which is kinda weird as sspiff mentioned using webpack 4.16.1).
Anyway to solve it I used the following source: medium.com,
Which gave me the knowledge to edit the given code.
This new code is situated in vue.config.js file:
/*
* This plugin will call dynamicImportScriptHook() just before
* the script element is added to the DOM. The script object is
* passed to dynamicImportScriptHook(), and it should return
* the script object or a replacement.
*/
class DynamicImportScriptHookPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"DynamicImportScriptHookPlugin", (compilation) =>
compilation.mainTemplate.hooks.render.tap(
{
name: "DynamicImportScriptHookPlugin",
stage: Infinity
},
rawSource => {
const sourceString = rawSource.source()
if (!sourceString.includes('jsonpScriptSrc')) {
return sourceString;
} else {
const sourceArray = sourceString.split('script.src = jsonpScriptSrc(chunkId);')
const newArray = [
sourceArray[0],
'script.src = jsonpScriptSrc(chunkId);',
"\n\nif (typeof dynamicImportScriptHook === 'function') {\n",
" script = dynamicImportScriptHook(script);\n",
"}\n",
sourceArray[1]
]
return newArray.join("")
}
}
)
);
}
}
module.exports = {
chainWebpack: (config) => {
config.plugins.delete('prefetch')
},
configureWebpack: {
plugins: [
new DynamicImportScriptHookPlugin()
]
}
}
The second piece of code provided by sspiff has stayed the same and can be placed in the App.vue file or the index.html between script tags.
Also to further improve this answer I will now explain how to split the chunks in Vue CLI 3 for this specific purpose.
as you can see I also added the chainWebpack field to the config. This makes sure that webpack does not add prefetch tags in the index.html. (e.g. it will now only load lazy chunks when they are needed)
To further improve your splitting I suggest changing all your imports to something like:
component: () => import(/* webpackChunkName: "public/componentName" */ /* webpackPrefetch: true */'#/components/yourpubliccomponent')
component: () => import(/* webpackChunkName: "private/componentName" */ /* webpackPrefetch: false */'#/components/yourprivatecomponent')
This will make sure that all your private chunks end up in a private folder and that they will not get prefetched.
The public chunks will end up in a public folder and will get prefetched.
For more information use the following source how-to-make-lazy-loading-actually-work-in-vue-cli-3
Hope this helps anyone with this problem!
To perform a simple component download using an access token, you can do the following...
1) Use asynchronous component loading with file extraction. Use webpackChunkName option to separate file or directory/file, like:
components: {
ProtectedComp: () => import(/* webpackChunkName: "someFolder/someName" */ './components/protected/componentA.vue')
}
2) Configure server redirection for protected files or direcory. Apache htaccess config for example:
RewriteRule ^js/protected/(.+)$ /js-provider.php?r=$1 [L]
3) write a server-side script that checks the token in the header or cookies and gives either the contents of .js or 403 error.

Categories

Resources