517 lines
16 KiB
HTML
517 lines
16 KiB
HTML
{% extends 'h_tmp_usr/z_tmp.html' %}
|
|
|
|
{% block css %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='f_contact/form.css') }}">
|
|
|
|
<!-- {# datatables #} -->
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/2.2.2/css/dataTables.bootstrap5.css">
|
|
|
|
<!-- {# i jconfirm #} -->
|
|
<link rel="stylesheet" href="https://htmlguyllc.github.io/jConfirm/jConfirm.min.css">
|
|
<!-- {# f jconfirm #} -->
|
|
{% endblock css %}
|
|
|
|
{% block body %}
|
|
|
|
|
|
|
|
|
|
<style>
|
|
/* estilos del carousel */
|
|
@media screen and (orientation: landscape) {
|
|
.carousel img {
|
|
height: 80vh;
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
|
|
.carousel-item video {
|
|
width: 100%;
|
|
height: 80vh;
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
.carousel-item {
|
|
position: relative;
|
|
overflow: hidden;
|
|
/* height: 100vh; */
|
|
}
|
|
.carousel-inner {
|
|
overflow: hidden;
|
|
}
|
|
|
|
.carousel-caption.centered {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.carousel-caption.centered .caption-content {
|
|
padding: 1rem 2rem;
|
|
border-radius: 10px;
|
|
text-align: center;
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
|
pointer-events: auto;
|
|
width: max-content;
|
|
max-width: 90%;
|
|
}
|
|
|
|
.carousel-caption.centered .caption-content h2 {
|
|
font-size: clamp(1.5rem, 5vw, 3rem);
|
|
margin: 0;
|
|
text-shadow: 1px 1px 4px rgba(0,0,0,0.6);
|
|
}
|
|
|
|
/* ------------------ */
|
|
/* Aplica a ambas: imágenes y videos */
|
|
.carousel-item img,
|
|
.carousel-item video {
|
|
/* width: 100%; */
|
|
/* height: 100%; */
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
<style>
|
|
/* Smartphones (hasta 767px) */
|
|
@media (max-width: 767px) {
|
|
/* main{ background-color: black; } */
|
|
#myTabContent{
|
|
min-height: 80dvh;
|
|
}
|
|
}
|
|
|
|
/* Tablets (768px - 1023px) */
|
|
@media (min-width: 768px) and (max-width: 1023px) {
|
|
/* main{ background-color: pink; } */
|
|
#myTabContent{
|
|
min-height: 80dvh;
|
|
}
|
|
}
|
|
|
|
/* Laptops (1024px - 1439px) monitores resulición baja */
|
|
@media (min-width: 1024px) and (max-width: 1439px) {
|
|
/* main{ background-color: purple; } */
|
|
#myTabContent{
|
|
min-height: 80dvh;
|
|
}
|
|
}
|
|
|
|
/* PCs de escritorio (1440px - 1919px) macbook */
|
|
@media (min-width: 1440px) and (max-width: 1919px) {
|
|
/* main{ background-color: greenyellow; } */
|
|
|
|
#myTabContent{
|
|
min-height: 80dvh;
|
|
}
|
|
|
|
}
|
|
|
|
/* Pantallas Ultrawide (1920px en adelante) */
|
|
@media (min-width: 1920px) {
|
|
/* main{ background-color: red; } */
|
|
|
|
#myTabContent{
|
|
min-height: 80dvh;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<style>
|
|
|
|
/* Estilo responsive para móviles */
|
|
@media screen and (max-width: 768px) {
|
|
#tblCarousel {
|
|
width: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
#tblCarousel_wrapper {
|
|
padding: 0 !important;
|
|
margin: 0 -0.5rem; /* Compensa el padding de las celdas */
|
|
}
|
|
|
|
#tblCarousel thead {
|
|
display: none;
|
|
}
|
|
|
|
#tblCarousel tbody {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
#tblCarousel tr {
|
|
display: block;
|
|
width: calc(100% - 2rem); /* Resta el padding horizontal */
|
|
margin: 0.5rem 1rem 1rem;
|
|
padding: 1rem;
|
|
border: 1px solid #ddd;
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
background-color: white;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#tblCarousel td {
|
|
display: block;
|
|
width: 100%;
|
|
position: relative;
|
|
padding: 0.75rem 1rem 0.75rem 7.5rem; /* Más espacio para etiquetas */
|
|
border: none;
|
|
border-bottom: 1px solid #eee;
|
|
text-align: left;
|
|
word-break: break-word;
|
|
box-sizing: border-box;
|
|
min-height: 3rem;
|
|
}
|
|
|
|
#tblCarousel td::before {
|
|
content: attr(data-label);
|
|
position: absolute;
|
|
top: 0.75rem;
|
|
left: 1rem;
|
|
width: 6rem; /* Ancho fijo para etiquetas */
|
|
font-weight: 600;
|
|
color: #444;
|
|
font-size: 0.85rem;
|
|
white-space: normal;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
#tblCarousel td:last-child {
|
|
border-bottom: none;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.field_btns {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.field_btns a {
|
|
flex: 0 1 calc(50% - 0.5rem); /* Dos botones por fila */
|
|
min-width: 0;
|
|
text-align: center;
|
|
padding: 0.375rem 0.5rem;
|
|
}
|
|
|
|
/* Manejo específico de columnas con contenido largo */
|
|
#tblCarousel td[data-label="Archivo"],
|
|
#tblCarousel td[data-label="Metadatos"] {
|
|
white-space: normal;
|
|
max-height: none;
|
|
overflow: visible;
|
|
}
|
|
|
|
/* Ajuste para filas con menos contenido */
|
|
#tblCarousel td[data-label="ID"],
|
|
#tblCarousel td[data-label="New Tab"] {
|
|
min-height: 2.5rem;
|
|
}
|
|
}
|
|
|
|
</style>
|
|
|
|
<style>
|
|
|
|
#dt-length-0 {
|
|
width: 6em;
|
|
margin-top: 1em;
|
|
}
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#home" type="button" role="tab"
|
|
aria-controls="home" aria-selected="true"><i class="bi bi-file-earmark-slides-fill"></i> Slides</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#profile" type="button" role="tab"
|
|
aria-controls="profile" aria-selected="false"><i class="bi bi-database-fill-up"></i> Añadir Slide</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="contact-tab" data-bs-toggle="tab" data-bs-target="#contact" type="button" role="tab"
|
|
aria-controls="contact" aria-selected="false"><i class="bi bi-play-circle-fill"></i> Carousel</button>
|
|
</li>
|
|
</ul>
|
|
<div class="tab-content" id="myTabContent">
|
|
<div class="tab-pane fade show active table-responsive" id="home" role="tabpanel" aria-labelledby="home-tab">
|
|
<!-- {# --------------------------------------------------------------- #} -->
|
|
<!-- {# i tabla slides #} -->
|
|
<table id="tblCarousel" class="table table-striped display responsive nowrap" style="width:100%">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Metadatos</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for ele in data %}
|
|
<tr data-id="{{ ele[0] }}">
|
|
<td data-label="ID">{{ loop.index }}</td>
|
|
|
|
<td data-label="Metadatos" style="background-color: {{ ele[2] }}; color: {{ ele[3] }};">
|
|
Texto {{ ele[4][:60] }}... <br>
|
|
Fecha creado: {{ ele[5] }} <br>
|
|
Archivo: {{ ele[1] }} <br>
|
|
New Tab: {% if ele[7] %}✔️{% else %}❌{% endif %}
|
|
</td>
|
|
<td data-label="Acciones">
|
|
<div class="field_btns">
|
|
<a href="{{ url_for('download_file', filename = ele[1] ) }}" class="btn btn-dark" style="color: white;">
|
|
<i class="bi bi-download"></i>
|
|
</a>
|
|
<a href="#" class="btn btn-danger delete-btn" data-id="{{ ele[0] }}">
|
|
<i class="bi bi-trash"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- {# f tabla slides #} -->
|
|
<!-- {# --------------------------------------------------------------- #} -->
|
|
</div>
|
|
<div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">
|
|
<!-- {# --------------------------------------------------------------- #} -->
|
|
<!-- {# i form add slide #} -->
|
|
<!-- {# inicio form #} -->
|
|
<div class="form-container" data-aos="fade-down" data-aos-delay="0" data-aos-duration="800"
|
|
data-aos-easing="ease-in-out">
|
|
<div class="form-header">
|
|
<h2>Agregar Slide</h2>
|
|
</div>
|
|
|
|
<form method="POST" action="{{ url_for('carousel') }}" enctype="multipart/form-data" class="needs-validation"
|
|
novalidate>
|
|
{{ form.hidden_tag() }}
|
|
|
|
<!-- Fila 1: Campos de imagen y colores -->
|
|
<div class="row">
|
|
<!-- Input imagen - Ocupa todo el ancho en móviles, mitad en pantallas medianas/grandes -->
|
|
<div class="col-12 col-md-6 mb-3">
|
|
{{ form.img.label(class="form-label fw-bold") }}
|
|
{{ form.img(class="form-control") }}
|
|
<div class="invalid-feedback">
|
|
Por favor selecciona un archivo válido.
|
|
</div>
|
|
<small class="form-text text-muted">
|
|
Formatos soportados: JPG, JPEG, PNG, MP4, AVIF Y WEBP.
|
|
</small>
|
|
</div>
|
|
|
|
<!-- bg color picker - Apila en móviles, 2 por fila en pantallas medianas/grandes -->
|
|
<div class="col-6 col-md-3 mb-3">
|
|
{{ form.bg_color.label(class="form-label fw-bold") }}
|
|
<div class="input-group">
|
|
{{ form.bg_color(type="color", class="form-control form-control-color", value="#ffffff") }}
|
|
<span class="input-group-text">{{ form.bg_color.data }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- txt color picker -->
|
|
<div class="col-6 col-md-3 mb-3">
|
|
{{ form.txt_color.label(class="form-label fw-bold") }}
|
|
<div class="input-group">
|
|
{{ form.txt_color(type="color", class="form-control form-control-color", value="#ffffff") }}
|
|
<span class="input-group-text">{{ form.txt_color.data }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Fila 2: Campos de texto y URL -->
|
|
<div class="row">
|
|
<!-- txt - Ocupa todo el ancho en móviles, 2/3 en pantallas medianas/grandes -->
|
|
<div class="col-12 col-md-8 mb-3">
|
|
{{ form.txt.label(class="form-label fw-bold") }}
|
|
{{ form.txt(class="form-control", maxlength="350", rows="3", placeholder="Texto que aparecerá en el slide") }}
|
|
<div class="form-text">
|
|
<span id="txt-counter">0</span>/350 caracteres
|
|
</div>
|
|
</div>
|
|
|
|
<!-- url - Ocupa todo el ancho en móviles, 1/3 en pantallas medianas/grandes -->
|
|
<div class="col-12 col-md-4 mb-3">
|
|
{{ form.url.label(class="form-label fw-bold") }}
|
|
{{ form.url(class="form-control", maxlength="250", placeholder="https://ejemplo.com") }}
|
|
<div class="form-text">
|
|
<span id="url-counter">0</span>/250 caracteres
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Switch para new tab -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="form-check form-switch">
|
|
{{ form.isNewTab(class="form-check-input", type="checkbox", role="switch") }}
|
|
{{ form.isNewTab.label(class="form-check-label fw-bold") }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Botón de submit -->
|
|
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
|
<button type="submit" class="btn btn-primary btn-lg">
|
|
<i class="bi bi-database-fill-up me-2"></i>Guardar Slide
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- JavaScript para contadores de caracteres -->
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Contador para el campo de texto
|
|
const txtField = document.querySelector('#{{ form.txt.id }}');
|
|
const txtCounter = document.querySelector('#txt-counter');
|
|
|
|
txtField.addEventListener('input', function () {
|
|
txtCounter.textContent = this.value.length;
|
|
});
|
|
|
|
// Contador para el campo URL
|
|
const urlField = document.querySelector('#{{ form.url.id }}');
|
|
const urlCounter = document.querySelector('#url-counter');
|
|
|
|
urlField.addEventListener('input', function () {
|
|
urlCounter.textContent = this.value.length;
|
|
});
|
|
|
|
// Validación del formulario
|
|
const forms = document.querySelectorAll('.needs-validation');
|
|
|
|
Array.from(forms).forEach(form => {
|
|
form.addEventListener('submit', event => {
|
|
if (!form.checkValidity()) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
form.classList.add('was-validated');
|
|
}, false);
|
|
});
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
<!-- {# final form #} -->
|
|
<!-- {# f form add slide #} -->
|
|
<!-- {# --------------------------------------------------------------- #} -->
|
|
|
|
</div>
|
|
<div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">
|
|
<!-- {# --------------------------------------------------------------- #} -->
|
|
<!-- {# i carousel #} -->
|
|
<div id="carouselExampleCaptions" class="carousel slide" data-bs-ride="carousel" data-bs-touch="true">
|
|
<!-- <div id="carouselExampleCaptions" class="carousel slide" data-bs-ride="carousel"> -->
|
|
<div class="carousel-indicators">
|
|
{% for s in data %}
|
|
<button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="{{ loop.index0 }}"
|
|
class="{% if loop.first %}active{% endif %}" aria-current="{{ 'true' if loop.first else 'false' }}"
|
|
aria-label="Slide {{ loop.index }}"></button>
|
|
{% endfor %}
|
|
</div>
|
|
<div class="carousel-inner">
|
|
<!-- {# inicio card item slide #} -->
|
|
{% for s in data %}
|
|
<div class="carousel-item {% if loop.first %}active{% endif %}">
|
|
{% if s[1].endswith('.mp4') %}
|
|
<video class="d-block w-100" autoplay muted loop playsinline>
|
|
<source src="{{ url_for('static', filename='uploads/' + s[1]) }}" type="video/mp4">
|
|
Tu navegador no soporta el video.
|
|
</video>
|
|
{% else %}
|
|
<img src="{{ url_for('static', filename='uploads/' + s[1]) }}" class="d-block w-100" alt="...">
|
|
{% endif %}
|
|
<div class="carousel-caption centered">
|
|
<div class="caption-content" style="background-color: {{ s[2] }}; color: {{ s[3] }};">
|
|
<h2>{{ s[4] }}</h2>
|
|
{% if s[6] is not none %}
|
|
<a href="{{ s[6] }}" type="button" class="btn btn-success" target="_blank">ver más</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
<!-- {# fin card item slide #} -->
|
|
</div>
|
|
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleCaptions"
|
|
data-bs-slide="prev">
|
|
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
|
<span class="visually-hidden">Previous</span>
|
|
</button>
|
|
<button class="carousel-control-next" type="button" data-bs-target="#carouselExampleCaptions"
|
|
data-bs-slide="next">
|
|
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
|
<span class="visually-hidden">Next</span>
|
|
</button>
|
|
</div>
|
|
<!-- {# f carousel #} -->
|
|
<!-- {# --------------------------------------------------------------- #} -->
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{% endblock body %}
|
|
|
|
{% block js %}
|
|
|
|
<!-- {# js databases #} -->
|
|
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
|
|
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script> -->
|
|
<script src="https://cdn.datatables.net/2.2.2/js/dataTables.js"></script>
|
|
<script src="https://cdn.datatables.net/2.2.2/js/dataTables.bootstrap5.js"></script>
|
|
|
|
<!-- jQuery y jConfirm -->
|
|
<!-- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> -->
|
|
<script src="https://htmlguyllc.github.io/jConfirm/jConfirm.min.js"></script>
|
|
|
|
|
|
|
|
|
|
<!-- {# if flash #} -->
|
|
{% include 'z_comps/if_flash.html' %}
|
|
|
|
<!-- {# aos script #} -->
|
|
{% include 'z_comps/aos_script.html' %}
|
|
|
|
<!-- {# validador archivos extensión #} -->
|
|
<script type="module" src="{{ url_for('static', filename='h_tmp_user/i_carousel_form/carousel_form.js') }}"></script>
|
|
|
|
<script type="module" src="{{ url_for('static', filename='h_tmp_user/i_carousel_form/delete_slide.js') }}"></script>
|
|
|
|
<script src="{{ url_for('static', filename='h_tmp_user/i_carousel_form/datatable_carousel.js') }}"></script>
|
|
|
|
|
|
{% endblock js %} |