iobroker.staticsfilefolder 0.0.2
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.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/admin/index_m.html +130 -0
- package/admin/staticsfilefolder.png +0 -0
- package/admin/style.css +32 -0
- package/admin/words.js +46 -0
- package/io-package.json +96 -0
- package/lib/adapter-config.d.ts +19 -0
- package/lib/web.js +76 -0
- package/main.js +138 -0
- package/package.json +74 -0
- package/www/app.js +321 -0
- package/www/index.html +72 -0
- package/www/libs/mammoth.browser.min.js +20 -0
- package/www/libs/pdfjs/pdf.min.mjs +29 -0
- package/www/libs/pdfjs/pdf.worker.min.mjs +29 -0
- package/www/libs/xlsx.full.min.js +22 -0
- package/www/style.css +265 -0
package/www/app.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// Config and State
|
|
2
|
+
const BASE_ROUTE = window.location.pathname.replace(/\/$/, ''); // e.g. /Omni
|
|
3
|
+
const API_URL = `${BASE_ROUTE}/api/list`;
|
|
4
|
+
const FILE_URL = `${BASE_ROUTE}/files`;
|
|
5
|
+
|
|
6
|
+
let currentPath = '';
|
|
7
|
+
let historyStack = [];
|
|
8
|
+
let forwardStack = [];
|
|
9
|
+
let currentItems = [];
|
|
10
|
+
|
|
11
|
+
// DOM Elements
|
|
12
|
+
const breadcrumbEl = document.getElementById('breadcrumb');
|
|
13
|
+
const fileListEl = document.getElementById('file-list');
|
|
14
|
+
const emptyStateEl = document.getElementById('empty-state');
|
|
15
|
+
const btnBack = document.getElementById('btn-back');
|
|
16
|
+
const btnForward = document.getElementById('btn-forward');
|
|
17
|
+
const btnHome = document.getElementById('btn-home');
|
|
18
|
+
const btnTheme = document.getElementById('btn-theme');
|
|
19
|
+
const searchBox = document.getElementById('search-box');
|
|
20
|
+
const typeFilter = document.getElementById('type-filter');
|
|
21
|
+
const sortSelect = document.getElementById('sort-select');
|
|
22
|
+
|
|
23
|
+
const viewerModal = document.getElementById('viewer-modal');
|
|
24
|
+
const closeModal = document.getElementById('close-modal');
|
|
25
|
+
const viewerBody = document.getElementById('viewer-body');
|
|
26
|
+
|
|
27
|
+
// Setup PDF.js worker
|
|
28
|
+
if (window.pdfjsLib) {
|
|
29
|
+
window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'libs/pdfjs/pdf.worker.min.mjs';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Theme Initialization
|
|
33
|
+
function initTheme() {
|
|
34
|
+
const savedTheme = localStorage.getItem('staticsfilefolder_theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
35
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
36
|
+
btnTheme.textContent = savedTheme === 'dark' ? '☀️' : '🌙';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function toggleTheme() {
|
|
40
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
41
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
42
|
+
document.documentElement.setAttribute('data-theme', newTheme);
|
|
43
|
+
localStorage.setItem('staticsfilefolder_theme', newTheme);
|
|
44
|
+
btnTheme.textContent = newTheme === 'dark' ? '☀️' : '🌙';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Initialize
|
|
48
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
49
|
+
initTheme();
|
|
50
|
+
loadPath('');
|
|
51
|
+
|
|
52
|
+
// Event Listeners
|
|
53
|
+
btnBack.addEventListener('click', goBack);
|
|
54
|
+
btnForward.addEventListener('click', goForward);
|
|
55
|
+
btnHome.addEventListener('click', () => navigateTo(''));
|
|
56
|
+
btnTheme.addEventListener('click', toggleTheme);
|
|
57
|
+
|
|
58
|
+
searchBox.addEventListener('input', renderItems);
|
|
59
|
+
typeFilter.addEventListener('change', renderItems);
|
|
60
|
+
sortSelect.addEventListener('change', renderItems);
|
|
61
|
+
|
|
62
|
+
closeModal.addEventListener('click', () => {
|
|
63
|
+
viewerModal.style.display = 'none';
|
|
64
|
+
viewerBody.innerHTML = ''; // clear memory
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
async function loadPath(path) {
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(`${API_URL}?path=${encodeURIComponent(path)}`);
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
|
|
73
|
+
if (data.success) {
|
|
74
|
+
currentPath = path;
|
|
75
|
+
currentItems = data.items;
|
|
76
|
+
|
|
77
|
+
updateBreadcrumb();
|
|
78
|
+
updateNavButtons();
|
|
79
|
+
renderItems();
|
|
80
|
+
} else {
|
|
81
|
+
alert('Qovluq yüklənərkən xəta: ' + data.error);
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error(e);
|
|
85
|
+
alert('Şəbəkə xətası.');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function navigateTo(path) {
|
|
90
|
+
if (path !== currentPath) {
|
|
91
|
+
historyStack.push(currentPath);
|
|
92
|
+
forwardStack = []; // Clear forward stack on new navigation
|
|
93
|
+
loadPath(path);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function goBack() {
|
|
98
|
+
if (historyStack.length > 0) {
|
|
99
|
+
forwardStack.push(currentPath);
|
|
100
|
+
const prevPath = historyStack.pop();
|
|
101
|
+
loadPath(prevPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function goForward() {
|
|
106
|
+
if (forwardStack.length > 0) {
|
|
107
|
+
historyStack.push(currentPath);
|
|
108
|
+
const nextPath = forwardStack.pop();
|
|
109
|
+
loadPath(nextPath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function updateNavButtons() {
|
|
114
|
+
btnBack.disabled = historyStack.length === 0;
|
|
115
|
+
btnForward.disabled = forwardStack.length === 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function updateBreadcrumb() {
|
|
119
|
+
if (!currentPath) {
|
|
120
|
+
breadcrumbEl.innerHTML = '<span><i class="icon">🏠</i> Kök Qovluq</span>';
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const parts = currentPath.split('/');
|
|
125
|
+
let html = '<span data-path="">🏠 Kök</span>';
|
|
126
|
+
let buildPath = '';
|
|
127
|
+
|
|
128
|
+
parts.forEach((part, index) => {
|
|
129
|
+
if (!part) return;
|
|
130
|
+
buildPath += (buildPath ? '/' : '') + part;
|
|
131
|
+
html += ` / <span data-path="${buildPath}">${part}</span>`;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
breadcrumbEl.innerHTML = html;
|
|
135
|
+
|
|
136
|
+
// Add click events to breadcrumb
|
|
137
|
+
breadcrumbEl.querySelectorAll('span').forEach(span => {
|
|
138
|
+
span.addEventListener('click', (e) => {
|
|
139
|
+
const path = e.target.getAttribute('data-path');
|
|
140
|
+
if (path !== null) navigateTo(path);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function renderItems() {
|
|
146
|
+
fileListEl.innerHTML = '';
|
|
147
|
+
|
|
148
|
+
const searchQuery = searchBox.value.toLowerCase();
|
|
149
|
+
const typeValue = typeFilter.value;
|
|
150
|
+
const sortValue = sortSelect.value;
|
|
151
|
+
|
|
152
|
+
let filtered = currentItems.filter(item => {
|
|
153
|
+
if (searchQuery && !item.name.toLowerCase().includes(searchQuery)) return false;
|
|
154
|
+
if (typeValue !== 'all' && !item.isDirectory) {
|
|
155
|
+
if (item.ext !== typeValue) return false;
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Sorting
|
|
161
|
+
filtered.sort((a, b) => {
|
|
162
|
+
// Always folders first
|
|
163
|
+
if (a.isDirectory && !b.isDirectory) return -1;
|
|
164
|
+
if (!a.isDirectory && b.isDirectory) return 1;
|
|
165
|
+
|
|
166
|
+
if (sortValue === 'name-asc') {
|
|
167
|
+
return a.name.localeCompare(b.name);
|
|
168
|
+
} else if (sortValue === 'name-desc') {
|
|
169
|
+
return b.name.localeCompare(a.name);
|
|
170
|
+
} else if (sortValue === 'date-desc') {
|
|
171
|
+
return b.mtime - a.mtime;
|
|
172
|
+
} else if (sortValue === 'date-asc') {
|
|
173
|
+
return a.mtime - b.mtime;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (filtered.length === 0) {
|
|
178
|
+
emptyStateEl.style.display = 'block';
|
|
179
|
+
return;
|
|
180
|
+
} else {
|
|
181
|
+
emptyStateEl.style.display = 'none';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const today = new Date().setHours(0,0,0,0);
|
|
185
|
+
|
|
186
|
+
filtered.forEach(item => {
|
|
187
|
+
const div = document.createElement('div');
|
|
188
|
+
div.className = 'file-item';
|
|
189
|
+
|
|
190
|
+
let icon = '📄';
|
|
191
|
+
if (item.isDirectory) icon = '📁';
|
|
192
|
+
else if (item.ext === '.pdf') icon = '📕';
|
|
193
|
+
else if (item.ext === '.xlsx' || item.ext === '.csv') icon = '📗';
|
|
194
|
+
else if (item.ext === '.docx' || item.ext === '.doc') icon = '📘';
|
|
195
|
+
|
|
196
|
+
// Check if today
|
|
197
|
+
const itemDate = new Date(item.mtime);
|
|
198
|
+
const isToday = itemDate.setHours(0,0,0,0) === today;
|
|
199
|
+
if (isToday) div.classList.add('is-today');
|
|
200
|
+
|
|
201
|
+
const dateStr = new Date(item.mtime).toLocaleString('az-AZ');
|
|
202
|
+
|
|
203
|
+
let sizeStr = '';
|
|
204
|
+
if (!item.isDirectory) {
|
|
205
|
+
if (item.size > 1024 * 1024) sizeStr = (item.size / (1024 * 1024)).toFixed(2) + ' MB';
|
|
206
|
+
else sizeStr = (item.size / 1024).toFixed(2) + ' KB';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
div.innerHTML = `
|
|
210
|
+
<div class="file-icon">${icon}</div>
|
|
211
|
+
<div class="file-name" title="${item.name}">${item.name}</div>
|
|
212
|
+
<div class="file-meta">${dateStr}<br>${sizeStr}</div>
|
|
213
|
+
${isToday ? '<div class="today-badge">Bu gün</div>' : ''}
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
div.addEventListener('click', () => handleItemClick(item));
|
|
217
|
+
fileListEl.appendChild(div);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function handleItemClick(item) {
|
|
222
|
+
if (item.isDirectory) {
|
|
223
|
+
const newPath = currentPath ? `${currentPath}/${item.name}` : item.name;
|
|
224
|
+
navigateTo(newPath);
|
|
225
|
+
} else {
|
|
226
|
+
const filePath = currentPath ? `${currentPath}/${item.name}` : item.name;
|
|
227
|
+
const fileUrl = `${FILE_URL}/${encodeURIComponent(filePath)}`;
|
|
228
|
+
|
|
229
|
+
if (item.ext === '.pdf') {
|
|
230
|
+
openPdf(fileUrl);
|
|
231
|
+
} else if (item.ext === '.xlsx' || item.ext === '.csv') {
|
|
232
|
+
openExcel(fileUrl);
|
|
233
|
+
} else if (item.ext === '.docx') {
|
|
234
|
+
openWord(fileUrl);
|
|
235
|
+
} else {
|
|
236
|
+
// Unhandled file type, just open in new tab
|
|
237
|
+
window.open(fileUrl, '_blank');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---- Document Viewers ----
|
|
243
|
+
|
|
244
|
+
async function openPdf(url) {
|
|
245
|
+
viewerModal.style.display = 'block';
|
|
246
|
+
viewerBody.innerHTML = '<h2>PDF Yüklənir...</h2>';
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const loadingTask = window.pdfjsLib.getDocument(url);
|
|
250
|
+
const pdf = await loadingTask.promise;
|
|
251
|
+
viewerBody.innerHTML = ''; // clear loading
|
|
252
|
+
|
|
253
|
+
// Render all pages
|
|
254
|
+
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
|
255
|
+
const page = await pdf.getPage(pageNum);
|
|
256
|
+
const scale = 1.5;
|
|
257
|
+
const viewport = page.getViewport({ scale: scale });
|
|
258
|
+
|
|
259
|
+
const canvas = document.createElement('canvas');
|
|
260
|
+
const context = canvas.getContext('2d');
|
|
261
|
+
canvas.height = viewport.height;
|
|
262
|
+
canvas.width = viewport.width;
|
|
263
|
+
|
|
264
|
+
viewerBody.appendChild(canvas);
|
|
265
|
+
|
|
266
|
+
const renderContext = {
|
|
267
|
+
canvasContext: context,
|
|
268
|
+
viewport: viewport
|
|
269
|
+
};
|
|
270
|
+
await page.render(renderContext).promise;
|
|
271
|
+
}
|
|
272
|
+
} catch (e) {
|
|
273
|
+
viewerBody.innerHTML = `<h2 style="color:red">Xəta baş verdi: ${e.message}</h2>`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function openExcel(url) {
|
|
278
|
+
viewerModal.style.display = 'block';
|
|
279
|
+
viewerBody.innerHTML = '<h2>Excel Yüklənir...</h2>';
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const res = await fetch(url);
|
|
283
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
284
|
+
|
|
285
|
+
const workbook = XLSX.read(arrayBuffer, {type: 'array'});
|
|
286
|
+
const firstSheetName = workbook.SheetNames[0];
|
|
287
|
+
const worksheet = workbook.Sheets[firstSheetName];
|
|
288
|
+
|
|
289
|
+
const htmlTable = XLSX.utils.sheet_to_html(worksheet, { id: 'excel-table' });
|
|
290
|
+
|
|
291
|
+
viewerBody.innerHTML = `<div style="padding: 20px; background: white; width: 100%; height: 100%; overflow: auto;">
|
|
292
|
+
<h3 style="margin-top:0">${firstSheetName}</h3>
|
|
293
|
+
${htmlTable}
|
|
294
|
+
</div>`;
|
|
295
|
+
|
|
296
|
+
// Add some style classes to the generated table
|
|
297
|
+
const table = document.getElementById('excel-table');
|
|
298
|
+
if(table) table.className = 'sheetjs-table';
|
|
299
|
+
|
|
300
|
+
} catch (e) {
|
|
301
|
+
viewerBody.innerHTML = `<h2 style="color:red">Xəta baş verdi: ${e.message}</h2>`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function openWord(url) {
|
|
306
|
+
viewerModal.style.display = 'block';
|
|
307
|
+
viewerBody.innerHTML = '<h2>Word Sənədi Yüklənir...</h2>';
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const res = await fetch(url);
|
|
311
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
312
|
+
|
|
313
|
+
const result = await mammoth.convertToHtml({arrayBuffer: arrayBuffer});
|
|
314
|
+
|
|
315
|
+
viewerBody.innerHTML = `<div style="padding: 40px; background: white; max-width: 800px; margin: 0 auto; box-shadow: 0 0 10px rgba(0,0,0,0.1);">
|
|
316
|
+
${result.value}
|
|
317
|
+
</div>`;
|
|
318
|
+
} catch (e) {
|
|
319
|
+
viewerBody.innerHTML = `<h2 style="color:red">Xəta baş verdi: ${e.message}</h2>`;
|
|
320
|
+
}
|
|
321
|
+
}
|
package/www/index.html
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="az">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Omni Report Browser</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
|
|
9
|
+
<!-- Offline Libraries -->
|
|
10
|
+
<script src="libs/pdfjs/pdf.min.mjs" type="module"></script>
|
|
11
|
+
<script src="libs/xlsx.full.min.js"></script>
|
|
12
|
+
<script src="libs/mammoth.browser.min.js"></script>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
|
|
16
|
+
<div class="app-container">
|
|
17
|
+
<!-- Header & Toolbar -->
|
|
18
|
+
<header class="app-header">
|
|
19
|
+
<div class="header-title">Omni Reports</div>
|
|
20
|
+
|
|
21
|
+
<div class="toolbar">
|
|
22
|
+
<button id="btn-back" class="nav-btn" disabled>← Geri</button>
|
|
23
|
+
<button id="btn-forward" class="nav-btn" disabled>İrəli →</button>
|
|
24
|
+
<button id="btn-home" class="nav-btn">Evə (Home)</button>
|
|
25
|
+
|
|
26
|
+
<div class="breadcrumb" id="breadcrumb">
|
|
27
|
+
<!-- Path will be injected here -->
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="filters">
|
|
31
|
+
<button id="btn-theme" class="nav-btn" title="Gecə/Gündüz Rejimi">🌙</button>
|
|
32
|
+
<input type="text" id="search-box" placeholder="Axtarış...">
|
|
33
|
+
<select id="type-filter">
|
|
34
|
+
<option value="all">Bütün Tiplər</option>
|
|
35
|
+
<option value=".pdf">PDF</option>
|
|
36
|
+
<option value=".xlsx">Excel</option>
|
|
37
|
+
<option value=".docx">Word</option>
|
|
38
|
+
</select>
|
|
39
|
+
<select id="sort-select">
|
|
40
|
+
<option value="name-asc">A-Z Sırala</option>
|
|
41
|
+
<option value="name-desc">Z-A Sırala</option>
|
|
42
|
+
<option value="date-desc">Ən Yeni</option>
|
|
43
|
+
<option value="date-asc">Ən Köhnə</option>
|
|
44
|
+
</select>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</header>
|
|
48
|
+
|
|
49
|
+
<!-- Main Content (File List) -->
|
|
50
|
+
<main class="file-list-container">
|
|
51
|
+
<div id="file-list" class="file-list">
|
|
52
|
+
<!-- File items will be injected here -->
|
|
53
|
+
</div>
|
|
54
|
+
<div id="empty-state" class="empty-state" style="display: none;">
|
|
55
|
+
Fayl tapılmadı
|
|
56
|
+
</div>
|
|
57
|
+
</main>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<!-- Viewer Modal -->
|
|
61
|
+
<div id="viewer-modal" class="modal">
|
|
62
|
+
<div class="modal-content">
|
|
63
|
+
<span id="close-modal" class="close-button">×</span>
|
|
64
|
+
<div id="viewer-body">
|
|
65
|
+
<!-- Viewer content (Canvas for PDF, Table for Excel, HTML for Word) -->
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<script src="app.js" type="module"></script>
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|