A brute force dataviewJS query to create a multi-column picture table

Recently I needed discovered that I had a lot of similar pictures in my vault because it seemed easier to paste new ones on the spot. Unfortunately using different pictures for the same concepts creates a messy knowledge graph which is difficult to remember. Hence, I needed a solution with following features:

  • Works out-of-the-box, no configuration necessary
  • Scans down a directory tree and collect all images
  • Presents images in a multi-column table format along with their filenames
  • Allow drag and drop of images from the gallery into notes
  • Allow searching images by name

Here’s a crude dataviewJS snippet that does something like that:

The screenshot below shows a picture gallery created by a dataviewJS query in the left pane and a new note in the right pane. The picture in the Untitled note was dragged from the gallery and dropped into that note.

DataViewJS Query

~~~dataviewjs
/** 
 * Picture Gallery (dvjs)
 *
 * Searches the current folder and its subfolders (`deepScan = true`) for
 * pictures by file extension.
 * Configuration Options:
 * - `pictureExtensions` - file extensions of the pictures to include
 * - `pictureColumns` - the number of picture columns in the gallery table.
 *   **Note** each picture colums has an associated `File` column, hence the table
 *   has twice as many columns in total.
 * - `pictureSize` - the size of the preview image
 * - `deepScan` - `true` to recurse into subfolders
 */
const
	pictureExtensions = ["jpg", "jpeg", "png", "webp", "svg"],
	pictureColumns = 2,
	pictureSize = "100",
	deepScan = true, // <- set to `false` to search current folder only
	current = dv.current(),
	vault = app.vault,
	currentFolder = await vault.getFolderByPath(current.file.folder),
	folders = [currentFolder ?? vault.getRoot()];
	
let pictures = [];
while (folders.length > 0) {
	// inspect all subfolders for pictures 
	folders.shift().children
		?.filter(c => c.type === "folder")
		.forEach( f => {
			pictures = [
				...pictures,
				...f.children?.filter(p => pictureExtensions.includes(p.extension))
			 ];
			if (deepScan) {
				folders.push(f);
			}
		});
}

if (pictures.length > 0) {
	const header = [];
	for (let i = 0; i < pictureColumns; i++) {
		header.push("Preview","File");
	}
	const rows = [];
	let currentRow = [];
	dv.array(pictures)
		.sort(p => p.name)
		.forEach(p => {
		if (currentRow.length === header.length){
			rows.push(currentRow);
			currentRow = []
		}
		currentRow.push(
			dv.fileLink(p.path,true,pictureSize),
			dv.fileLink(p.path,false,p.name));
	});
        rows.push(currentRow); // the last row
	dv.table(header,rows);
} else {
	dv.paragraph(`_No ${pictureExtensions.join(",")} images found._`)
}
~~~

To use that snippet you need to have the Dataview plugin installed and configured to allow JavaScript queries.

Picture Gallery Styling

If you have the cards layout activated, i.e the cssclasses: cards property in the page’s frontmatter, the gallery does not look too shabby:

A Plugin Solution

For a professionally looking solution you may want to check out the Masonry Note Gallery for Obsidian plugin.

Thank you for sharing this snippet!

I tried using your solution, but unfortunately, every time I attempted to display the table in Obsidian, the app would freeze. After running into this issue, I decided to create my own version to build an image library.

Here’s the solution I came up with:

```dataviewjs
const allImages = app.vault.getFiles().filter(file => 
  ["jpg", "jpeg", "png", "webp", "svg"].includes(file.extension)
);

dv.table(
  ["Image", "Lien"],
  dv.array(allImages).map(link => [
    dv.fileLink(link.path, true), 
    dv.fileLink(link.path)
  ])
);
```

Edit : I also added a search field (not in realtime, need to type enter to search) with this code and a propery search_field in the note :

let currentFilemot = app.vault.getAbstractFileByPath(dv.current().file.path);
let inputElementmot = dv.el("input", "", {
  attr: {
    id: "search_field",
    value: dv.current()["search_field"]
  }
});
inputElementmot.addEventListener("change", (event) => {
  app.fileManager.processFrontMatter(currentFilemot, (frontmatter) => { frontmatter["search_field"] = event.target.value})
});
dv.span("<br/>")

let allImages = app.vault.getFiles().filter(file => 
  ["jpg", "jpeg", "png", "webp", "svg"].includes(file.extension)
);

if (dv.current()["search_field"]) {
    allImages = allImages.filter(p => 
        p.name && typeof dv.current()["search_field"] === 'string' 
        ? p.name.toLowerCase().replace(/\s+/g, '').indexOf(dv.current()["search_field"].toLowerCase().replace(/\s+/g, '')) !== -1 
        : true
    );
}

dv.table(
  ["Image", "Lien"],  // Noms des colonnes
  dv.array(allImages).map(link => [
    dv.fileLink(link.path, true), 
    dv.fileLink(link.path)
  ])
);

Nice solution for the case you want all pictures in the entire vault. The search feature is really cool!

As for the freeze. I had issues when pasting the snippet and Obsidian would replace the three dots ... with a single ellipsis character . That would not explain the freeze though. What platform would run this on? Windows, Apple, Mobile?

For what it is worth, here is a cleaned up version of my original dataviewJS snippet:

~~~dataviewjs
/** 
 * Picture Gallery (dvjs)
 *
 * Searches the current folder and its subfolders (`deepScan = true`) for
 * pictures by file extension.
 * Configuration Options:
 * - `pictureExtensions` - file extensions of the pictures to include
 * - `pictureColumns` - the number of picture columns in the gallery table.
 *   **Note** each picture colums has an associated `File` column, hence the table
 *   has twice as many columns in total.
 * - `pictureSize` - the size of the preview image
 * - `deepScan` - `true` to recurse into subfolders
 */
const
	pictureExtensions = ["jpg", "jpeg", "png", "webp", "svg", "gif"],
	pictureColumns = 2,
	pictureSize = "100",
	deepScan = true, // <- set to `false` to search current folder only
	current = dv.current(),
	vault = app.vault,
	currentFolder = await vault.getFolderByPath(current.file.folder),
	folders = [currentFolder ?? vault.getRoot()];
	
const pictures = [];
while (folders.length > 0) {
	// inspect all subfolders for pictures 
	folders.shift().children
		.forEach( f => {
			if (f.children) {
				// this is a folder
				if (deepScan) {
					folders.push(f);
				}
			} else if (pictureExtensions.includes(f.extension)) {
				pictures.push(f);	
			}
		});
}

if (pictures.length > 0) {
	const header = [];
	for (let i = 0; i < pictureColumns; i++) {
		header.push("Preview","File");
	}
	const rows = [];
	let currentRow = [];
	dv.array(pictures)
		.sort(p => p.name)
		.forEach(p => {
		if (currentRow.length === header.length){
			rows.push(currentRow);
			currentRow = []
		}
		currentRow.push(
			dv.fileLink(p.path,true,pictureSize),
			dv.fileLink(p.path,false,p.name));
	});
	rows.push(currentRow); // the last row
	dv.table(header,rows);
} else {
	dv.paragraph(`_No ${pictureExtensions.join(",")} images found._`)
}
~~~

I’m on Windows. I retested your second script. And this time, I don’t have a problem with freezing.

Thanks for taking the time!