Made a Small WebApp to Organize My Posts Images [Code Inside]
Lately, I've been posting way more screenshots in my Gaming Community articles than before, and the time I spent copying and pasting the URLs is getting unbearable...
I'm uploading the images through INLEO. If I upload the images one-by-one the time in-between piles up, but if I upload in bulk, the URLs drop as soon as each image finishes uploading, so I can't control their order. I used to do it this way in the past few weeks...
The other day, I asked AI to code me something that gives me the images as small thumbnails. It allows me to order them via Drag-and-Drop. The app should give me the new order as the output I can copy. Also, it works offline!!
The version I currently use doesn't keep the aspect ratio of the images, and I also need to put the images in a table after I order them, so I might update the code to provide these features, but I decided to share the current version here.
I feel so proud!

This code was generated with GLM 4.7 via Venice.ai:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Grid Reorder Tool</title>
(html comment removed: SortableJS for Drag and Drop )
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.2/Sortable.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
padding: 20px;
background-color: #f4f4f9;
color: #333;
max-width: 1200px;
margin: 0 auto;
}
.section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
h2 { margin-top: 0; font-size: 1.2rem; }
textarea {
width: 100%;
height: 100px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-family: monospace;
resize: vertical;
}
.controls {
display: flex;
align-items: center;
gap: 20px;
margin-top: 10px;
}
input[type="number"] {
width: 60px;
padding: 5px;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
button:hover {
background-color: #0056b3;
}
#image-grid {
display: grid;
gap: 15px;
margin-top: 20px;
min-height: 100px;
border: 2px dashed #eee;
padding: 10px;
}
.grid-item {
background: #eee;
border-radius: 4px;
overflow: hidden;
cursor: grab;
position: relative;
aspect-ratio: 1 / 1;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ddd;
}
.grid-item:active {
cursor: grabbing;
}
.grid-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.sortable-ghost {
opacity: 0.3;
}
#output-text {
height: 150px;
background-color: #f8f9fa;
}
.copy-btn {
margin-top: 10px;
background-color: #28a745;
}
</style>
</head>
<body>
<div class="section">
<h2>[Input] Paste Text with Image URLs</h2>
<textarea id="input-text" placeholder="Paste text here... The app will find all URLs ending in jpg, png, webp, etc."></textarea>
<div class="controls">
<div>
<label for="grid-columns">Columns: </label>
<input type="number" id="grid-columns" value="4" min="1" max="12">
</div>
<button id="generate-btn">[Generate Blocks Button]</button>
</div>
</div>
<div class="section">
<h2>[The Image Section] Drag to Reorder</h2>
<div id="image-grid"></div>
</div>
<div class="section">
<h2>[The Output section]</h2>
<textarea id="output-text" readonly placeholder="Reordered URLs will appear here..."></textarea>
<button class="copy-btn" onclick="copyOutput()">Copy to Clipboard</button>
</div>
<script>
const inputText = document.getElementById('input-text');
const outputText = document.getElementById('output-text');
const imageGrid = document.getElementById('image-grid');
const generateBtn = document.getElementById('generate-btn');
const gridColumns = document.getElementById('grid-columns');
let sortableInstance = null;
// Initialize SortableJS
function initSortable() {
if (sortableInstance) sortableInstance.destroy();
sortableInstance = new Sortable(imageGrid, {
animation: 150,
ghostClass: 'sortable-ghost',
onEnd: updateOutput
});
}
// Extract URLs from text
function extractUrls(text) {
const urlRegex = /https?:\/\/[^\s<]+?\.(jpg|jpeg|png|gif|webp|svg|bmp)/gi;
return text.match(urlRegex) || [];
}
// Update the grid layout
function updateGridLayout() {
imageGrid.style.gridTemplateColumns = `repeat(${gridColumns.value}, 1fr)`;
}
// Generate the image blocks
generateBtn.addEventListener('click', () => {
const urls = extractUrls(inputText.value);
imageGrid.innerHTML = '';
if (urls.length === 0) {
alert("No image URLs found.");
return;
}
urls.forEach(url => {
const div = document.createElement('div');
div.className = 'grid-item';
div.dataset.url = url;
const img = document.createElement('img');
img.src = url;
img.loading = "lazy";
// Handle broken images
img.onerror = () => {
div.style.backgroundColor = '#ffcccc';
div.innerText = 'Broken Link';
};
div.appendChild(img);
imageGrid.appendChild(div);
});
updateGridLayout();
initSortable();
updateOutput();
});
// Sync Output Textarea with Grid Order
function updateOutput() {
const items = imageGrid.querySelectorAll('.grid-item');
const orderedUrls = Array.from(items).map(item => item.dataset.url);
outputText.value = orderedUrls.join('\n');
}
// Listen for column changes in real-time
gridColumns.addEventListener('input', updateGridLayout);
// Copy Function
function copyOutput() {
outputText.select();
document.execCommand('copy');
alert('Copied to clipboard!');
}
</script>
</body>
</html>
Posted Using INLEO
!ALIVE | !BBH | !CTP