iobroker.staticsfilefolder 0.0.22 → 0.0.24
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/README.md +6 -0
- package/io-package.json +16 -15
- package/lib/adapter-config.d.ts +8 -7
- package/lib/web.js +72 -69
- package/main.js +129 -122
- package/package.json +5 -4
- package/www/app.js +35 -0
package/README.md
CHANGED
|
@@ -64,6 +64,12 @@ Once configured and the instance is running (green status):
|
|
|
64
64
|
Placeholder for the next version (at the beginning of the line):
|
|
65
65
|
### **WORK IN PROGRESS**
|
|
66
66
|
-->
|
|
67
|
+
### 0.0.24
|
|
68
|
+
* Resolved all remaining repochecker warnings and suggestions, migrated to standard automerge-dependabot workflow, and updated to ESLint v9 with flat config.
|
|
69
|
+
|
|
70
|
+
### 0.0.23
|
|
71
|
+
* Added live rendering / auto-refresh in file explorer on file additions/deletions.
|
|
72
|
+
|
|
67
73
|
### 0.0.22
|
|
68
74
|
* Fixed PDF print layout to prevent vertical page overflow, and fixed zoom/scale rendering issues in PDF viewer.
|
|
69
75
|
|
package/io-package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "staticsfilefolder",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.24",
|
|
5
5
|
"news": {
|
|
6
|
-
"0.0.
|
|
7
|
-
"en": "
|
|
8
|
-
"de": "
|
|
9
|
-
"ru": "
|
|
10
|
-
"pt": "
|
|
11
|
-
"nl": "
|
|
12
|
-
"fr": "
|
|
13
|
-
"it": "
|
|
14
|
-
"es": "Se
|
|
15
|
-
"pl": "
|
|
16
|
-
"uk": "
|
|
17
|
-
"zh-cn": "
|
|
6
|
+
"0.0.24": {
|
|
7
|
+
"en": "Resolved all remaining repochecker warnings and suggestions, migrated to standard automerge-dependabot workflow, and updated to ESLint v9 with flat config.",
|
|
8
|
+
"de": "Alle verbleibenden Warnungen und Vorschläge des Repocheckers behoben, auf Standard-Automerge-Dependabot-Workflow umgestellt und auf ESLint v9 mit Flat-Config aktualisiert.",
|
|
9
|
+
"ru": "Устранены все оставшиеся предупреждения и предложения repochecker, осуществлен переход на стандартный рабочий процесс автослияния dependabot и обновлен до ESLint v9 с плоской конфигурацией.",
|
|
10
|
+
"pt": "Resolvido todos os avisos e sugestões restantes do repochecker, migrado para o fluxo de trabalho padrão de mesclagem automática do dependabot e atualizado para ESLint v9 com configuração plana.",
|
|
11
|
+
"nl": "Alle resterende repochecker waarschuwingen en suggesties opgelost, gemigreerd naar de standaard automerge-dependabot workflow, en geüpgraded naar ESLint v9 met flat config.",
|
|
12
|
+
"fr": "Résolution de tous les avertissements et suggestions restants de repochecker, migration vers le flux de travail d'auto-fusion standard de dependabot et mise à jour vers ESLint v9 avec configuration plate.",
|
|
13
|
+
"it": "Risolti tutti gli avvisi e i suggerimenti rimanenti di repochecker, migrato al flusso di lavoro standard di unione automatica di dependabot e aggiornato a ESLint v9 con configurazione piatta.",
|
|
14
|
+
"es": "Se resolvieron todas las advertencias y sugerencias restantes de repochecker, se migró al flujo de trabajo estándar de fusión automática de dependabot y se actualizó a ESLint v9 con configuración plana.",
|
|
15
|
+
"pl": "Rozwiązano wszystkie pozostałe ostrzeżenia i sugestie repocheckera, zmigrowano do standardowego przepływu pracy automatycznego scalania dependabot oraz zaktualizowano do ESLint v9 z płaską konfiguracją.",
|
|
16
|
+
"uk": "Усунено всі залишені попередження та пропозиції repochecker, здійснено перехід на стандартний робочий процес автозлиття dependabot та оновлено до ESLint v9 з плоскою конфігурацією.",
|
|
17
|
+
"zh-cn": "解决了所有剩余的 repochecker 警告和建议,迁移到标准的自动合并 dependabot 工作流,并升级到具有平面配置的 ESLint v9。"
|
|
18
18
|
},
|
|
19
19
|
"0.0.15": {
|
|
20
20
|
"en": "Reverted minimum Node.js engine requirement to 20 to support GitHub Actions runners.",
|
|
@@ -99,8 +99,9 @@
|
|
|
99
99
|
"gokturk413 <gokturk413@gmail.com>"
|
|
100
100
|
],
|
|
101
101
|
"keywords": [
|
|
102
|
-
"
|
|
103
|
-
"
|
|
102
|
+
"static-files",
|
|
103
|
+
"directory-browsing",
|
|
104
|
+
"file-viewer"
|
|
104
105
|
],
|
|
105
106
|
"licenseInformation": {
|
|
106
107
|
"type": "free",
|
package/lib/adapter-config.d.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
1
2
|
// This file extends the AdapterConfig type from "@types/iobroker"
|
|
2
3
|
// using the actual properties present in io-package.json
|
|
3
4
|
// in order to provide typings for adapter.config properties
|
|
4
5
|
|
|
5
|
-
import { native } from
|
|
6
|
+
import type { native } from '../io-package.json';
|
|
6
7
|
|
|
7
8
|
type _AdapterConfig = typeof native;
|
|
8
9
|
|
|
9
10
|
// Augment the globally declared type ioBroker.AdapterConfig
|
|
10
11
|
declare global {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
namespace ioBroker {
|
|
13
|
+
interface AdapterConfig extends _AdapterConfig {
|
|
14
|
+
// Do not enter anything here!
|
|
15
|
+
}
|
|
16
|
+
}
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
// this is required so the above AdapterConfig is found by TypeScript / type checking
|
|
19
|
-
export {};
|
|
20
|
+
export {};
|
package/lib/web.js
CHANGED
|
@@ -1,88 +1,91 @@
|
|
|
1
|
-
const express = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const fs = require(
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Extension class to serve static files and configuration API endpoints.
|
|
7
|
+
*/
|
|
5
8
|
class ExtensionExample {
|
|
6
|
-
|
|
9
|
+
/**
|
|
7
10
|
* @param {object} server http or https node.js object
|
|
8
11
|
* @param {object} webSettings settings of the web server
|
|
9
12
|
* @param {object} adapter web adapter object
|
|
10
13
|
* @param {object} instanceSettings instance object with common and native
|
|
11
14
|
* @param {object} app express application
|
|
12
15
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
constructor(server, webSettings, adapter, instanceSettings, app) {
|
|
17
|
+
this.app = app;
|
|
18
|
+
this.adapter = adapter;
|
|
19
|
+
this.config = instanceSettings ? instanceSettings.native : {};
|
|
20
|
+
this.config.route = this.config.route || 'demo';
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
try {
|
|
23
|
+
adapter.log.info(`Install extension on /${this.config.route}`);
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
// Serve our SPA static files from www folder
|
|
26
|
+
const wwwPath = path.join(__dirname, '../www');
|
|
27
|
+
this.app.use(`/${this.config.route}`, express.static(wwwPath));
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
// Serve the reports directory so files can be accessed via URL
|
|
30
|
+
if (this.config.dirname) {
|
|
31
|
+
this.app.use(`/${this.config.route}/files`, express.static(this.config.dirname));
|
|
32
|
+
}
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
// API Endpoint for configuration and system language
|
|
35
|
+
this.app.get(`/${this.config.route}/api/config`, async (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
const systemConfig = await this.adapter.getForeignObjectAsync('system.config');
|
|
38
|
+
const systemLanguage =
|
|
39
|
+
(systemConfig && systemConfig.common && systemConfig.common.language) || 'en';
|
|
40
|
+
res.json({ success: true, language: systemLanguage });
|
|
41
|
+
} catch {
|
|
42
|
+
res.json({ success: false, language: 'en' });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// API Endpoint for listing files
|
|
47
|
+
this.app.get(`/${this.config.route}/api/list`, (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const relPath = req.query.path || '';
|
|
50
|
+
if (relPath.includes('..')) {
|
|
51
|
+
return res.status(400).json({ error: 'Invalid path' });
|
|
52
|
+
}
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
const targetDir = path.join(this.config.dirname, relPath);
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
|
|
57
|
+
return res.status(404).json({ error: 'Directory not found' });
|
|
58
|
+
}
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
const items = fs.readdirSync(targetDir);
|
|
61
|
+
const result = [];
|
|
62
|
+
for (const item of items) {
|
|
63
|
+
try {
|
|
64
|
+
const itemPath = path.join(targetDir, item);
|
|
65
|
+
const stat = fs.statSync(itemPath);
|
|
66
|
+
result.push({
|
|
67
|
+
name: item,
|
|
68
|
+
isDirectory: stat.isDirectory(),
|
|
69
|
+
size: stat.size,
|
|
70
|
+
mtime: stat.mtimeMs,
|
|
71
|
+
birthtime: stat.birthtimeMs,
|
|
72
|
+
ext: path.extname(item).toLowerCase(),
|
|
73
|
+
});
|
|
74
|
+
} catch {
|
|
75
|
+
// ignore errors
|
|
76
|
+
}
|
|
77
|
+
}
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
79
|
+
res.json({ success: true, items: result, path: relPath });
|
|
80
|
+
} catch (e) {
|
|
81
|
+
adapter.log.error(`Error in /api/list: ${e}`);
|
|
82
|
+
res.status(500).json({ error: e.toString() });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
} catch (err) {
|
|
86
|
+
adapter.log.error(`Error during Extension init: ${err.stack}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
module.exports = ExtensionExample;
|
|
91
|
+
module.exports = ExtensionExample;
|
package/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
* Created with @iobroker/create-adapter v2.6.2
|
|
@@ -6,133 +6,140 @@
|
|
|
6
6
|
|
|
7
7
|
// The adapter-core module gives you access to the core ioBroker functions
|
|
8
8
|
// you need to create an adapter
|
|
9
|
-
const utils = require(
|
|
9
|
+
const utils = require('@iobroker/adapter-core');
|
|
10
10
|
|
|
11
11
|
// Load your modules here, e.g.:
|
|
12
12
|
// const fs = require("fs");
|
|
13
13
|
|
|
14
14
|
class Staticsfilefolder extends utils.Adapter {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
15
|
+
/**
|
|
16
|
+
* @param {Partial<utils.AdapterOptions>} [options] The adapter options.
|
|
17
|
+
*/
|
|
18
|
+
constructor(options) {
|
|
19
|
+
super({
|
|
20
|
+
...options,
|
|
21
|
+
name: 'staticsfilefolder',
|
|
22
|
+
});
|
|
23
|
+
this.on('ready', this.onReady.bind(this));
|
|
24
|
+
this.on('stateChange', this.onStateChange.bind(this));
|
|
25
|
+
this.on('unload', this.onUnload.bind(this));
|
|
26
|
+
|
|
27
|
+
this.watcher = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Is called when databases are connected and adapter received configuration.
|
|
32
|
+
*/
|
|
33
|
+
async onReady() {
|
|
34
|
+
// Create state for latest file url
|
|
35
|
+
await this.setObjectNotExistsAsync('latest_file_url', {
|
|
36
|
+
type: 'state',
|
|
37
|
+
common: {
|
|
38
|
+
name: 'Latest File URL',
|
|
39
|
+
type: 'string',
|
|
40
|
+
role: 'url',
|
|
41
|
+
read: true,
|
|
42
|
+
write: false,
|
|
43
|
+
},
|
|
44
|
+
native: {},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create state for the latest file path
|
|
48
|
+
await this.setObjectNotExistsAsync('latest_file_path', {
|
|
49
|
+
type: 'state',
|
|
50
|
+
common: {
|
|
51
|
+
name: 'Latest File Path',
|
|
52
|
+
type: 'string',
|
|
53
|
+
role: 'value',
|
|
54
|
+
read: true,
|
|
55
|
+
write: false,
|
|
56
|
+
},
|
|
57
|
+
native: {},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (this.config.dirname) {
|
|
61
|
+
this.log.info(`Starting file observer on: ${this.config.dirname}`);
|
|
62
|
+
try {
|
|
63
|
+
const chokidar = require('chokidar');
|
|
64
|
+
const path = require('node:path');
|
|
65
|
+
|
|
66
|
+
this.watcher = chokidar.watch(this.config.dirname, {
|
|
67
|
+
ignored: /(^|[/\\])\../, // ignore dotfiles
|
|
68
|
+
persistent: true,
|
|
69
|
+
ignoreInitial: true, // do not fire for existing files on startup
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.watcher.on('add', async filePath => {
|
|
73
|
+
this.log.info(`New file detected: ${filePath}`);
|
|
74
|
+
|
|
75
|
+
// Calculate relative path for URL
|
|
76
|
+
const relativePath = path.relative(this.config.dirname, filePath).replace(/\\/g, '/');
|
|
77
|
+
const route = this.config.route || 'demo';
|
|
78
|
+
|
|
79
|
+
// Assuming the web adapter serves this extension under the web server's port
|
|
80
|
+
// and relative path is encoded.
|
|
81
|
+
const fileUrl = `/${route}/files/${encodeURI(relativePath)}`;
|
|
82
|
+
|
|
83
|
+
await this.setStateAsync('latest_file_url', {
|
|
84
|
+
val: fileUrl,
|
|
85
|
+
ack: true,
|
|
86
|
+
});
|
|
87
|
+
await this.setStateAsync('latest_file_path', {
|
|
88
|
+
val: filePath,
|
|
89
|
+
ack: true,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.watcher.on('error', error => this.log.error(`Watcher error: ${error}`));
|
|
94
|
+
} catch (e) {
|
|
95
|
+
this.log.error(`Failed to start file observer: ${e}`);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
this.log.warn('Directory name not configured. File observer will not start.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Is called when adapter shuts down - callback has to be called under any circumstances!
|
|
104
|
+
*
|
|
105
|
+
* @param {() => void} callback The callback function to call after unloading.
|
|
106
|
+
*/
|
|
107
|
+
onUnload(callback) {
|
|
108
|
+
try {
|
|
109
|
+
if (this.watcher) {
|
|
110
|
+
this.watcher.close();
|
|
111
|
+
this.watcher = null;
|
|
112
|
+
}
|
|
113
|
+
callback();
|
|
114
|
+
} catch {
|
|
115
|
+
callback();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Is called if a subscribed state changes
|
|
121
|
+
*
|
|
122
|
+
* @param {string} id The state ID.
|
|
123
|
+
* @param {ioBroker.State | null | undefined} state The state object.
|
|
124
|
+
*/
|
|
125
|
+
onStateChange(id, state) {
|
|
126
|
+
if (state) {
|
|
127
|
+
// The state was changed
|
|
128
|
+
this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
|
|
129
|
+
} else {
|
|
130
|
+
// The state was deleted
|
|
131
|
+
this.log.info(`state ${id} deleted`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
if (require.main !== module) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
// Export the constructor in compact mode
|
|
138
|
+
/**
|
|
139
|
+
* @param {Partial<utils.AdapterOptions>} [options] The adapter options.
|
|
140
|
+
*/
|
|
141
|
+
module.exports = options => new Staticsfilefolder(options);
|
|
135
142
|
} else {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
143
|
+
// otherwise start the instance directly
|
|
144
|
+
new Staticsfilefolder();
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.staticsfilefolder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "Static Files Folder",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "gokturk413",
|
|
@@ -29,14 +29,15 @@
|
|
|
29
29
|
"@alcalzone/release-script-plugin-iobroker": "^5.2.0",
|
|
30
30
|
"@alcalzone/release-script-plugin-license": "^5.2.0",
|
|
31
31
|
"@alcalzone/release-script-plugin-manual-review": "^5.2.0",
|
|
32
|
-
"@iobroker/adapter-dev": "^1.
|
|
32
|
+
"@iobroker/adapter-dev": "^1.5.0",
|
|
33
|
+
"@iobroker/eslint-config": "^2.3.4",
|
|
33
34
|
"@iobroker/testing": "^5.2.2",
|
|
34
35
|
"@tsconfig/node22": "^22.0.0",
|
|
35
36
|
"@types/node": "^18.19.24",
|
|
36
37
|
"@types/proxyquire": "^1.3.31",
|
|
37
|
-
"eslint": "^
|
|
38
|
+
"eslint": "^9.10.0",
|
|
38
39
|
"proxyquire": "^2.1.3",
|
|
39
|
-
"typescript": "
|
|
40
|
+
"typescript": "^5.5.4"
|
|
40
41
|
},
|
|
41
42
|
"main": "main.js",
|
|
42
43
|
"files": [
|
package/www/app.js
CHANGED
|
@@ -7,6 +7,7 @@ let currentPath = '';
|
|
|
7
7
|
let historyStack = [];
|
|
8
8
|
let forwardStack = [];
|
|
9
9
|
let currentItems = [];
|
|
10
|
+
let pollInterval = null;
|
|
10
11
|
|
|
11
12
|
// DOM Elements
|
|
12
13
|
const breadcrumbEl = document.getElementById('breadcrumb');
|
|
@@ -227,6 +228,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
227
228
|
initTheme();
|
|
228
229
|
initLang();
|
|
229
230
|
loadPath('');
|
|
231
|
+
startPolling();
|
|
230
232
|
|
|
231
233
|
// Event Listeners
|
|
232
234
|
btnBack.addEventListener('click', goBack);
|
|
@@ -281,6 +283,39 @@ async function loadPath(path) {
|
|
|
281
283
|
}
|
|
282
284
|
}
|
|
283
285
|
|
|
286
|
+
function startPolling() {
|
|
287
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
288
|
+
pollInterval = setInterval(async () => {
|
|
289
|
+
if (document.hidden) return;
|
|
290
|
+
try {
|
|
291
|
+
const response = await fetch(`${API_URL}?path=${encodeURIComponent(currentPath)}`);
|
|
292
|
+
const data = await response.json();
|
|
293
|
+
if (data.success) {
|
|
294
|
+
const itemsChanged = JSON.stringify(data.items) !== JSON.stringify(currentItems);
|
|
295
|
+
if (itemsChanged) {
|
|
296
|
+
currentItems = data.items;
|
|
297
|
+
renderItems();
|
|
298
|
+
|
|
299
|
+
if (currentOpenItem) {
|
|
300
|
+
const fileStillExists = data.items.some(item => !item.isDirectory && item.name === currentOpenItem.name);
|
|
301
|
+
if (!fileStillExists) {
|
|
302
|
+
viewerModal.style.display = 'none';
|
|
303
|
+
viewerBody.innerHTML = '';
|
|
304
|
+
currentOpenItem = null;
|
|
305
|
+
currentPdfDoc = null;
|
|
306
|
+
currentPdfZoom = 1.5;
|
|
307
|
+
modalPdfZoom.value = '1.5';
|
|
308
|
+
modalPdfZoom.style.display = 'none';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch (e) {
|
|
314
|
+
console.warn('Polling error:', e);
|
|
315
|
+
}
|
|
316
|
+
}, 3000);
|
|
317
|
+
}
|
|
318
|
+
|
|
284
319
|
function navigateTo(path) {
|
|
285
320
|
if (path !== currentPath) {
|
|
286
321
|
historyStack.push(currentPath);
|