Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion public/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
.action-btn-rename {
display: none; background: rgba(0,0,0,0.05); color: var(--text-main); padding: 0.4rem 0.8rem;
}
.action-btn-replace {
display: none; background: rgba(0,0,0,0.05); color: var(--text-main); padding: 0.4rem 0.8rem;
}
.action-btn-edit {
display: none; padding: 0.4rem 0.8rem;
}
Expand Down Expand Up @@ -153,8 +156,29 @@
background: rgba(18, 18, 24, 0.85);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
}
.replace-modal-content {
max-width: 480px;
margin: 8% auto;
padding: 2.5rem;
position: relative;
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(18, 18, 24, 0.85);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
}

.share-file-name {
.share-file-name{
margin-top: 1rem;
margin-bottom: 2rem;
color: var(--text-muted);
font-size: 0.95rem;
padding: 0.75rem 1rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 12px;
border-left: 3px solid var(--primary-color);
}
.replace-file-name{
margin-top: 1rem;
margin-bottom: 2rem;
color: var(--text-muted);
font-size: 0.95rem;
Expand All @@ -170,6 +194,44 @@
font-weight: 500;
}

.replace-form-group label {
display: block;
margin-bottom: 0.75rem;
font-weight: 500;
}

#replaceFile {
width: 100%;
padding: 0.8rem 1rem;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
color: rgb(255, 255, 255);
border: 1px solid rgba(255, 255, 255, 0.1);
font-size: 1rem;
outline: none;
cursor: pointer;
transition: all 0.3s ease;
appearance: none;
}

.replace-result-area p {
/* Layout & Spacing */
margin: 20px 0;
padding: 15px 20px;
border-radius: 8px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 14px;
line-height: 1.5;

/* Success Theme Colors */
color: #155724;
background-color: #d4edda;
border: 1px solid #c3e6cb;

/* Subtle Shadow */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.share-select {
width: 100%;
padding: 0.8rem 1rem;
Expand Down Expand Up @@ -200,6 +262,12 @@
animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

.replace-result-area {
display: none;
margin-top: 2rem;
animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes slideUp {
from { opacity: 0; transform: translateY(15px); }
to { opacity: 1; transform: translateY(0); }
Expand Down Expand Up @@ -243,6 +311,13 @@
gap: 1rem;
}

.replace-modal-actions {
margin-top: 2.5rem;
display: flex;
justify-content: flex-end;
gap: 1rem;
}

.shadow-glow {
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
}
Expand Down
38 changes: 31 additions & 7 deletions public/dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
// Handle File Upload
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
try {
$filename = $fileManager->uploadFile($_FILES['file']);
$filename = $fileManager->uploadFile($_FILES['file'], false);
$_SESSION['success'] = "Arquivo '$filename' enviado com sucesso!";
} catch (Exception $e) {
$_SESSION['error'] = $e->getMessage();
Expand Down Expand Up @@ -240,7 +240,7 @@ function formatBytes($bytes, $precision = 2) {
<input type="hidden" name="name" id="createFileNameInput" value="">
</form>
</div>

<?php if ($errorMsg): ?>
<div class="alert alert-error"><?= htmlspecialchars($errorMsg) ?></div>
<?php endif; ?>
Expand Down Expand Up @@ -273,18 +273,21 @@ function formatBytes($bytes, $precision = 2) {
<button class="btn action-btn action-btn-rename" id="btnRename" onclick="renameSelected()">
Renomear
</button>
<button class="btn btn-primary action-btn action-btn-edit" id="btnEdit" onclick="editSelected()">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg> Editar
<button class="btn action-btn action-btn-replace" id="btnReplace" onclick="replaceSelected()">
Substituir
</button>
<button class="btn action-btn action-btn-download" id="btnDownload" onclick="downloadSelected()">
Baixar
</button>
<button class="btn btn-danger action-btn action-btn-delete" id="btnDelete" onclick="deleteSelected()">
Apagar
</button>
<button class="btn action-btn action-btn-share" id="btnShare" onclick="shareSelected()">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg> Compartilhar
</button>
<button class="btn btn-primary action-btn action-btn-edit" id="btnEdit" onclick="editSelected()">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg> Editar
</button>
<button class="btn btn-danger action-btn action-btn-delete" id="btnDelete" onclick="deleteSelected()">
Apagar
</button>
<span class="selection-count" id="selectionCount">0 itens selecionados</span>
</div>

Expand Down Expand Up @@ -326,6 +329,27 @@ function formatBytes($bytes, $precision = 2) {
</div>
</div>

<div id="replaceModal" class="modal">
<div class="modal-content glass-panel replace-modal-content">
<h2 class="text-gradient">Substituir Arquivo</h2>
<p id="replaceFileNameDisplay" class="replace-file-name"></p>

<div class="form-group replace-form-group">
<label for="replaceFile">Novo arquivo</label>
<input type="file" id="replaceFile">
</div>

<div id="replaceResultArea" class="replace-result-area">
<p></p>
</div>

<div class="modal-actions replace-modal-actions">
<button type="button" class="btn btn-secondary" onclick="closeReplaceModal()">Cancelar</button>
<button type="button" class="btn btn-primary shadow-glow" id="btnReplaceFile" onclick="replaceFile()">Confirmar</button>
</div>
</div>
</div>

<!-- Share Modal -->
<div id="shareModal" class="modal">
<div class="modal-content glass-panel share-modal-content">
Expand Down
75 changes: 75 additions & 0 deletions public/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function updateActionBar() {

const topActionBar = document.getElementById('topActionBar');
const btnRename = document.getElementById('btnRename');
const btnReplace = document.getElementById('btnReplace');
const btnEdit = document.getElementById('btnEdit');
const btnDownload = document.getElementById('btnDownload');
const btnDelete = document.getElementById('btnDelete');
Expand All @@ -33,6 +34,7 @@ function updateActionBar() {

if (count === 0) {
if (btnRename) btnRename.style.display = 'none';
if (btnReplace) btnReplace.style.display = 'none';
if (btnEdit) btnEdit.style.display = 'none';
if (btnDownload) btnDownload.style.display = 'none';
if (btnDelete) btnDelete.style.display = 'none';
Expand All @@ -43,6 +45,11 @@ function updateActionBar() {
btnRename.disabled = false;
}

if (btnReplace) {
btnReplace.style.display = (count === 1) ? 'inline-flex' : 'none';
btnReplace.disabled = false;
}

if (btnEdit) {
btnEdit.style.display = canEdit ? 'inline-flex' : 'none';
btnEdit.disabled = false;
Expand Down Expand Up @@ -216,6 +223,24 @@ document.addEventListener('click', function(event) {
}
});

function replaceSelected() {
const files = getSelectedFiles();
if (files.length === 1) {
const filename = files[0];
document.getElementById('replaceFileNameDisplay').innerText = `Arquivo: ${filename}`;
document.getElementById('replaceResultArea').style.display = 'none';
const btnGen = document.getElementById('btnReplaceFile');
btnGen.style.display = 'inline-block';
btnGen.disabled = false;
btnGen.innerText = 'Gerar Link';
document.getElementById('replaceModal').classList.add('active');
}
}

function closeReplaceModal() {
document.getElementById('replaceModal').classList.remove('active');
}

// Share Functions
function shareSelected() {
const files = getSelectedFiles();
Expand All @@ -235,6 +260,56 @@ function closeShareModal() {
document.getElementById('shareModal').classList.remove('active');
}

function replaceFile(){
const files = getSelectedFiles();
if (files.length !== 1) return;

const filename = files[0];
const newFile = document.getElementById('replaceFile').files[0];
const btn = document.getElementById('btnReplaceFile');

console.log(newFile)

btn.disabled = true;
btn.innerText = 'Substituindo...';

//if (filename != newFile.name){
// alert('Nome do arquivo é diferente do original');
// btn.disabled = false;
// btn.innerText = 'Confirmar';
// return;
//}

const formData = new FormData();
formData.append('file', filename);
formData.append('newFile', newFile);

fetch('/replace.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log(data)
if (data.success) {

document.querySelector('#replaceResultArea > p').innerText = "Arquivo substituído com sucesso";
document.getElementById('replaceResultArea').style.display = 'block';
btn.style.display = 'none';
} else {
alert('Erro: ' + data.error);
btn.disabled = false;
btn.innerText = 'Confirmar';
}
})
.catch(error => {
console.error('Error:', error);
alert('Erro ao conectar com o servidor.');
btn.disabled = false;
btn.innerText = 'Confirmar';
});
}

function generateShare() {
const files = getSelectedFiles();
if (files.length !== 1) return;
Expand Down
53 changes: 53 additions & 0 deletions public/replace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

require_once __DIR__ . '/../vendor/autoload.php';

use App\Auth;
use App\Config\Database;
use App\FileManager;

session_start();

$db = new Database();
$auth = new Auth($db);
if (!$auth->isLoggedIn()) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Acesso negado.']);
exit;
}

$userId = $auth->getCurrentUserId();
$storage = \App\Storage\StorageFactory::create();
$fileManager = new FileManager($userId, $storage);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$filename = $_POST['file'] ?? '';

if (empty($filename)) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Nome do arquivo é obrigatório.']);
exit;
}

if (empty($_FILES['newFile']['tmp_name'])) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Um novo arquivo é obrigatório.']);
exit;
}

if ($_FILES['newFile']['name'] != $filename){
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Nome do arquivo é diferente do original.']);
exit;
}

try {
$filename = $fileManager->uploadFile($_FILES['newFile'], true);
header('Content-Type: application/json');
echo json_encode(['success' => true]);
} catch (Exception $e) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Erro ao substituir arquivo: ' . $e->getMessage()]);
}
exit;
}
6 changes: 3 additions & 3 deletions src/FileManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private function getFullContentPath(string $filename): string {
return $this->getUserPrefix() . '/' . basename($filename);
}

public function uploadFile(array $file): string {
public function uploadFile(array $file, bool $overwrite = false): string {
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new Exception("Upload failed with error code: " . $file['error']);
}
Expand All @@ -34,13 +34,13 @@ public function uploadFile(array $file): string {

$path = $this->getFullContentPath($filename);

if ($this->storage->exists($path)) {
if ($this->storage->exists($path) && !$overwrite) {
throw new Exception("File already exists.");
}

$content = file_get_contents($file['tmp_name']);
if ($content === false) {
throw new Exception("Failed to read uploaded file.");
throw new Exception("Failed to read uploaded file.");
}

$this->storage->put($path, $content);
Expand Down
Loading
Loading