pyzbrowser 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyzbrowser/__init__.py +47 -0
- pyzbrowser/__main__.py +9 -0
- pyzbrowser/routes.py +636 -0
- pyzbrowser/templates/index.html +567 -0
- pyzbrowser/utils.py +120 -0
- pyzbrowser-1.0.0.dist-info/METADATA +313 -0
- pyzbrowser-1.0.0.dist-info/RECORD +10 -0
- pyzbrowser-1.0.0.dist-info/WHEEL +5 -0
- pyzbrowser-1.0.0.dist-info/entry_points.txt +2 -0
- pyzbrowser-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="pt-br">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>PyBrowser - {{ hostname }}</title>
|
|
6
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90'>🐍</text></svg>">
|
|
7
|
+
<style>
|
|
8
|
+
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f0f2f5; margin: 0; padding: 20px; }
|
|
9
|
+
.container { max-width: 900px; margin: auto; background: white; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); position: relative; }
|
|
10
|
+
|
|
11
|
+
/* Header */
|
|
12
|
+
.header { padding: 15px 20px; background: #2c3e50; color: white; display: flex; justify-content: space-between; align-items: center; position: sticky; top: 0; z-index: 100; }
|
|
13
|
+
.header-title { display: flex; align-items: center; gap: 10px; font-weight: bold; }
|
|
14
|
+
.hostname { font-weight: normal; font-size: 0.85rem; opacity: 0.7; background: rgba(255,255,255,0.1); padding: 2px 8px; border-radius: 4px; }
|
|
15
|
+
|
|
16
|
+
.header-actions { display: flex; align-items: center; gap: 12px; }
|
|
17
|
+
|
|
18
|
+
/* Download Button */
|
|
19
|
+
.download-btn { display: none; padding: 8px 16px; background: #27ae60; color: white; border: 2px solid white; border-radius: 20px; cursor: pointer; font-size: 0.9rem; font-weight: bold; transition: all 0.2s; }
|
|
20
|
+
.download-btn:hover { background: #229954; transform: scale(1.05); }
|
|
21
|
+
.download-btn.show { display: inline-block; }
|
|
22
|
+
|
|
23
|
+
/* Breadcrumb */
|
|
24
|
+
.breadcrumb { padding: 10px 20px; background: #ecf0f1; border-bottom: 1px solid #bdc3c7; font-size: 0.9rem; position: sticky; top: 68px; z-index: 99; }
|
|
25
|
+
.breadcrumb a { color: #3498db; text-decoration: none; }
|
|
26
|
+
.breadcrumb a:hover { text-decoration: underline; }
|
|
27
|
+
.breadcrumb span { color: #7f8c8d; margin: 0 5px; }
|
|
28
|
+
|
|
29
|
+
/* Search and Sort Bar */
|
|
30
|
+
.search-sort-bar { padding: 15px 20px; background: #fff; border-bottom: 1px solid #e0e0e0; display: flex; gap: 10px; align-items: center; position: sticky; top: 108px; z-index: 98; }
|
|
31
|
+
.search-box { flex-grow: 1; position: relative; }
|
|
32
|
+
.search-box input { width: 100%; padding: 10px 40px 10px 15px; border: 2px solid #cbd5e0; border-radius: 8px; font-size: 0.95rem; box-sizing: border-box; }
|
|
33
|
+
.search-box input:focus { outline: none; border-color: #3498db; }
|
|
34
|
+
.search-icon { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: #7f8c8d; }
|
|
35
|
+
.sort-buttons { display: flex; gap: 5px; }
|
|
36
|
+
.sort-btn { padding: 8px 15px; background: #ecf0f1; border: 2px solid #cbd5e0; border-radius: 6px; cursor: pointer; font-size: 0.85rem; transition: all 0.2s; color: #333; text-decoration: none; display: inline-block; }
|
|
37
|
+
.sort-btn:hover { background: #cbd5e0; }
|
|
38
|
+
.sort-btn.active { background: #3498db; color: white; border-color: #3498db; }
|
|
39
|
+
|
|
40
|
+
/* Botões Circulares: Fundo Escuro, Ícone Branco */
|
|
41
|
+
.circle-btn {
|
|
42
|
+
width: 40px; height: 40px; background: #2c3e50; border-radius: 50%;
|
|
43
|
+
display: flex; align-items: center; justify-content: center;
|
|
44
|
+
text-decoration: none; border: 2px solid white; cursor: pointer;
|
|
45
|
+
transition: transform 0.2s, opacity 0.2s; padding: 0; flex-shrink: 0;
|
|
46
|
+
}
|
|
47
|
+
.circle-btn:hover { transform: scale(1.1); opacity: 0.9; }
|
|
48
|
+
.circle-btn svg { fill: white; width: 18px; height: 18px; }
|
|
49
|
+
|
|
50
|
+
/* Itens da Lista */
|
|
51
|
+
.item { display: flex; align-items: center; padding: 12px 20px; border-bottom: 1px solid #eee; gap: 15px; }
|
|
52
|
+
.item:hover { background: #f8f9fa; }
|
|
53
|
+
.item-checkbox { width: 20px; height: 20px; cursor: pointer; flex-shrink: 0; }
|
|
54
|
+
.item-info { flex-grow: 1; text-decoration: none; color: #333; display: flex; align-items: center; min-width: 0; }
|
|
55
|
+
.icon-type { margin-right: 15px; font-size: 1.2rem; flex-shrink: 0; }
|
|
56
|
+
.item-name { flex-grow: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
57
|
+
.item-date { color: #7f8c8d; font-size: 0.85rem; min-width: 130px; text-align: right; flex-shrink: 0; }
|
|
58
|
+
.item .circle-btn { border: none; box-shadow: 0 2px 4px rgba(0,0,0,0.2); flex-shrink: 0; }
|
|
59
|
+
|
|
60
|
+
/* Modal */
|
|
61
|
+
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); align-items: center; justify-content: center; }
|
|
62
|
+
.modal-content { background: white; padding: 30px; border-radius: 12px; width: 90%; max-width: 400px; text-align: center; color: #333; }
|
|
63
|
+
.upload-option { display: block; border: 2px dashed #cbd5e0; padding: 20px; margin: 15px 0; border-radius: 8px; cursor: pointer; }
|
|
64
|
+
.upload-option:hover { border-color: #2c3e50; background: #f8f9fa; }
|
|
65
|
+
input[type="file"] { display: none; }
|
|
66
|
+
|
|
67
|
+
/* Upload File List */
|
|
68
|
+
.file-list-container { margin-top: 15px; max-height: 300px; overflow-y: auto; background: #f8f9fa; border-radius: 8px; padding: 10px; }
|
|
69
|
+
.file-list-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
|
|
70
|
+
.file-list-table th { background: #2c3e50; color: white; padding: 8px; text-align: left; font-weight: bold; position: sticky; top: 0; }
|
|
71
|
+
.file-list-table td { padding: 8px; border-bottom: 1px solid #e0e0e0; }
|
|
72
|
+
.file-list-table tr:hover { background: #fff; }
|
|
73
|
+
.file-size { color: #7f8c8d; font-size: 0.85rem; }
|
|
74
|
+
.file-more { color: #7f8c8d; font-style: italic; text-align: center; padding: 12px; }
|
|
75
|
+
|
|
76
|
+
/* Search Modal */
|
|
77
|
+
.search-modal-content { max-width: 1000px; width: 95%; max-height: 85vh; display: flex; flex-direction: column; padding: 40px; }
|
|
78
|
+
.search-input-container { display: flex; gap: 12px; margin-bottom: 25px; }
|
|
79
|
+
.search-input-container input { flex: 1; padding: 15px 20px; border: 2px solid #e0e0e0; border-radius: 10px; font-size: 1.05rem; transition: all 0.2s; }
|
|
80
|
+
.search-input-container input:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); }
|
|
81
|
+
.search-input-container button { padding: 15px 30px; background: #3498db; color: white; border: none; border-radius: 10px; cursor: pointer; font-weight: bold; font-size: 1rem; transition: all 0.2s; }
|
|
82
|
+
.search-input-container button:hover { background: #2980b9; transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); }
|
|
83
|
+
.search-results { flex: 1; overflow-y: auto; max-height: 500px; border-radius: 10px; background: #fafafa; padding: 10px; }
|
|
84
|
+
.search-result-item { display: flex; align-items: center; padding: 16px 20px; border: 1px solid #e8e8e8; gap: 15px; background: white; margin-bottom: 8px; border-radius: 8px; transition: all 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
|
|
85
|
+
.search-result-item:hover { background: #f8f9fa; border-color: #3498db; transform: translateX(4px); box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
86
|
+
.search-result-icon { font-size: 1.5rem; flex-shrink: 0; }
|
|
87
|
+
.search-result-info { flex: 1; min-width: 0; }
|
|
88
|
+
.search-result-name { font-weight: 600; color: #2c3e50; font-size: 1rem; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
89
|
+
.search-result-path { font-size: 0.875rem; color: #95a5a6; display: flex; align-items: center; gap: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
90
|
+
.search-navigate-btn { padding: 10px 20px; background: #27ae60; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 0.9rem; font-weight: 600; white-space: nowrap; transition: all 0.2s; flex-shrink: 0; }
|
|
91
|
+
.search-navigate-btn:hover { background: #229954; transform: translateY(-1px); box-shadow: 0 4px 8px rgba(39, 174, 96, 0.3); }
|
|
92
|
+
.search-loading { text-align: center; padding: 60px 20px; color: #7f8c8d; font-size: 1.1rem; }
|
|
93
|
+
.search-empty { text-align: center; padding: 60px 20px; color: #95a5a6; font-size: 1.05rem; }
|
|
94
|
+
|
|
95
|
+
/* Item Highlight */
|
|
96
|
+
.item.highlight { background: #fff3cd !important; border: 2px solid #ffc107; animation: highlight-pulse 1.5s ease-in-out; }
|
|
97
|
+
@keyframes highlight-pulse { 0%, 100% { background: #fff3cd; } 50% { background: #ffe69c; } }
|
|
98
|
+
|
|
99
|
+
/* Viewer Modal */
|
|
100
|
+
.viewer-modal { display: none; position: fixed; z-index: 2000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); align-items: center; justify-content: center; }
|
|
101
|
+
.viewer-content { background: white; border-radius: 12px; width: 90%; height: 90%; max-width: 1200px; max-height: 90vh; display: flex; flex-direction: column; overflow: hidden; }
|
|
102
|
+
.viewer-header { padding: 15px 20px; background: #2c3e50; color: white; display: flex; justify-content: space-between; align-items: center; border-radius: 12px 12px 0 0; }
|
|
103
|
+
.viewer-title { font-weight: bold; font-size: 1.1rem; }
|
|
104
|
+
.viewer-close { background: #e74c3c; color: white; border: none; border-radius: 50%; width: 35px; height: 35px; cursor: pointer; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; }
|
|
105
|
+
.viewer-close:hover { background: #c0392b; }
|
|
106
|
+
.viewer-body { flex-grow: 1; overflow: auto; padding: 0; display: flex; align-items: center; justify-content: center; background: #f8f9fa; }
|
|
107
|
+
.viewer-body img { max-width: 100%; max-height: 100%; object-fit: contain; }
|
|
108
|
+
.viewer-body video, .viewer-body audio { max-width: 100%; }
|
|
109
|
+
.viewer-body iframe { width: 100%; height: 100%; border: none; }
|
|
110
|
+
.viewer-body pre { text-align: left; background: #282c34; color: #abb2bf; padding: 20px; border-radius: 0; overflow: auto; width: 100%; height: 100%; font-family: 'Consolas', 'Monaco', monospace; font-size: 0.9rem; margin: 0; box-sizing: border-box; }
|
|
111
|
+
</style>
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
<div class="container">
|
|
115
|
+
<div class="header">
|
|
116
|
+
<div class="header-title">
|
|
117
|
+
<span>📁 PyBrowser</span>
|
|
118
|
+
<span class="hostname">{{ hostname }}</span>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="header-actions">
|
|
121
|
+
<button class="download-btn" id="downloadBtn" onclick="downloadSelected()" title="Baixar selecionados">
|
|
122
|
+
<span id="downloadCount">0</span> selecionado(s) 📥
|
|
123
|
+
</button>
|
|
124
|
+
{% if current_path %}
|
|
125
|
+
<a href="{% if parent_path %}/browse/{{ parent_path }}{% else %}/{% endif %}" class="circle-btn" title="Voltar">
|
|
126
|
+
<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
|
127
|
+
</a>
|
|
128
|
+
{% endif %}
|
|
129
|
+
<a href="/" class="circle-btn" title="Início">
|
|
130
|
+
<svg viewBox="0 0 24 24"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
|
|
131
|
+
</a>
|
|
132
|
+
<button class="circle-btn" onclick="openFolderModal()" title="Nova Pasta">
|
|
133
|
+
<svg viewBox="0 0 24 24"><path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-1 8h-3v3h-2v-3h-3v-2h3V9h2v3h3v2z"/></svg>
|
|
134
|
+
</button>
|
|
135
|
+
<button class="circle-btn" onclick="openUploadModal()" title="Upload">
|
|
136
|
+
<svg viewBox="0 0 24 24"><path d="M9 16h6v-6h4l-7-7-7 7h4v6zm-4 2h14v2H5v-2z"/></svg>
|
|
137
|
+
</button>
|
|
138
|
+
<button class="circle-btn" onclick="openSearchModal()" title="Busca Avançada">
|
|
139
|
+
<svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div class="breadcrumb">
|
|
145
|
+
<a href="/">Home</a>
|
|
146
|
+
{% if current_path %}
|
|
147
|
+
{% set parts = current_path.split('/') %}
|
|
148
|
+
{% for part in parts %}
|
|
149
|
+
<span>></span>
|
|
150
|
+
{% set path_so_far = '/'.join(parts[:loop.index]) %}
|
|
151
|
+
{% if loop.last %}
|
|
152
|
+
<strong>{{ part }}</strong>
|
|
153
|
+
{% else %}
|
|
154
|
+
<a href="/browse/{{ path_so_far }}">{{ part }}</a>
|
|
155
|
+
{% endif %}
|
|
156
|
+
{% endfor %}
|
|
157
|
+
{% endif %}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="search-sort-bar">
|
|
161
|
+
<div class="search-box">
|
|
162
|
+
<form method="get" style="margin: 0;">
|
|
163
|
+
<input type="text" name="search" placeholder="Buscar arquivos e pastas..." value="{{ search_query or '' }}">
|
|
164
|
+
<input type="hidden" name="sort" value="{{ sort_by }}">
|
|
165
|
+
<span class="search-icon">🔍</span>
|
|
166
|
+
</form>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="sort-buttons">
|
|
169
|
+
<a href="?sort=name{% if search_query %}&search={{ search_query }}{% endif %}" class="sort-btn {% if sort_by == 'name' or not sort_by %}active{% endif %}" title="Ordenar por Nome">📝 Nome</a>
|
|
170
|
+
<a href="?sort=date{% if search_query %}&search={{ search_query }}{% endif %}" class="sort-btn {% if sort_by == 'date' %}active{% endif %}" title="Ordenar por Data">📅 Data</a>
|
|
171
|
+
<a href="?sort=type{% if search_query %}&search={{ search_query }}{% endif %}" class="sort-btn {% if sort_by == 'type' %}active{% endif %}" title="Ordenar por Tipo">📂 Tipo</a>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{% for item in items %}
|
|
176
|
+
<div class="item">
|
|
177
|
+
<input type="checkbox" class="item-checkbox" data-path="{{ item.path }}" onchange="updateDownloadButton()">
|
|
178
|
+
<a href="{% if item.is_dir %}/browse/{{ item.path }}{% else %}/download/{{ item.path }}{% endif %}" class="item-info">
|
|
179
|
+
<span class="icon-type">{{ item.icon }}</span>
|
|
180
|
+
<span class="item-name">{{ item.name }}</span>
|
|
181
|
+
</a>
|
|
182
|
+
<span class="item-date">{{ item.created_date }}</span>
|
|
183
|
+
|
|
184
|
+
{% if item.is_viewable %}
|
|
185
|
+
<button class="circle-btn" onclick="viewFile('{{ item.path }}', '{{ item.name }}', '{{ item.ext }}')" title="Visualizar">
|
|
186
|
+
<svg viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>
|
|
187
|
+
</button>
|
|
188
|
+
{% endif %}
|
|
189
|
+
|
|
190
|
+
<button class="circle-btn" onclick="confirmDelete('{{ item.path }}', '{{ item.name }}')" title="Deletar">
|
|
191
|
+
<svg viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
|
|
192
|
+
</button>
|
|
193
|
+
</div>
|
|
194
|
+
{% endfor %}
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div id="uploadModal" class="modal">
|
|
198
|
+
<div class="modal-content">
|
|
199
|
+
<h3 style="margin-top: 0; color: #2c3e50;">Upload em: /{{ current_path }}</h3>
|
|
200
|
+
<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
|
|
201
|
+
<input type="hidden" name="path" value="{{ current_path }}">
|
|
202
|
+
<label class="upload-option">
|
|
203
|
+
<strong>Selecionar Arquivos</strong>
|
|
204
|
+
<input type="file" id="fileInput" name="files" multiple onchange="showSelectedFiles()">
|
|
205
|
+
</label>
|
|
206
|
+
<label class="upload-option">
|
|
207
|
+
<strong>Selecionar Pasta</strong>
|
|
208
|
+
<input type="file" id="folderInput" name="files" webkitdirectory directory onchange="showSelectedFiles()">
|
|
209
|
+
</label>
|
|
210
|
+
<div id="fileList" style="display: none;">
|
|
211
|
+
<div class="file-list-container">
|
|
212
|
+
<table class="file-list-table">
|
|
213
|
+
<thead>
|
|
214
|
+
<tr>
|
|
215
|
+
<th>Nome do Arquivo</th>
|
|
216
|
+
<th style="width: 100px;">Tamanho</th>
|
|
217
|
+
</tr>
|
|
218
|
+
</thead>
|
|
219
|
+
<tbody id="fileListItems"></tbody>
|
|
220
|
+
</table>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
<div style="margin-top: 20px; display: flex; gap: 10px; justify-content: center;">
|
|
224
|
+
<button type="button" onclick="closeUploadModal()" style="padding: 10px 20px; background: #95a5a6; color: white; border: none; border-radius: 8px; cursor: pointer;">Cancelar</button>
|
|
225
|
+
<button type="submit" id="uploadButton" style="padding: 10px 20px; background: #27ae60; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; display: none;">Upload</button>
|
|
226
|
+
</div>
|
|
227
|
+
</form>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div id="folderModal" class="modal">
|
|
232
|
+
<div class="modal-content">
|
|
233
|
+
<h3 style="margin-top: 0; color: #2c3e50;">Nova Pasta em: /{{ current_path }}</h3>
|
|
234
|
+
<form action="/create_folder" method="post">
|
|
235
|
+
<input type="hidden" name="path" value="{{ current_path }}">
|
|
236
|
+
<input type="text" name="folder_name" placeholder="Nome da pasta"
|
|
237
|
+
style="width: 100%; padding: 12px; border: 2px solid #cbd5e0; border-radius: 8px; font-size: 1rem; box-sizing: border-box;"
|
|
238
|
+
required autofocus>
|
|
239
|
+
<div style="margin-top: 20px; display: flex; gap: 10px; justify-content: center;">
|
|
240
|
+
<button type="button" onclick="closeFolderModal()" style="padding: 10px 20px; background: #95a5a6; color: white; border: none; border-radius: 8px; cursor: pointer;">Cancelar</button>
|
|
241
|
+
<button type="submit" style="padding: 10px 20px; background: #2c3e50; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">Criar</button>
|
|
242
|
+
</div>
|
|
243
|
+
</form>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div id="deleteModal" class="modal">
|
|
248
|
+
<div class="modal-content">
|
|
249
|
+
<h3 style="margin-top: 0; color: #e74c3c;">⚠️ Confirmar Exclusão</h3>
|
|
250
|
+
<p style="margin: 20px 0;">Tem certeza que deseja deletar <strong id="deleteItemName"></strong>?</p>
|
|
251
|
+
<p style="font-size: 0.9rem; color: #7f8c8d;">Esta ação não pode ser desfeita.</p>
|
|
252
|
+
<form id="deleteForm" method="post" action="">
|
|
253
|
+
<div style="margin-top: 20px; display: flex; gap: 10px; justify-content: center;">
|
|
254
|
+
<button type="button" onclick="closeDeleteModal()" style="padding: 10px 20px; background: #95a5a6; color: white; border: none; border-radius: 8px; cursor: pointer;">Cancelar</button>
|
|
255
|
+
<button type="submit" style="padding: 10px 20px; background: #e74c3c; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: bold;">Deletar</button>
|
|
256
|
+
</div>
|
|
257
|
+
</form>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<div id="searchModal" class="modal">
|
|
262
|
+
<div class="modal-content search-modal-content">
|
|
263
|
+
<h3 style="margin-top: 0; color: #2c3e50;">🔍 Busca Avançada</h3>
|
|
264
|
+
<div class="search-input-container">
|
|
265
|
+
<input type="text" id="advancedSearchInput" placeholder="Digite o nome do arquivo ou pasta..." onkeypress="if(event.key==='Enter') performAdvancedSearch()">
|
|
266
|
+
<button onclick="performAdvancedSearch()">Buscar</button>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="search-results" id="searchResults">
|
|
269
|
+
<div class="search-empty">Digite algo e clique em Buscar para pesquisar em todos os diretórios</div>
|
|
270
|
+
</div>
|
|
271
|
+
<div style="margin-top: 20px; text-align: center;">
|
|
272
|
+
<button onclick="closeSearchModal()" style="padding: 10px 20px; background: #95a5a6; color: white; border: none; border-radius: 8px; cursor: pointer;">Fechar</button>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div id="viewerModal" class="viewer-modal">
|
|
278
|
+
<div class="viewer-content">
|
|
279
|
+
<div class="viewer-header">
|
|
280
|
+
<span class="viewer-title" id="viewerTitle"></span>
|
|
281
|
+
<button class="viewer-close" onclick="closeViewer()">✕</button>
|
|
282
|
+
</div>
|
|
283
|
+
<div class="viewer-body" id="viewerBody">
|
|
284
|
+
<!-- Conteúdo do arquivo será carregado aqui -->
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<script>
|
|
290
|
+
function openUploadModal() {
|
|
291
|
+
document.getElementById('uploadModal').style.display = 'flex';
|
|
292
|
+
// Limpar seleção anterior
|
|
293
|
+
document.getElementById('fileInput').value = '';
|
|
294
|
+
document.getElementById('folderInput').value = '';
|
|
295
|
+
document.getElementById('fileList').style.display = 'none';
|
|
296
|
+
document.getElementById('uploadButton').style.display = 'none';
|
|
297
|
+
}
|
|
298
|
+
function closeUploadModal() {
|
|
299
|
+
document.getElementById('uploadModal').style.display = 'none';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function showSelectedFiles() {
|
|
303
|
+
const fileInput = document.getElementById('fileInput');
|
|
304
|
+
const folderInput = document.getElementById('folderInput');
|
|
305
|
+
const fileList = document.getElementById('fileList');
|
|
306
|
+
const fileListItems = document.getElementById('fileListItems');
|
|
307
|
+
const uploadButton = document.getElementById('uploadButton');
|
|
308
|
+
|
|
309
|
+
// Determinar qual input foi usado
|
|
310
|
+
const files = fileInput.files.length > 0 ? fileInput.files : folderInput.files;
|
|
311
|
+
|
|
312
|
+
if (files.length > 0) {
|
|
313
|
+
fileListItems.innerHTML = '';
|
|
314
|
+
|
|
315
|
+
// Função para formatar tamanho do arquivo
|
|
316
|
+
function formatFileSize(bytes) {
|
|
317
|
+
if (bytes === 0) return '0 B';
|
|
318
|
+
const k = 1024;
|
|
319
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
320
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
321
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Mostrar primeiros 50 arquivos
|
|
325
|
+
const maxDisplay = 50;
|
|
326
|
+
let totalSize = 0;
|
|
327
|
+
|
|
328
|
+
for (let i = 0; i < Math.min(files.length, maxDisplay); i++) {
|
|
329
|
+
const file = files[i];
|
|
330
|
+
const tr = document.createElement('tr');
|
|
331
|
+
|
|
332
|
+
const tdName = document.createElement('td');
|
|
333
|
+
tdName.textContent = file.webkitRelativePath || file.name;
|
|
334
|
+
tr.appendChild(tdName);
|
|
335
|
+
|
|
336
|
+
const tdSize = document.createElement('td');
|
|
337
|
+
tdSize.className = 'file-size';
|
|
338
|
+
tdSize.textContent = formatFileSize(file.size);
|
|
339
|
+
tr.appendChild(tdSize);
|
|
340
|
+
|
|
341
|
+
fileListItems.appendChild(tr);
|
|
342
|
+
totalSize += file.size;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Calcular tamanho total de todos os arquivos
|
|
346
|
+
for (let i = 0; i < files.length; i++) {
|
|
347
|
+
if (i >= maxDisplay) totalSize += files[i].size;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (files.length > maxDisplay) {
|
|
351
|
+
const tr = document.createElement('tr');
|
|
352
|
+
const td = document.createElement('td');
|
|
353
|
+
td.colSpan = 2;
|
|
354
|
+
td.className = 'file-more';
|
|
355
|
+
td.textContent = `... e mais ${files.length - maxDisplay} arquivo(s)`;
|
|
356
|
+
tr.appendChild(td);
|
|
357
|
+
fileListItems.appendChild(tr);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Adicionar linha de resumo
|
|
361
|
+
const trTotal = document.createElement('tr');
|
|
362
|
+
trTotal.style.fontWeight = 'bold';
|
|
363
|
+
trTotal.style.background = '#ecf0f1';
|
|
364
|
+
|
|
365
|
+
const tdTotal = document.createElement('td');
|
|
366
|
+
tdTotal.textContent = `Total: ${files.length} arquivo(s)`;
|
|
367
|
+
trTotal.appendChild(tdTotal);
|
|
368
|
+
|
|
369
|
+
const tdTotalSize = document.createElement('td');
|
|
370
|
+
tdTotalSize.textContent = formatFileSize(totalSize);
|
|
371
|
+
trTotal.appendChild(tdTotalSize);
|
|
372
|
+
|
|
373
|
+
fileListItems.appendChild(trTotal);
|
|
374
|
+
|
|
375
|
+
fileList.style.display = 'block';
|
|
376
|
+
uploadButton.style.display = 'inline-block';
|
|
377
|
+
} else {
|
|
378
|
+
fileList.style.display = 'none';
|
|
379
|
+
uploadButton.style.display = 'none';
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function openFolderModal() { document.getElementById('folderModal').style.display = 'flex'; }
|
|
384
|
+
function closeFolderModal() { document.getElementById('folderModal').style.display = 'none'; }
|
|
385
|
+
|
|
386
|
+
function confirmDelete(path, name) {
|
|
387
|
+
document.getElementById('deleteItemName').textContent = name;
|
|
388
|
+
document.getElementById('deleteForm').action = '/delete/' + path;
|
|
389
|
+
document.getElementById('deleteModal').style.display = 'flex';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function closeDeleteModal() { document.getElementById('deleteModal').style.display = 'none'; }
|
|
393
|
+
|
|
394
|
+
function updateDownloadButton() {
|
|
395
|
+
const checkboxes = document.querySelectorAll('.item-checkbox:checked');
|
|
396
|
+
const downloadBtn = document.getElementById('downloadBtn');
|
|
397
|
+
const downloadCount = document.getElementById('downloadCount');
|
|
398
|
+
|
|
399
|
+
if (checkboxes.length > 0) {
|
|
400
|
+
downloadBtn.classList.add('show');
|
|
401
|
+
downloadCount.textContent = checkboxes.length;
|
|
402
|
+
} else {
|
|
403
|
+
downloadBtn.classList.remove('show');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function downloadSelected() {
|
|
408
|
+
const checkboxes = document.querySelectorAll('.item-checkbox:checked');
|
|
409
|
+
if (checkboxes.length === 0) return;
|
|
410
|
+
|
|
411
|
+
const form = document.createElement('form');
|
|
412
|
+
form.method = 'POST';
|
|
413
|
+
form.action = '/download_selected';
|
|
414
|
+
|
|
415
|
+
checkboxes.forEach(checkbox => {
|
|
416
|
+
const input = document.createElement('input');
|
|
417
|
+
input.type = 'hidden';
|
|
418
|
+
input.name = 'items[]';
|
|
419
|
+
input.value = checkbox.dataset.path;
|
|
420
|
+
form.appendChild(input);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
document.body.appendChild(form);
|
|
424
|
+
form.submit();
|
|
425
|
+
document.body.removeChild(form);
|
|
426
|
+
|
|
427
|
+
// Desmarcar todos após download
|
|
428
|
+
setTimeout(() => {
|
|
429
|
+
checkboxes.forEach(cb => cb.checked = false);
|
|
430
|
+
updateDownloadButton();
|
|
431
|
+
}, 500);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function viewFile(path, name, ext) {
|
|
435
|
+
document.getElementById('viewerTitle').textContent = name;
|
|
436
|
+
const viewerBody = document.getElementById('viewerBody');
|
|
437
|
+
const viewUrl = '/view/' + path;
|
|
438
|
+
|
|
439
|
+
// Limpar conteúdo anterior
|
|
440
|
+
viewerBody.innerHTML = '';
|
|
441
|
+
|
|
442
|
+
// Imagens
|
|
443
|
+
if (['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp', '.ico'].includes(ext)) {
|
|
444
|
+
viewerBody.innerHTML = '<img src="' + viewUrl + '" alt="' + name + '">';
|
|
445
|
+
}
|
|
446
|
+
// Vídeos
|
|
447
|
+
else if (['.mp4', '.webm', '.ogg'].includes(ext)) {
|
|
448
|
+
viewerBody.innerHTML = '<video controls autoplay><source src="' + viewUrl + '" type="video/' + ext.substring(1) + '">Seu navegador não suporta vídeo.</video>';
|
|
449
|
+
}
|
|
450
|
+
// Áudio
|
|
451
|
+
else if (['.mp3', '.wav', '.ogg', '.m4a'].includes(ext)) {
|
|
452
|
+
viewerBody.innerHTML = '<audio controls autoplay><source src="' + viewUrl + '" type="audio/' + ext.substring(1) + '">Seu navegador não suporta áudio.</audio>';
|
|
453
|
+
}
|
|
454
|
+
// PDF
|
|
455
|
+
else if (ext === '.pdf') {
|
|
456
|
+
viewerBody.innerHTML = '<iframe src="' + viewUrl + '"></iframe>';
|
|
457
|
+
}
|
|
458
|
+
// Arquivos de texto/código
|
|
459
|
+
else {
|
|
460
|
+
fetch(viewUrl)
|
|
461
|
+
.then(response => response.text())
|
|
462
|
+
.then(text => {
|
|
463
|
+
const pre = document.createElement('pre');
|
|
464
|
+
pre.textContent = text;
|
|
465
|
+
viewerBody.appendChild(pre);
|
|
466
|
+
})
|
|
467
|
+
.catch(err => {
|
|
468
|
+
viewerBody.innerHTML = '<p style="color: #e74c3c;">Erro ao carregar arquivo.</p>';
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
document.getElementById('viewerModal').style.display = 'flex';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function closeViewer() { document.getElementById('viewerModal').style.display = 'none'; }
|
|
476
|
+
|
|
477
|
+
function openSearchModal() {
|
|
478
|
+
document.getElementById('searchModal').style.display = 'flex';
|
|
479
|
+
document.getElementById('advancedSearchInput').focus();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function closeSearchModal() {
|
|
483
|
+
document.getElementById('searchModal').style.display = 'none';
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function performAdvancedSearch() {
|
|
487
|
+
const query = document.getElementById('advancedSearchInput').value.trim();
|
|
488
|
+
const resultsDiv = document.getElementById('searchResults');
|
|
489
|
+
|
|
490
|
+
if (!query) {
|
|
491
|
+
resultsDiv.innerHTML = '<div class="search-empty">Por favor, digite algo para buscar</div>';
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
resultsDiv.innerHTML = '<div class="search-loading">🔍 Buscando...</div>';
|
|
496
|
+
|
|
497
|
+
fetch('/api/search?q=' + encodeURIComponent(query))
|
|
498
|
+
.then(response => response.json())
|
|
499
|
+
.then(data => {
|
|
500
|
+
if (data.results.length === 0) {
|
|
501
|
+
resultsDiv.innerHTML = '<div class="search-empty">Nenhum resultado encontrado para "' + query + '"</div>';
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
let html = '';
|
|
506
|
+
data.results.forEach(item => {
|
|
507
|
+
html += '<div class="search-result-item">';
|
|
508
|
+
html += '<span class="search-result-icon">' + item.icon + '</span>';
|
|
509
|
+
html += '<div class="search-result-info">';
|
|
510
|
+
html += '<div class="search-result-name">' + item.name + '</div>';
|
|
511
|
+
html += '<div class="search-result-path">📍 ' + (item.parent_path || '/') + '</div>';
|
|
512
|
+
html += '</div>';
|
|
513
|
+
html += '<button class="search-navigate-btn" onclick="navigateToItem(\'' + item.parent_path + '\', \'' + item.path + '\')">Ir para local</button>';
|
|
514
|
+
html += '</div>';
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
resultsDiv.innerHTML = html;
|
|
518
|
+
})
|
|
519
|
+
.catch(err => {
|
|
520
|
+
resultsDiv.innerHTML = '<div class="search-empty" style="color: #e74c3c;">Erro ao buscar. Tente novamente.</div>';
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function navigateToItem(parentPath, itemPath) {
|
|
525
|
+
closeSearchModal();
|
|
526
|
+
const url = parentPath ? '/browse/' + parentPath + '?highlight=' + encodeURIComponent(itemPath) : '/?highlight=' + encodeURIComponent(itemPath);
|
|
527
|
+
window.location.href = url;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Destacar item se houver parâmetro highlight na URL
|
|
531
|
+
window.addEventListener('DOMContentLoaded', function() {
|
|
532
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
533
|
+
const highlightPath = urlParams.get('highlight');
|
|
534
|
+
|
|
535
|
+
if (highlightPath) {
|
|
536
|
+
const checkboxes = document.querySelectorAll('.item-checkbox');
|
|
537
|
+
checkboxes.forEach(checkbox => {
|
|
538
|
+
if (checkbox.dataset.path === highlightPath) {
|
|
539
|
+
const item = checkbox.closest('.item');
|
|
540
|
+
if (item) {
|
|
541
|
+
item.classList.add('highlight');
|
|
542
|
+
setTimeout(() => {
|
|
543
|
+
item.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
544
|
+
}, 100);
|
|
545
|
+
setTimeout(() => {
|
|
546
|
+
item.classList.remove('highlight');
|
|
547
|
+
}, 3000);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
window.onclick = function(event) {
|
|
555
|
+
if (event.target.className === 'modal') {
|
|
556
|
+
closeUploadModal();
|
|
557
|
+
closeFolderModal();
|
|
558
|
+
closeDeleteModal();
|
|
559
|
+
closeSearchModal();
|
|
560
|
+
}
|
|
561
|
+
if (event.target.id === 'viewerModal') {
|
|
562
|
+
closeViewer();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
</script>
|
|
566
|
+
</body>
|
|
567
|
+
</html>
|