feat: adição de interface web gráfica para gerenciar os slides
This commit is contained in:
1 parent
cc3ba92caa
commit
a8d41b892d
7 files changed
+398
-22
No files matched your search
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
session_start();
|
||||
|
||||
$env = parse_ini_file(__DIR__ . '/../.env');
|
||||
$senha_correta = $env['PASSWORD'] ?? '';
|
||||
|
||||
if (isset($_POST['senha'])) {
|
||||
if ($_POST['senha'] === $senha_correta) {
|
||||
$_SESSION['logado'] = true;
|
||||
} else {
|
||||
$erro = "Senha incorreta.";
|
||||
}
|
||||
}
|
||||
|
||||
if (!($_SESSION['logado'] ?? false)) {
|
||||
?>
|
||||
<form method="POST">
|
||||
<h2>Entrar</h2>
|
||||
<?php if (!empty($erro)) echo "<p style='color:red'>$erro</p>"; ?>
|
||||
<input type="password" name="senha" placeholder="Senha"/>
|
||||
<button type="submit">Entrar</button>
|
||||
</form>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
$jsonPath = __DIR__ . '/slides.json';
|
||||
$imagensDir = __DIR__ . '/imagens/';
|
||||
$htmlPath = __DIR__ . '/index.html';
|
||||
|
||||
$slides = [];
|
||||
if (file_exists($jsonPath)) {
|
||||
$conteudo = file_get_contents($jsonPath);
|
||||
$decodificado = json_decode($conteudo, true);
|
||||
if (is_array($decodificado)) {
|
||||
$slides = $decodificado;
|
||||
}
|
||||
}
|
||||
|
||||
// ADICIONAR SLIDE
|
||||
if (isset($_POST['add'])) {
|
||||
if (!is_dir($imagensDir)) mkdir($imagensDir);
|
||||
|
||||
$imgName = '';
|
||||
if (isset($_FILES['imagem']) && $_FILES['imagem']['error'] === 0) {
|
||||
$ext = strtolower(pathinfo($_FILES['imagem']['name'], PATHINFO_EXTENSION));
|
||||
$permitidos = ['jpg', 'jpeg', 'png', 'webp', 'apng'];
|
||||
if (in_array($ext, $permitidos)) {
|
||||
$imgName = uniqid('img_') . ".$ext";
|
||||
if (!move_uploaded_file($_FILES['imagem']['tmp_name'], "$imagensDir/$imgName")) {
|
||||
die("Erro ao salvar a imagem.");
|
||||
}
|
||||
} else {
|
||||
die("Formato de imagem não permitido.");
|
||||
}
|
||||
} else {
|
||||
die("Erro no envio da imagem.");
|
||||
}
|
||||
|
||||
$slides[] = [
|
||||
'imagem' => $imgName,
|
||||
'texto' => trim($_POST['texto'] ?? ''),
|
||||
'cor' => $_POST['cor'] ?? '',
|
||||
'direcao' => $_POST['direcao'] ?? '',
|
||||
'link' => trim($_POST['link'] ?? ''),
|
||||
];
|
||||
|
||||
if (file_put_contents($jsonPath, json_encode($slides, JSON_PRETTY_PRINT)) === false) {
|
||||
die("Erro ao gravar no slides.json");
|
||||
}
|
||||
|
||||
atualizarHTML($slides, $htmlPath);
|
||||
header("Location: " . basename(__FILE__));
|
||||
exit;
|
||||
}
|
||||
|
||||
// SALVAR ALTERAÇÕES (editar e reordenar)
|
||||
if (isset($_POST['save'])) {
|
||||
$novos_slides = [];
|
||||
$ids = array_keys($slides);
|
||||
$ordem = $_POST['ordem'] ?? [];
|
||||
|
||||
foreach ($ids as $i) {
|
||||
$texto = trim($_POST['texto'][$i] ?? '');
|
||||
$cor = $_POST['cor'][$i] ?? '';
|
||||
$link = trim($_POST['link'][$i] ?? '');
|
||||
$direcao = $_POST['direcao'][$i] ?? '';
|
||||
$ord = intval($ordem[$i] ?? 0);
|
||||
|
||||
$novos_slides[$i] = [
|
||||
'imagem' => $slides[$i]['imagem'],
|
||||
'texto' => $texto,
|
||||
'cor' => $cor,
|
||||
'link' => $link,
|
||||
'direcao' => $direcao,
|
||||
'ordem' => $ord,
|
||||
];
|
||||
}
|
||||
|
||||
usort($novos_slides, function($a, $b) {
|
||||
return ($a['ordem'] ?? 0) <=> ($b['ordem'] ?? 0);
|
||||
});
|
||||
|
||||
foreach ($novos_slides as &$slide) {
|
||||
unset($slide['ordem']);
|
||||
}
|
||||
|
||||
$slides = $novos_slides;
|
||||
|
||||
if (file_put_contents($jsonPath, json_encode($slides, JSON_PRETTY_PRINT)) === false) {
|
||||
die("Erro ao gravar no slides.json");
|
||||
}
|
||||
|
||||
atualizarHTML($slides, $htmlPath);
|
||||
header("Location: " . basename(__FILE__));
|
||||
exit;
|
||||
}
|
||||
|
||||
// EXCLUIR SLIDE
|
||||
if (isset($_GET['delete'])) {
|
||||
$index = (int) $_GET['delete'];
|
||||
if (isset($slides[$index])) {
|
||||
$imgDel = $slides[$index]['imagem'];
|
||||
@unlink("$imagensDir/$imgDel");
|
||||
array_splice($slides, $index, 1);
|
||||
file_put_contents($jsonPath, json_encode($slides, JSON_PRETTY_PRINT));
|
||||
atualizarHTML($slides, $htmlPath);
|
||||
}
|
||||
header("Location: " . basename(__FILE__));
|
||||
exit;
|
||||
}
|
||||
|
||||
function atualizarHTML($slides, $htmlPath) {
|
||||
$novoConteudo = '<div class="slideshow-container">' . PHP_EOL;
|
||||
foreach ($slides as $slide) {
|
||||
$botao = '';
|
||||
if (!empty(trim($slide['texto'] ?? ''))) {
|
||||
$cor = $slide['cor'] ?? '#000';
|
||||
$dir = $slide['direcao'] ?? 'up';
|
||||
$txt = htmlspecialchars($slide['texto']);
|
||||
$link = htmlspecialchars($slide['link'] ?? '#') ?: '#';
|
||||
$botao = "<a data-moan-direction=\"$dir\" class=\"moan-link\" href=\"$link\" style=\"background: $cor;\">$txt</a>";
|
||||
}
|
||||
|
||||
$novoConteudo .= <<<HTML
|
||||
<div class="slide">
|
||||
<img src="./imagens/{$slide['imagem']}" alt=""/>
|
||||
$botao
|
||||
</div>
|
||||
|
||||
HTML;
|
||||
}
|
||||
|
||||
$novoConteudo .= <<<HTML
|
||||
<a class="prev" onclick="plusSlides(-1)">❮</a>
|
||||
<a class="next" onclick="plusSlides(1)">❯</a>
|
||||
|
||||
<div class="dots">
|
||||
HTML;
|
||||
|
||||
foreach ($slides as $i => $_) {
|
||||
$n = $i + 1;
|
||||
$novoConteudo .= "<span class=\"dot\" onclick=\"currentSlide($n)\"></span> ";
|
||||
}
|
||||
|
||||
$novoConteudo .= '</div></div>';
|
||||
|
||||
$html = file_get_contents($htmlPath);
|
||||
|
||||
$novoHtml = preg_replace(
|
||||
'#<div class="slideshow-container">.*?</div>\s*</div>#s',
|
||||
$novoConteudo,
|
||||
$html
|
||||
);
|
||||
|
||||
file_put_contents($htmlPath, $novoHtml);
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Editar Slides</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: linear-gradient(300deg,deepskyblue,darkviolet,blue);
|
||||
background-size: 180% 180%;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
animation: gradient-animation 18s ease infinite;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
|
||||
@keyframes gradient-animation {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, table {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
div.form {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 600px;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 10px auto;
|
||||
display: block;
|
||||
background-color: #263748;;
|
||||
color: white; /* White text */
|
||||
border: none; /* No border */
|
||||
padding: 10px 20px; /* Padding around the text */
|
||||
font-size: 16px; /* Font size */
|
||||
cursor: pointer; /* Changes mouse cursor to a hand on hover */
|
||||
border-radius: 5px; /* Slightly rounded corners */
|
||||
transition: background-color 0.3s ease; /* Smooth transition for hover effect */
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color: #555;
|
||||
transform: translateY(1px); /* Slight downward shift for a "pressed" effect */
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Gerenciar Slides</h1>
|
||||
|
||||
<p style="text-align: center;">Use imagens com a proporção 81/46. As dimensões de exibição são 405px de largura por 230px de altura.</p>
|
||||
|
||||
<div class="form">
|
||||
<form method="POST" enctype="multipart/form-data" action="<?= basename(__FILE__) ?>">
|
||||
<p><input type="file" name="imagem" required></p>
|
||||
<p><input type="text" name="texto" placeholder="Texto do botão (opcional)"></p>
|
||||
<p><input type="text" name="link" placeholder="Link do botão (opcional, ex: https://...)"></p>
|
||||
<p><input type="color" name="cor" value="#000000"></p>
|
||||
<p>
|
||||
<select name="direcao">
|
||||
<option value="up">De cima</option>
|
||||
<option value="from-right">Da direita</option>
|
||||
<option value="from-left">Da esquerda</option>
|
||||
<option value="down">De baixo</option>
|
||||
</select>
|
||||
</p>
|
||||
<p><button type="submit" name="add">Adicionar Slide</button></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Editar slides existentes</h2>
|
||||
|
||||
<form method="POST" action="<?= basename(__FILE__) ?>">
|
||||
<table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ordem</th>
|
||||
<th>Imagem</th>
|
||||
<th>Texto do botão</th>
|
||||
<th>Link do botão</th>
|
||||
<th>Cor do botão</th>
|
||||
<th>Direção</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($slides as $i => $s): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="number" name="ordem[<?= $i ?>]" value="<?= $i + 1 ?>" style="width: 50px;">
|
||||
</td>
|
||||
<td>
|
||||
<img src="imagens/<?= $s['imagem'] ?>" style="height:50px;vertical-align:middle;">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="texto[<?= $i ?>]" value="<?= htmlspecialchars($s['texto']) ?>">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="link[<?= $i ?>]" value="<?= htmlspecialchars($s['link'] ?? '') ?>">
|
||||
</td>
|
||||
<td>
|
||||
<input type="color" name="cor[<?= $i ?>]" value="<?= htmlspecialchars($s['cor'] ?: '#000000') ?>">
|
||||
</td>
|
||||
<td>
|
||||
<select name="direcao[<?= $i ?>]">
|
||||
<?php
|
||||
$dirs = ['up' => 'De cima', 'from-right' => 'Da direita', 'from-left' => 'Da esquerda', 'down' => 'De baixo'];
|
||||
foreach ($dirs as $val => $label): ?>
|
||||
<option value="<?= $val ?>" <?= ($s['direcao'] === $val) ? 'selected' : '' ?>><?= $label ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<a href="?delete=<?= $i ?>" onclick="return confirm('Excluir este slide?')">Excluir</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><button type="submit" name="save">Salvar alterações</button></p>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Destaques da Editora Moan</title>
|
||||
|
||||
<style>
|
||||
* {box-sizing: border-box}
|
||||
body {font-family: Verdana, sans-serif; margin:0}
|
||||
img {vertical-align: middle;}
|
||||
|
||||
@keyframes up {
|
||||
from {margin-bottom: 40px;}
|
||||
to {margin-bottom: 0;}
|
||||
}
|
||||
|
||||
@keyframes down {
|
||||
from {margin-bottom: -40px;}
|
||||
to {margin-bottom: 0;}
|
||||
}
|
||||
|
||||
@keyframes from-left {
|
||||
from {margin-left: -100%;}
|
||||
to {margin-left: 0;}
|
||||
}
|
||||
|
||||
@keyframes from-right {
|
||||
from {margin-left: 100%;}
|
||||
to {margin-left: 0;}
|
||||
}
|
||||
|
||||
/* Slideshow container */
|
||||
.slideshow-container {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
max-width: 405px;
|
||||
height: 56.8vw;
|
||||
max-height: 230px;
|
||||
}
|
||||
|
||||
/* Next & previous buttons */
|
||||
.prev, .next {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: auto;
|
||||
padding: 9px;
|
||||
margin-top: -22px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
transition: 0.6s ease;
|
||||
border-radius: 0 3px 3px 0;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
background-color: rgba(0,0,0,0.35);
|
||||
}
|
||||
|
||||
/* Position the "next button" to the right */
|
||||
.next {
|
||||
right: 0;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
/* On hover, add a black background color with a little bit see-through */
|
||||
.prev:hover, .next:hover {
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
/* Caption text */
|
||||
.text {
|
||||
color: #f2f2f2;
|
||||
font-size: 15px;
|
||||
padding: 8px 12px;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dots {
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.dot {
|
||||
cursor: pointer;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin: 0 2px;
|
||||
background-color: #aaa;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
transition: background-color 0.6s ease;
|
||||
}
|
||||
|
||||
.active, .dot:hover {
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.slide {
|
||||
width: 100%;
|
||||
transition: all .3s ease-in-out;
|
||||
}
|
||||
|
||||
.slide img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
max-width: 405px;
|
||||
height: auto;
|
||||
max-height: 230px;
|
||||
}
|
||||
|
||||
.slide a.moan-link {
|
||||
padding: 5px 10px;
|
||||
white-space: nowrap;
|
||||
background-color: #F0AF2A;
|
||||
position: absolute;
|
||||
z-index: 101;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
font-family: sans-serif;
|
||||
bottom: 40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: large;
|
||||
transition: all .4s ease-in-out;
|
||||
}
|
||||
|
||||
.slide a.moan-link:hover {
|
||||
background-color: #eec26b;
|
||||
}
|
||||
|
||||
.moan-up {
|
||||
animation: up .5s ease-in-out;
|
||||
}
|
||||
|
||||
.moan-down{
|
||||
animation: down .5s ease-in-out;
|
||||
}
|
||||
|
||||
.moan-from-left {
|
||||
animation: from-left .8s ease-in-out;
|
||||
}
|
||||
|
||||
.moan-from-right {
|
||||
animation: from-right .8s ease-in-out;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 310px) {
|
||||
.prev, .next,.text {font-size: 11px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="slideshow-container">
|
||||
<div class="slide">
|
||||
<img src="./imagens/img_688fb8b5c8ee6.webp" alt=""/>
|
||||
<a data-moan-direction="down" class="moan-link" href="#" style="background: #339d08;">Adquira já!</a>
|
||||
</div>
|
||||
<div class="slide">
|
||||
<img src="./imagens/img_688fbff50d93b.webp" alt=""/>
|
||||
<a data-moan-direction="up" class="moan-link" href="#" style="background: #ec606d;">Adquira Já!</a>
|
||||
</div>
|
||||
<a class="prev" onclick="plusSlides(-1)">❮</a>
|
||||
<a class="next" onclick="plusSlides(1)">❯</a>
|
||||
|
||||
<div class="dots"><span class="dot" onclick="currentSlide(1)"></span> <span class="dot" onclick="currentSlide(2)"></span> </div></div>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
let slideIndex = 1;
|
||||
showSlides(slideIndex);
|
||||
|
||||
let slideInterval = setInterval(function() {
|
||||
plusSlides(1);
|
||||
}, 5000);
|
||||
|
||||
document.querySelector('.slideshow-container').addEventListener('mouseenter', function() {
|
||||
clearInterval(slideInterval);
|
||||
});
|
||||
|
||||
document.querySelector('.slideshow-container').addEventListener('mouseleave', function() {
|
||||
slideInterval = setInterval(function() {
|
||||
plusSlides(1);
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
document.querySelector('.dot').addEventListener('mouseenter', function() {
|
||||
clearInterval(slideInterval);
|
||||
});
|
||||
|
||||
document.querySelector('.dot').addEventListener('mouseleave', function() {
|
||||
slideInterval = setInterval(function() {
|
||||
plusSlides(1);
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
function plusSlides(n) {
|
||||
|
||||
showSlides(slideIndex += n);
|
||||
}
|
||||
|
||||
function currentSlide(n) {
|
||||
|
||||
showSlides(slideIndex = n);
|
||||
}
|
||||
|
||||
function showSlides(n) {
|
||||
let i;
|
||||
let slides = document.getElementsByClassName("slide");
|
||||
let dots = document.getElementsByClassName("dot");
|
||||
if (n > slides.length) {slideIndex = 1}
|
||||
if (n < 1) {slideIndex = slides.length}
|
||||
for (i = 0; i < slides.length; i++) {
|
||||
slides[i].style.opacity = 0;
|
||||
let temp = slides[i].querySelector('.moan-link');
|
||||
if (temp) {
|
||||
|
||||
let direction = temp.getAttribute("data-moan-direction")
|
||||
|
||||
if (direction === "up") {
|
||||
temp.classList.remove('moan-up');
|
||||
} else if (direction === "down") {
|
||||
temp.classList.remove('moan-down');
|
||||
} else if (direction === "from-left") {
|
||||
temp.classList.remove('moan-from-left');
|
||||
} else if (direction === "from-right") {
|
||||
temp.classList.remove('moan-from-right');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
for (i = 0; i < dots.length; i++) {
|
||||
dots[i].className = dots[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
slides[slideIndex-1].style.opacity = 1;
|
||||
|
||||
dots[slideIndex-1].className += " active";
|
||||
|
||||
let link = slides[slideIndex - 1].querySelector('.moan-link');
|
||||
|
||||
if (link) {
|
||||
|
||||
let direction = link.getAttribute("data-moan-direction")
|
||||
|
||||
if (direction === "up") {
|
||||
link.classList.add('moan-up');
|
||||
} else if (direction === "down") {
|
||||
link.classList.add('moan-down');
|
||||
} else if (direction === "from-left") {
|
||||
link.classList.add('moan-from-left');
|
||||
} else if (direction === "from-right") {
|
||||
link.classList.add('moan-from-right');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"imagem": "img_688fb8b5c8ee6.webp",
|
||||
"texto": "Adquira j\u00e1!",
|
||||
"cor": "#339d08",
|
||||
"link": "",
|
||||
"direcao": "down"
|
||||
},
|
||||
{
|
||||
"imagem": "img_688fbff50d93b.webp",
|
||||
"texto": "Adquira J\u00e1!",
|
||||
"cor": "#ec606d",
|
||||
"link": "",
|
||||
"direcao": "up"
|
||||
}
|
||||
]
|
||||
Reference in new issue
Block a user