mirror of
https://github.com/Radiquum/photos.git
synced 2025-04-05 15:54:31 +00:00
feat/admin: add edit ui
This commit is contained in:
parent
5e3116d46f
commit
af38ba70fc
4 changed files with 286 additions and 3 deletions
|
@ -2,7 +2,7 @@ import json
|
|||
import PIL
|
||||
import PIL.Image
|
||||
import boto3.session
|
||||
from flask import Flask, render_template, request, Response
|
||||
from flask import Flask, render_template, request, Response, redirect, url_for
|
||||
from werkzeug.utils import secure_filename
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
@ -63,7 +63,7 @@ def Home():
|
|||
|
||||
objects.append({"name": name, "img": img, **obj, "date": date})
|
||||
|
||||
return render_template("Index.html", objects=objects, page_title="Home")
|
||||
return render_template("Index.html", objects=objects, page_title="Home", s3_endpoint=os.getenv("AWS_ENDPOINT"), s3_bucket=os.getenv("AWS_BUCKET"))
|
||||
|
||||
|
||||
@app.route("/upload/")
|
||||
|
@ -217,3 +217,19 @@ def ApiUpload():
|
|||
os.remove(os.path.join(app.config["UPLOAD_FOLDER"], f"{file_path}-24px.{file_ext}"))
|
||||
|
||||
return {"status": "ok", "message": "Uploaded"}
|
||||
|
||||
|
||||
@app.route("/edit/<string:id>", methods=["GET"])
|
||||
def Edit(id):
|
||||
document = db.collection(os.getenv("PREFIX")).document(id).get()
|
||||
if document.exists:
|
||||
name = document.id
|
||||
img = f"{document.id.split(".")[0]}/{document.id}"
|
||||
|
||||
obj = document.to_dict()
|
||||
tags = obj["tags"]
|
||||
urls = obj["urls"]
|
||||
date = datetime.fromtimestamp(float(float(str(obj['date'])[:10]))).strftime("%d/%m/%Y")
|
||||
return render_template("edit.html", name=name, img=img, tags=":".join(tags), urls=urls, date=date, page_title=f"Edit - {name}", s3_endpoint=os.getenv("AWS_ENDPOINT"), s3_bucket=os.getenv("AWS_BUCKET"))
|
||||
else:
|
||||
return redirect(url_for("Home"))
|
|
@ -1140,6 +1140,9 @@
|
|||
.static {
|
||||
position: static;
|
||||
}
|
||||
.sticky {
|
||||
position: sticky;
|
||||
}
|
||||
.inset-0 {
|
||||
inset: calc(var(--spacing) * 0);
|
||||
}
|
||||
|
@ -1492,6 +1495,9 @@
|
|||
margin-left: e !important;
|
||||
}
|
||||
}
|
||||
.mr-2 {
|
||||
margin-right: calc(var(--spacing) * 2);
|
||||
}
|
||||
.apexcharts-tooltip-title {
|
||||
.apexcharts-canvas .apexcharts-tooltip & {
|
||||
padding-top: 0.5rem !important;
|
||||
|
@ -1741,6 +1747,9 @@
|
|||
.w-128 {
|
||||
width: calc(var(--spacing) * 128);
|
||||
}
|
||||
.w-\[296px\] {
|
||||
width: 296px;
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -1950,6 +1959,9 @@
|
|||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.rounded-full {
|
||||
border-radius: calc(infinity * 1px);
|
||||
}
|
||||
|
@ -2214,6 +2226,9 @@
|
|||
.bg-black\/75 {
|
||||
background-color: color-mix(in oklab, var(--color-black) 75%, transparent);
|
||||
}
|
||||
.bg-blue-100 {
|
||||
background-color: var(--color-blue-100);
|
||||
}
|
||||
.bg-blue-700 {
|
||||
background-color: var(--color-blue-700);
|
||||
}
|
||||
|
@ -2443,9 +2458,15 @@
|
|||
.px-2 {
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
}
|
||||
.px-2\.5 {
|
||||
padding-inline: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
.px-5 {
|
||||
padding-inline: calc(var(--spacing) * 5);
|
||||
}
|
||||
.py-0\.5 {
|
||||
padding-block: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
.py-1 {
|
||||
padding-block: calc(var(--spacing) * 1);
|
||||
}
|
||||
|
@ -2665,6 +2686,9 @@
|
|||
.text-blue-600 {
|
||||
color: var(--color-blue-600);
|
||||
}
|
||||
.text-blue-800 {
|
||||
color: var(--color-blue-800);
|
||||
}
|
||||
.text-gray-200 {
|
||||
color: var(--color-gray-200);
|
||||
}
|
||||
|
@ -2961,6 +2985,11 @@
|
|||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
.dark\:bg-blue-200 {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: var(--color-blue-200);
|
||||
}
|
||||
}
|
||||
.dark\:bg-blue-600 {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: var(--color-blue-600);
|
||||
|
@ -3006,6 +3035,11 @@
|
|||
color: var(--color-blue-500);
|
||||
}
|
||||
}
|
||||
.dark\:text-blue-800 {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: var(--color-blue-800);
|
||||
}
|
||||
}
|
||||
.dark\:text-gray-300 {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: var(--color-gray-300);
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
<div class="w-full h-128 bg-slate-800 p-2 relative">
|
||||
<a href="/edit/{{ object.name }}">
|
||||
<img class="absolute w-full h-full inset-0 object-cover"
|
||||
src="https://s3.tebi.io/radiquum-photos/{{ object.img }}" alt="" />
|
||||
src="{{ s3_endpoint }}/{{ s3_bucket }}/{{ object.img }}" alt="" />
|
||||
</a>
|
||||
<div
|
||||
class="absolute bottom-0 left-0 w-full px-2 pb-2 pt-16 bg-gradient-to-t from-black/90 to-transparent to-95%">
|
||||
|
|
233
admin/templates/edit.html
Normal file
233
admin/templates/edit.html
Normal file
|
@ -0,0 +1,233 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
<form id="upload-form" class="py-2 border border-gray-700 px-2 rounded-lg flex items-start gap-4"
|
||||
enctype="multipart/form-data">
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<img src="{{ s3_endpoint }}/{{ s3_bucket }}/{{ img }}" alt="" class="w-[296px] object-scale-down rounded-lg" />
|
||||
<div id="datepicker-inline" inline-datepicker datepicker-buttons datepicker-format="dd/mm/yyyy"
|
||||
datepicker-title="Shoot Date" data-date="{{ date }}"></div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
|
||||
<div class="w-full">
|
||||
<label for="tags-input" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Tags</label>
|
||||
<input type="text" id="tags-input"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="Winter" />
|
||||
<div class="w-full flex gap-2 mt-2 flex-wrap" id="tags-container">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<label for="url-input" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Urls</label>
|
||||
<input type="text" id="url-input"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="https://www.example.com" />
|
||||
<div class="w-full flex flex-wrap gap-2 mt-2" id="url-container">
|
||||
{% for url in urls %}
|
||||
<input data-name="{{url.name}}" type="text"
|
||||
class="w-full bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
value="{{ url.value }}" />
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-wrap gap-2">
|
||||
<button type="submit" id="btn-submit"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Save</button>
|
||||
<button type="button" id="btn-delete"
|
||||
class="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
|
||||
let tags = "{{ tags }}".split(':') || [];
|
||||
let urls = [];
|
||||
let datepicker = null;
|
||||
|
||||
const tagInp = document.getElementById('tags-input')
|
||||
const tagCon = document.getElementById('tags-container')
|
||||
const urlInp = document.getElementById('url-input')
|
||||
const urlCon = document.getElementById('url-container')
|
||||
|
||||
window.onload = () => {
|
||||
tags.forEach((tag) => {
|
||||
const tagEl = document.createElement('div');
|
||||
tagEl.classList.add('bg-gray-600', 'rounded-lg', 'p-2', 'flex', 'items-center', 'gap-2');
|
||||
tagEl.innerHTML = `${tag} <button class="rounded-full bg-gray-800 w-6 h-6 text-sm cursor-pointer" type="button" onclick="removeTag('${tag}')">X</button>`;
|
||||
tagCon.appendChild(tagEl);
|
||||
})
|
||||
|
||||
const urlsEls = document.querySelectorAll('#url-container input');
|
||||
urlsEls.forEach((input) => {
|
||||
urls.push({ "name": input.dataset.name, "value": input.value });
|
||||
|
||||
input.onchange = (e) => {
|
||||
const target = urls.find(url => url.name === e.target.dataset.name);
|
||||
const index = urls.indexOf(url => url.name === e.target.dataset.name);
|
||||
if (e.target.value.trim() === '') {
|
||||
urls.splice(index, 1);
|
||||
urlCon.removeChild(e.target);
|
||||
return;
|
||||
}
|
||||
if (target) {
|
||||
target.value = e.target.value.trim();
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
datepicker = FlowbiteInstances.getInstance('Datepicker', 'datepicker-inline')
|
||||
}
|
||||
|
||||
function removeTag(tag) {
|
||||
tags.splice(tags.indexOf(tag), 1);
|
||||
tagCon.innerHTML = '';
|
||||
tags.forEach(tag => {
|
||||
const tagEl = document.createElement('div');
|
||||
tagEl.classList.add('bg-gray-600', 'rounded-lg', 'p-2', 'flex', 'items-center', 'gap-2');
|
||||
tagEl.innerHTML = `${tag} <button class="rounded-full bg-gray-800 w-6 h-6 text-sm cursor-pointer" type="button" onclick="removeTag('${tag}')">X</button>`;
|
||||
tagCon.appendChild(tagEl);
|
||||
});
|
||||
};
|
||||
|
||||
// const tagSearch = ["Winter", "Summer", "Spring", "Autumn"];
|
||||
|
||||
tagInp.addEventListener('input', (e) => {
|
||||
if (e.target.value.includes(',')) {
|
||||
const tag = e.target.value.split(',')[0].trim().replaceAll(" ", "_").toLowerCase();
|
||||
if (tags.includes(tag) || !tag) {
|
||||
tagInp.value = '';
|
||||
return;
|
||||
};
|
||||
|
||||
tags.push(tag);
|
||||
tagInp.value = '';
|
||||
tagCon.innerHTML = '';
|
||||
tags.forEach(tag => {
|
||||
const tagEl = document.createElement('div');
|
||||
tagEl.classList.add('bg-gray-600', 'rounded-lg', 'p-2', 'flex', 'items-center', 'gap-2');
|
||||
tagEl.innerHTML = `${tag} <button class="rounded-full bg-gray-800 w-6 h-6 text-sm cursor-pointer" type="button" onclick="removeTag('${tag}')">X</button>`;
|
||||
tagCon.appendChild(tagEl);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
urlInp.addEventListener('keyup', (e) => {
|
||||
if (e.code == 'Enter') {
|
||||
if (!e.target.value.startsWith('http')) { alert('Invalid URL'); return; }
|
||||
if (!e.target.value.split('/')[2].includes(".")) { alert('Invalid URL'); return; }
|
||||
if (urls.find(url => url.value === e.target.value.trim())) { e.target.value = ''; return; }
|
||||
|
||||
const urlEl = document.createElement('input');
|
||||
urlEl.type = 'text';
|
||||
urlEl.classList.add('bg-gray-50', 'border', 'border-gray-300', 'text-gray-900', 'text-sm', 'rounded-lg', 'focus:ring-blue-500', 'focus:border-blue-500', 'block', 'w-full', 'p-2.5', 'dark:bg-gray-700', 'dark:border-gray-600', 'dark:placeholder-gray-400', 'dark:text-white', 'dark:focus:ring-blue-500', 'dark:focus:border-blue-500');
|
||||
urlEl.value = e.target.value.trim();
|
||||
urlEl.dataset.name = `${e.target.value.split('/')[2].split('.')[0]}`;
|
||||
urlEl.onchange = (e) => {
|
||||
const target = urls.find(url => url.name === e.target.dataset.name);
|
||||
const index = urls.indexOf(url => url.name === e.target.dataset.name);
|
||||
if (e.target.value.trim() === '') {
|
||||
urls.splice(index, 1);
|
||||
urlCon.removeChild(e.target);
|
||||
return;
|
||||
}
|
||||
if (target) {
|
||||
target.value = e.target.value.trim();
|
||||
return;
|
||||
}
|
||||
}
|
||||
urls.push({
|
||||
name: e.target.value.split('/')[2].split('.')[0],
|
||||
value: e.target.value.trim()
|
||||
})
|
||||
urlCon.appendChild(urlEl)
|
||||
|
||||
e.target.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
const btnDelete = document.getElementById('btn-delete');
|
||||
const btnSubmit = document.getElementById('btn-submit');
|
||||
|
||||
// btnReset.addEventListener('click', () => {
|
||||
// tagInp.value = '';
|
||||
// tagCon.innerHTML = '';
|
||||
// tags = [];
|
||||
// urlInp.value = '';
|
||||
// urlCon.innerHTML = '';
|
||||
// urls = [];
|
||||
// datepicker.setDate("today");
|
||||
// });
|
||||
|
||||
// const form = document.getElementById('upload-form');
|
||||
// form.addEventListener('keydown', (e) => {
|
||||
// if (e.key === "Enter") {
|
||||
// e.preventDefault(); // Prevent form submission
|
||||
// }
|
||||
// });
|
||||
// form.addEventListener('submit', (e) => {
|
||||
// e.preventDefault();
|
||||
|
||||
// const formFields = form.elements;
|
||||
// const formData = new FormData(form);
|
||||
// if (!formFields['file'].files.length > 0) {
|
||||
// alert('Please select a file');
|
||||
// return;
|
||||
// }
|
||||
// if (!datepicker.getDate()) {
|
||||
// alert('Please select a date');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// let convertedUrls = "";
|
||||
// urls.forEach(url => {
|
||||
// convertedUrls += `{"name":"${url.name}","value":"${url.value}"};`;
|
||||
// });
|
||||
// formData.set('file', formFields['file'].files[0]);
|
||||
// formData.set('alt', formFields['alt'].value.trim());
|
||||
// formData.append('tags', tags.toString());
|
||||
// formData.append('urls', convertedUrls.toString());
|
||||
// formData.append('date', datepicker.getDatepickerInstance().picker.viewDate);
|
||||
|
||||
// for (let pair of formData.entries()) {
|
||||
// if (pair[0] == "urls") {
|
||||
// console.log(`${pair[0]}:`, urls);
|
||||
// } else {
|
||||
// console.log(`${pair[0]}: ${pair[1]}`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// btnSubmit.setAttribute('disabled', true);
|
||||
|
||||
// fetch('{{ url_for("ApiUpload") }}', {
|
||||
// method: 'POST',
|
||||
// body: formData
|
||||
// })
|
||||
// .then(res => {
|
||||
// return res.json();
|
||||
// })
|
||||
// .then(data => {
|
||||
// console.log(data);
|
||||
// btnSubmit.removeAttribute('disabled');
|
||||
// alert(data.message);
|
||||
// if (data.status != "error") {
|
||||
// window.location.href = "{{ url_for('Home') }}";
|
||||
// }
|
||||
// })
|
||||
// .catch(err => {
|
||||
// console.log(err);
|
||||
// btnSubmit.removeAttribute('disabled');
|
||||
// alert(err.message);
|
||||
// })
|
||||
// });
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
Loading…
Add table
Reference in a new issue