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/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>&larr; Geri</button>
23
+ <button id="btn-forward" class="nav-btn" disabled>İrəli &rarr;</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">&times;</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>