adding custom meme template instead of fixed library
This commit is contained in:
@@ -260,3 +260,26 @@ canvas#memeCanvas {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Template Card & Drag Hover Styles */
|
||||||
|
.custom-template-card .template-image-wrapper {
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-template-card:hover .template-image-wrapper {
|
||||||
|
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-template-card i {
|
||||||
|
transition: transform 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-template-card:hover i {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-wrapper.drag-hover {
|
||||||
|
border-color: var(--accent, #9f0) !important;
|
||||||
|
box-shadow: 0 0 25px rgba(159, 255, 0, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,11 +82,23 @@
|
|||||||
|
|
||||||
const defaultSize = 40;
|
const defaultSize = 40;
|
||||||
|
|
||||||
// Initial layers
|
// Initial layers - only set if we don't have any layers yet
|
||||||
|
if (textLayers.length === 0) {
|
||||||
textLayers = [
|
textLayers = [
|
||||||
{ id: Date.now(), text: '', x: canvas.width / 2, y: 40, fontSize: defaultSize },
|
{ id: Date.now(), text: '', x: canvas.width / 2, y: 40, fontSize: defaultSize },
|
||||||
{ id: Date.now() + 1, text: '', x: canvas.width / 2, y: canvas.height - 100, fontSize: defaultSize }
|
{ id: Date.now() + 1, text: '', x: canvas.width / 2, y: canvas.height - 100, fontSize: defaultSize }
|
||||||
];
|
];
|
||||||
|
} else {
|
||||||
|
// Keep the text layers but adjust their coordinates to be in-bounds if they exceed new boundaries
|
||||||
|
textLayers.forEach(layer => {
|
||||||
|
if (layer.x > canvas.width) {
|
||||||
|
layer.x = canvas.width / 2;
|
||||||
|
}
|
||||||
|
if (layer.y > canvas.height) {
|
||||||
|
layer.y = canvas.height - 100;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderInputs();
|
renderInputs();
|
||||||
draw();
|
draw();
|
||||||
@@ -460,6 +472,77 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Custom Local Image Selector logic
|
||||||
|
const fileInput = document.getElementById('customTemplateFile');
|
||||||
|
const selectFileBtn = document.getElementById('selectCustomFileBtn');
|
||||||
|
const canvasWrapper = document.querySelector('.canvas-wrapper');
|
||||||
|
|
||||||
|
const loadLocalImage = (file) => {
|
||||||
|
if (file && file.type.startsWith('image/')) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
img.src = event.target.result;
|
||||||
|
|
||||||
|
// Update template metadata
|
||||||
|
window.memeTemplate.name = file.name.replace(/\.[^/.]+$/, "");
|
||||||
|
|
||||||
|
// Update header title dynamically
|
||||||
|
const headerTitle = document.querySelector('.meme-title');
|
||||||
|
if (headerTitle) {
|
||||||
|
headerTitle.innerHTML = `${window.f0ckI18n?.meme?.create_meme || 'Create Meme:'} ${window.memeTemplate.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tags input value if tags are present
|
||||||
|
const tagsInput = document.getElementById('tags');
|
||||||
|
if (tagsInput) {
|
||||||
|
tagsInput.value = `meme, Custom, ${window.memeTemplate.name}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selectFileBtn && fileInput) {
|
||||||
|
selectFileBtn.addEventListener('click', () => {
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
loadLocalImage(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML5 Drag & Drop Support
|
||||||
|
if (canvas && canvasWrapper) {
|
||||||
|
const preventDefaults = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||||
|
canvasWrapper.addEventListener(eventName, preventDefaults, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
['dragenter', 'dragover'].forEach(eventName => {
|
||||||
|
canvasWrapper.addEventListener(eventName, () => {
|
||||||
|
canvasWrapper.classList.add('drag-hover');
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
['dragleave', 'drop'].forEach(eventName => {
|
||||||
|
canvasWrapper.addEventListener(eventName, () => {
|
||||||
|
canvasWrapper.classList.remove('drag-hover');
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
canvasWrapper.addEventListener('drop', (e) => {
|
||||||
|
const dt = e.dataTransfer;
|
||||||
|
const file = dt.files[0];
|
||||||
|
loadLocalImage(file);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Initial draw
|
// Initial draw
|
||||||
setTimeout(draw, 300);
|
setTimeout(draw, 300);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -528,7 +528,11 @@
|
|||||||
"text_layer": "Textebene",
|
"text_layer": "Textebene",
|
||||||
"enter_text": "Text eingeben...",
|
"enter_text": "Text eingeben...",
|
||||||
"size_label": "Größe",
|
"size_label": "Größe",
|
||||||
"create_meme": "Meme erstellen:"
|
"create_meme": "Meme erstellen:",
|
||||||
|
"custom_template_title": "Eigene Vorlage",
|
||||||
|
"custom_template_tag": "Lokale Datei",
|
||||||
|
"select_image": "Eigenes Bild auswählen",
|
||||||
|
"choose_file_btn": "Bild aussuchen"
|
||||||
},
|
},
|
||||||
"timeago": {
|
"timeago": {
|
||||||
"just_now": "gerade eben",
|
"just_now": "gerade eben",
|
||||||
|
|||||||
@@ -532,7 +532,11 @@
|
|||||||
"text_layer": "Text Layer",
|
"text_layer": "Text Layer",
|
||||||
"enter_text": "Enter text...",
|
"enter_text": "Enter text...",
|
||||||
"size_label": "Size",
|
"size_label": "Size",
|
||||||
"create_meme": "Create Meme:"
|
"create_meme": "Create Meme:",
|
||||||
|
"custom_template_title": "Use Own Template",
|
||||||
|
"custom_template_tag": "Local File",
|
||||||
|
"select_image": "Select Custom Image",
|
||||||
|
"choose_file_btn": "Choose Image"
|
||||||
},
|
},
|
||||||
"timeago": {
|
"timeago": {
|
||||||
"just_now": "just now",
|
"just_now": "just now",
|
||||||
|
|||||||
@@ -528,7 +528,11 @@
|
|||||||
"text_layer": "Tekstlaag",
|
"text_layer": "Tekstlaag",
|
||||||
"enter_text": "Voer tekst in...",
|
"enter_text": "Voer tekst in...",
|
||||||
"size_label": "Grootte",
|
"size_label": "Grootte",
|
||||||
"create_meme": "Meme Maken:"
|
"create_meme": "Meme Maken:",
|
||||||
|
"custom_template_title": "Eigen sjabloon gebruiken",
|
||||||
|
"custom_template_tag": "Lokaal bestand",
|
||||||
|
"select_image": "Selecteer eigen afbeelding",
|
||||||
|
"choose_file_btn": "Kies afbeelding"
|
||||||
},
|
},
|
||||||
"timeago": {
|
"timeago": {
|
||||||
"just_now": "zojuist",
|
"just_now": "zojuist",
|
||||||
|
|||||||
@@ -531,7 +531,11 @@
|
|||||||
"text_layer": "Textebene",
|
"text_layer": "Textebene",
|
||||||
"enter_text": "Text eingeben...",
|
"enter_text": "Text eingeben...",
|
||||||
"size_label": "Größe",
|
"size_label": "Größe",
|
||||||
"create_meme": "Memel erstellen:"
|
"create_meme": "Memel erstellen:",
|
||||||
|
"custom_template_title": "Eigene Vorlage nutzen",
|
||||||
|
"custom_template_tag": "Lokale Datei",
|
||||||
|
"select_image": "Eigenes Bild auswählen",
|
||||||
|
"choose_file_btn": "Bild aussuchen"
|
||||||
},
|
},
|
||||||
"timeago": {
|
"timeago": {
|
||||||
"just_now": "gerade eben",
|
"just_now": "gerade eben",
|
||||||
|
|||||||
@@ -29,6 +29,30 @@ export default (router, tpl) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Custom meme template page
|
||||||
|
router.get(/^\/meme\/custom$/, lib.userauth, async (req, res) => {
|
||||||
|
if (!cfg.websrv.meme_creator) {
|
||||||
|
res.writeHead(404).end('Not Found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.reply({
|
||||||
|
body: tpl.render('meme-creator', {
|
||||||
|
template: {
|
||||||
|
id: 'custom',
|
||||||
|
name: 'Custom Template',
|
||||||
|
url: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" viewBox="0 0 800 600"><rect width="800" height="600" fill="%231a1a1b"/><text x="50%25" y="50%25" fill="%23888888" font-family="sans-serif" font-size="24" dominant-baseline="middle" text-anchor="middle">Click %22Choose Image%22 or Drag and Drop here</text></svg>',
|
||||||
|
category: 'Custom',
|
||||||
|
sub_category: ''
|
||||||
|
},
|
||||||
|
page_meta: {
|
||||||
|
title: 'Create Meme - Custom Template',
|
||||||
|
description: 'Create a meme using your own custom template',
|
||||||
|
url: `https://${cfg.main.url.domain}/meme/custom`
|
||||||
|
}
|
||||||
|
}, req)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Meme creator page
|
// Meme creator page
|
||||||
router.get(/^\/meme\/(?<id>[a-z0-9-]+)$/, lib.userauth, async (req, res) => {
|
router.get(/^\/meme\/(?<id>[a-z0-9-]+)$/, lib.userauth, async (req, res) => {
|
||||||
if (!cfg.websrv.meme_creator) {
|
if (!cfg.websrv.meme_creator) {
|
||||||
|
|||||||
@@ -32,6 +32,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="meme-controls">
|
<div class="meme-controls">
|
||||||
|
<div id="customTemplateSelector" class="form-group" style="margin-bottom: 20px;">
|
||||||
|
<label for="customTemplateFile">{{ t('meme.select_image') }}</label>
|
||||||
|
<input type="file" id="customTemplateFile" accept="image/*" style="display: none;">
|
||||||
|
<button id="selectCustomFileBtn" type="button" class="btn btn-secondary btn-block">
|
||||||
|
<i class="fa fa-image"></i> {{ t('meme.choose_file_btn') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="textLayersContainer">
|
<div id="textLayersContainer">
|
||||||
<!-- Dynamic inputs injected by JS -->
|
<!-- Dynamic inputs injected by JS -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="template-grid">
|
<div class="template-grid">
|
||||||
|
<a href="/meme/custom" class="template-item custom-template-card" data-category="All">
|
||||||
|
<div class="template-image-wrapper" style="background: var(--nav-bg, #2b2b2b); border-bottom: 1px solid rgba(255,255,255,0.05);">
|
||||||
|
<i class="fa fa-upload" style="font-size: 3.5rem; color: var(--accent, #9f0);"></i>
|
||||||
|
</div>
|
||||||
|
<div class="template-info">
|
||||||
|
<span class="template-name">{{ t('meme.custom_template_title') }}</span>
|
||||||
|
<span class="template-category-tag">{{ t('meme.custom_template_tag') }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
@each(templates as template)
|
@each(templates as template)
|
||||||
<a href="/meme/{{ template.id }}" class="template-item" data-category="{!! template.category || 'General' !!}">
|
<a href="/meme/{{ template.id }}" class="template-item" data-category="{!! template.category || 'General' !!}">
|
||||||
<div class="template-image-wrapper">
|
<div class="template-image-wrapper">
|
||||||
|
|||||||
Reference in New Issue
Block a user