diff --git a/public/s/css/meme-creator.css b/public/s/css/meme-creator.css index ecfd276..d3748a5 100644 --- a/public/s/css/meme-creator.css +++ b/public/s/css/meme-creator.css @@ -260,3 +260,26 @@ canvas#memeCanvas { 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; +} + diff --git a/public/s/js/meme-creator.js b/public/s/js/meme-creator.js index 8ad59e3..84c918f 100644 --- a/public/s/js/meme-creator.js +++ b/public/s/js/meme-creator.js @@ -82,11 +82,23 @@ const defaultSize = 40; - // Initial layers - textLayers = [ - { 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 } - ]; + // Initial layers - only set if we don't have any layers yet + if (textLayers.length === 0) { + textLayers = [ + { 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 } + ]; + } 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(); 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 setTimeout(draw, 300); } diff --git a/src/inc/locales/de.json b/src/inc/locales/de.json index 43ebda6..fd3404b 100644 --- a/src/inc/locales/de.json +++ b/src/inc/locales/de.json @@ -528,7 +528,11 @@ "text_layer": "Textebene", "enter_text": "Text eingeben...", "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": { "just_now": "gerade eben", diff --git a/src/inc/locales/en.json b/src/inc/locales/en.json index e0109d1..1c0a5e6 100644 --- a/src/inc/locales/en.json +++ b/src/inc/locales/en.json @@ -532,7 +532,11 @@ "text_layer": "Text Layer", "enter_text": "Enter text...", "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": { "just_now": "just now", diff --git a/src/inc/locales/nl.json b/src/inc/locales/nl.json index c378c2c..640803f 100644 --- a/src/inc/locales/nl.json +++ b/src/inc/locales/nl.json @@ -528,7 +528,11 @@ "text_layer": "Tekstlaag", "enter_text": "Voer tekst in...", "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": { "just_now": "zojuist", diff --git a/src/inc/locales/zange.json b/src/inc/locales/zange.json index 0e2cb93..07da7d5 100644 --- a/src/inc/locales/zange.json +++ b/src/inc/locales/zange.json @@ -531,7 +531,11 @@ "text_layer": "Textebene", "enter_text": "Text eingeben...", "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": { "just_now": "gerade eben", diff --git a/src/inc/routes/meme.mjs b/src/inc/routes/meme.mjs index 1c52462..f3fe4ed 100644 --- a/src/inc/routes/meme.mjs +++ b/src/inc/routes/meme.mjs @@ -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,Click %22Choose Image%22 or Drag and Drop here', + 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 router.get(/^\/meme\/(?[a-z0-9-]+)$/, lib.userauth, async (req, res) => { if (!cfg.websrv.meme_creator) { diff --git a/views/meme-creator.html b/views/meme-creator.html index 1508eef..7be8313 100644 --- a/views/meme-creator.html +++ b/views/meme-creator.html @@ -32,6 +32,14 @@ + + {{ t('meme.select_image') }} + + + {{ t('meme.choose_file_btn') }} + + + diff --git a/views/meme-select.html b/views/meme-select.html index f50a4ff..ab04c8d 100644 --- a/views/meme-select.html +++ b/views/meme-select.html @@ -14,6 +14,15 @@ + + + + + + {{ t('meme.custom_template_title') }} + {{ t('meme.custom_template_tag') }} + + @each(templates as template)