@preference-sl/pref-viewer 2.11.0-beta.0 → 2.11.0-beta.10
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/package.json +14 -8
- package/src/babylonjs-animation-controller.js +235 -0
- package/src/babylonjs-animation-opening-menu.js +360 -0
- package/src/babylonjs-animation-opening.js +496 -0
- package/src/babylonjs-controller.js +1186 -0
- package/src/css/pref-viewer-2d.css +39 -0
- package/src/css/pref-viewer-3d.css +28 -0
- package/src/css/pref-viewer-dialog.css +105 -0
- package/src/css/pref-viewer.css +11 -0
- package/src/file-storage.js +166 -39
- package/src/gltf-resolver.js +288 -0
- package/src/index.js +721 -1057
- package/src/panzoom-controller.js +494 -0
- package/src/pref-viewer-2d.js +460 -0
- package/src/pref-viewer-3d-data.js +178 -0
- package/src/pref-viewer-3d.js +700 -0
- package/src/pref-viewer-dialog.js +139 -0
- package/src/pref-viewer-task.js +54 -0
- package/src/svg-resolver.js +281 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Variables */
|
|
2
|
+
pref-viewer-2d {
|
|
3
|
+
--pref-viewer-2d-bg-color: #ffffff;
|
|
4
|
+
--pref-viewer-2d-svg-padding: 10px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
pref-viewer-2d[visible="true"] {
|
|
8
|
+
display: block;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pref-viewer-2d[visible="false"] {
|
|
12
|
+
display: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pref-viewer-2d {
|
|
16
|
+
grid-column: 1;
|
|
17
|
+
grid-row: 1;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
min-width: 0;
|
|
20
|
+
min-height: 0;
|
|
21
|
+
align-self: stretch;
|
|
22
|
+
justify-self: stretch;
|
|
23
|
+
background: var(--pref-viewer-2d-bg-color);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pref-viewer-2d,
|
|
27
|
+
pref-viewer-2d>div,
|
|
28
|
+
pref-viewer-2d>div>svg {
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 100%;
|
|
31
|
+
display: block;
|
|
32
|
+
position: relative;
|
|
33
|
+
outline: none;
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pref-viewer-2d>div>svg {
|
|
38
|
+
padding: var(--pref-viewer-2d-svg-padding);
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
pref-viewer-3d[visible="true"] {
|
|
2
|
+
display: block;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
pref-viewer-3d[visible="false"] {
|
|
6
|
+
display: none;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
pref-viewer-3d {
|
|
10
|
+
grid-column: 1;
|
|
11
|
+
grid-row: 1;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
min-height: 0;
|
|
15
|
+
align-self: stretch;
|
|
16
|
+
justify-self: stretch;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pref-viewer-3d,
|
|
20
|
+
pref-viewer-3d>div,
|
|
21
|
+
pref-viewer-3d>div>canvas {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
display: block;
|
|
25
|
+
position: relative;
|
|
26
|
+
outline: none;
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* Variables */
|
|
2
|
+
pref-viewer-dialog {
|
|
3
|
+
--brand-color: #ff6700;
|
|
4
|
+
--dialog-general-space: 16px;
|
|
5
|
+
--dialog-bg-color: #ffffff;
|
|
6
|
+
--dialog-backdrop-color: rgba(0, 0, 0, 0.25);
|
|
7
|
+
--dialog-border-color: #e7e7e7;
|
|
8
|
+
--dialog-border-radius: 8px;
|
|
9
|
+
--dialog-box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
|
|
10
|
+
--button-default-bg-color: #bbbbbb;
|
|
11
|
+
--button-default-bg-color-hover: #a1a1a1;
|
|
12
|
+
--button-primary-bg-color: color-mix(in oklab, var(--brand-color), white 25%);
|
|
13
|
+
--button-primary-bg-color-hover: var(--brand-color);
|
|
14
|
+
--button-border-radius: 4px;
|
|
15
|
+
--button-padding-horizontal: 16px;
|
|
16
|
+
--button-padding-vertical: 8px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pref-viewer-dialog:not {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pref-viewer-dialog[open] {
|
|
24
|
+
font-family: 'Roboto', ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
|
25
|
+
grid-row: 1;
|
|
26
|
+
grid-column: 1;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
min-width: 0;
|
|
29
|
+
min-height: 0;
|
|
30
|
+
align-self: stretch;
|
|
31
|
+
justify-self: stretch;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
background-color: var(--dialog-backdrop-color);
|
|
36
|
+
position: relative;
|
|
37
|
+
z-index: 1000;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pref-viewer-dialog>.dialog-wrapper {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
align-items: stretch;
|
|
44
|
+
background: var(--dialog-bg-color);
|
|
45
|
+
border: 1px solid var(--dialog-border-color);
|
|
46
|
+
border-radius: var(--dialog-border-radius);
|
|
47
|
+
box-shadow: var(--dialog-box-shadow);
|
|
48
|
+
padding: 0;
|
|
49
|
+
min-width: 320px;
|
|
50
|
+
max-width: 90%;
|
|
51
|
+
max-height: 90%;
|
|
52
|
+
overflow: auto;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pref-viewer-dialog .dialog-header {
|
|
56
|
+
padding: var(--dialog-general-space);
|
|
57
|
+
border-bottom: 1px solid var(--dialog-border-color);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pref-viewer-dialog .dialog-header h3 {
|
|
61
|
+
margin: 0;
|
|
62
|
+
font-weight: 500;
|
|
63
|
+
font-size: 1.1em;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pref-viewer-dialog .dialog-content {
|
|
67
|
+
padding: var(--dialog-general-space);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pref-viewer-dialog .dialog-content h4 {
|
|
71
|
+
margin: 0;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
font-size: 1.05em;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pref-viewer-dialog .dialog-footer {
|
|
77
|
+
border-top: 1px solid var(--dialog-border-color);
|
|
78
|
+
padding: var(--dialog-general-space);
|
|
79
|
+
display: flex;
|
|
80
|
+
gap: var(--dialog-general-space);
|
|
81
|
+
justify-content: stretch;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pref-viewer-dialog .dialog-footer button {
|
|
85
|
+
width: 100%;
|
|
86
|
+
font-size: 1em;
|
|
87
|
+
padding: var(--button-padding-vertical) var(--button-padding-horizontal);
|
|
88
|
+
border-radius: var(--button-border-radius);
|
|
89
|
+
border: none;
|
|
90
|
+
background: var(--button-default-bg-color);
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
transition: background 0.2s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pref-viewer-dialog .dialog-footer button.primary {
|
|
96
|
+
background: var(--button-primary-bg-color);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pref-viewer-dialog .dialog-footer button:hover {
|
|
100
|
+
background: var(--button-default-bg-color-hover);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pref-viewer-dialog .dialog-footer button.primary:hover {
|
|
104
|
+
background: var(--button-primary-bg-color-hover);
|
|
105
|
+
}
|
package/src/file-storage.js
CHANGED
|
@@ -1,12 +1,119 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FileStore class to manage file storage in IndexedDB using a promise-based wrapper around the IndexedDB API.
|
|
3
|
-
* @see {@link https://github.com/jakearchibald/idb|GitHub - jakearchibald/idb: IndexedDB, but with promises}
|
|
4
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API|IndexedDB API - MDN}
|
|
5
|
-
* @see {@link https://www.npmjs.com/package/idb|idb - npm}
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { openDB } from "idb";
|
|
9
2
|
|
|
3
|
+
/**
|
|
4
|
+
* FileStorage - Class for managing file storage in IndexedDB with server synchronization.
|
|
5
|
+
*
|
|
6
|
+
* Overview:
|
|
7
|
+
* - Provides a promise-based wrapper around the IndexedDB API for file caching.
|
|
8
|
+
* - Automatically manages cache versioning by comparing server and cached file timestamps.
|
|
9
|
+
* - Falls back to direct server access when IndexedDB is unavailable.
|
|
10
|
+
* - Supports file blob retrieval, timestamp checking, and size queries.
|
|
11
|
+
* - Uses the idb library for simplified IndexedDB operations.
|
|
12
|
+
*
|
|
13
|
+
* Dependencies:
|
|
14
|
+
* - idb (IndexedDB wrapper library)
|
|
15
|
+
* - Modern browser with IndexedDB support
|
|
16
|
+
*
|
|
17
|
+
* Constructor:
|
|
18
|
+
* - FileStorage(dbName, osName): Creates a new FileStorage instance.
|
|
19
|
+
* Parameters:
|
|
20
|
+
* - dbName: Name of the IndexedDB database (default: "FilesDB")
|
|
21
|
+
* - osName: Name of the object store (default: "FilesObjectStore")
|
|
22
|
+
*
|
|
23
|
+
* Private Methods:
|
|
24
|
+
* - #createObjectStore(db, osName): Creates the object store if it doesn't exist.
|
|
25
|
+
* - #openDB(): Opens or creates the IndexedDB database.
|
|
26
|
+
* - #getServerFile(uri): Downloads file blob and timestamp from server.
|
|
27
|
+
* - #getServerFileTimeStamp(uri): Retrieves Last-Modified timestamp via HEAD request.
|
|
28
|
+
* - #getServerFileSize(uri): Retrieves Content-Length via HEAD request.
|
|
29
|
+
* - #putFile(file, uri): Stores file record in IndexedDB.
|
|
30
|
+
* - #getFile(uri): Retrieves file record from IndexedDB.
|
|
31
|
+
*
|
|
32
|
+
* Public Methods:
|
|
33
|
+
* - getURL(uri): Gets an object URL for cached blob or original URI.
|
|
34
|
+
* - getTimeStamp(uri): Retrieves cached or server Last-Modified timestamp.
|
|
35
|
+
* - getSize(uri): Gets cached or server file size in bytes.
|
|
36
|
+
* - getBlob(uri): Retrieves file blob from cache or server.
|
|
37
|
+
* - get(uri): Gets file from cache with automatic server sync and cache versioning.
|
|
38
|
+
* - put(uri): Stores file from server in IndexedDB cache.
|
|
39
|
+
*
|
|
40
|
+
* Features:
|
|
41
|
+
* - Automatic Cache Versioning: Compares server and cached timestamps to update cache.
|
|
42
|
+
* - Fallback Support: Uses direct server access when IndexedDB is unavailable.
|
|
43
|
+
* - Object URL Generation: Creates efficient object URLs for cached blobs.
|
|
44
|
+
* - HEAD Request Optimization: Uses HEAD requests to check metadata without downloading full files.
|
|
45
|
+
* - Promise-Based API: All operations return promises for async/await usage.
|
|
46
|
+
*
|
|
47
|
+
* Usage Example:
|
|
48
|
+
* const storage = new FileStorage("MyFilesDB", "CachedFiles");
|
|
49
|
+
*
|
|
50
|
+
* // Get file blob with automatic caching
|
|
51
|
+
* const file = await storage.get("https://example.com/model.glb");
|
|
52
|
+
*
|
|
53
|
+
* // Get object URL for cached file
|
|
54
|
+
* const url = await storage.getURL("https://example.com/model.glb");
|
|
55
|
+
*
|
|
56
|
+
* // Check file timestamp
|
|
57
|
+
* const timestamp = await storage.getTimeStamp("https://example.com/model.glb");
|
|
58
|
+
*
|
|
59
|
+
* // Get file size
|
|
60
|
+
* const size = await storage.getSize("https://example.com/model.glb");
|
|
61
|
+
*
|
|
62
|
+
* // Manually cache a file
|
|
63
|
+
* await storage.put("https://example.com/model.glb");
|
|
64
|
+
*
|
|
65
|
+
* Cache Behavior:
|
|
66
|
+
* - First call: Downloads from server and caches in IndexedDB
|
|
67
|
+
* - Subsequent calls: Returns cached version if timestamp matches
|
|
68
|
+
* - Updated file: Detects timestamp change and re-downloads automatically
|
|
69
|
+
* - No server access: Returns cached version if available
|
|
70
|
+
* - IndexedDB unavailable: Falls back to direct server access
|
|
71
|
+
*
|
|
72
|
+
* Return Values:
|
|
73
|
+
* - get(uri): Blob | undefined | false
|
|
74
|
+
* - Blob: File successfully retrieved
|
|
75
|
+
* - undefined: Download failed but no cached copy exists
|
|
76
|
+
* - false: URI is falsy
|
|
77
|
+
*
|
|
78
|
+
* - getURL(uri): string | boolean
|
|
79
|
+
* - string: Object URL or original URI
|
|
80
|
+
* - false: File not available
|
|
81
|
+
*
|
|
82
|
+
* - getTimeStamp(uri): string | null
|
|
83
|
+
* - string: ISO 8601 timestamp
|
|
84
|
+
* - null: Timestamp unavailable
|
|
85
|
+
*
|
|
86
|
+
* - getSize(uri): number | null
|
|
87
|
+
* - number: File size in bytes
|
|
88
|
+
* - null: Size unavailable
|
|
89
|
+
*
|
|
90
|
+
* - put(uri): boolean
|
|
91
|
+
* - true: Successfully cached
|
|
92
|
+
* - false: Failed to cache
|
|
93
|
+
*
|
|
94
|
+
* Storage Limits:
|
|
95
|
+
* - Chrome/Edge: ~50GB per origin
|
|
96
|
+
* - Firefox: ~1GB per origin
|
|
97
|
+
* - Safari: ~1GB per origin
|
|
98
|
+
*
|
|
99
|
+
* Notes:
|
|
100
|
+
* - Requires CORS headers for cross-origin file access
|
|
101
|
+
* - Uses XMLHttpRequest for file downloads and HEAD requests
|
|
102
|
+
* - Supports both HTTPS and HTTP (HTTP not recommended for production)
|
|
103
|
+
* - Object URLs should be revoked after use to free memory
|
|
104
|
+
* - IndexedDB quota management should be implemented for long-term storage
|
|
105
|
+
*
|
|
106
|
+
* Error Handling:
|
|
107
|
+
* - Network failures: Returns undefined (get) or false (other methods)
|
|
108
|
+
* - IndexedDB errors: Falls back to server access or returns false
|
|
109
|
+
* - CORS errors: Treated as download failures
|
|
110
|
+
* - Invalid URIs: Returns false or undefined
|
|
111
|
+
*
|
|
112
|
+
* References:
|
|
113
|
+
* - MDN IndexedDB API: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
|
|
114
|
+
* - idb Library: https://github.com/jakearchibald/idb
|
|
115
|
+
* - npm idb: https://www.npmjs.com/package/idb
|
|
116
|
+
*/
|
|
10
117
|
export class FileStorage {
|
|
11
118
|
#supported = "indexedDB" in window && openDB; // true if IndexedDB is available in the browser and the idb helper is loaded
|
|
12
119
|
#dbVersion = 1; // single DB version; cache is managed per-file
|
|
@@ -19,19 +126,22 @@ export class FileStorage {
|
|
|
19
126
|
|
|
20
127
|
/**
|
|
21
128
|
* Create the object store if it does not exist.
|
|
129
|
+
* @private
|
|
22
130
|
* @param {IDBDatabase} db IndexedDB database instance
|
|
131
|
+
* @param {String} osName Object store name
|
|
23
132
|
*/
|
|
24
|
-
#createObjectStore
|
|
133
|
+
#createObjectStore(db, osName) {
|
|
25
134
|
if (!db.objectStoreNames.contains(osName)) {
|
|
26
135
|
db.createObjectStore(osName);
|
|
27
136
|
}
|
|
28
|
-
}
|
|
137
|
+
}
|
|
29
138
|
|
|
30
139
|
/**
|
|
31
140
|
* Open the IndexedDB database (creating it if needed) and ensure the object store for files exists.
|
|
141
|
+
* @private
|
|
32
142
|
* @returns {Promise<IDBDatabase|null>} Resolves with the opened database or null if IndexedDB is not supported or opening fails.
|
|
33
143
|
*/
|
|
34
|
-
#openDB
|
|
144
|
+
async #openDB() {
|
|
35
145
|
if (!this.#supported) {
|
|
36
146
|
return null;
|
|
37
147
|
}
|
|
@@ -45,16 +155,17 @@ export class FileStorage {
|
|
|
45
155
|
} catch (error) {
|
|
46
156
|
return null;
|
|
47
157
|
}
|
|
48
|
-
}
|
|
158
|
+
}
|
|
49
159
|
|
|
50
160
|
/**
|
|
51
161
|
* Download the file from the server (forced download).
|
|
162
|
+
* @private
|
|
52
163
|
* @param {String} uri File URI
|
|
53
164
|
* @returns {Promise<{blob: Blob, timeStamp: string}|undefined>} Resolves with:
|
|
54
165
|
* - { blob, timeStamp }: object with the downloaded Blob and the Last-Modified timestamp (ISO string) on success
|
|
55
166
|
* - undefined: if the download fails (non-200 status or network error)
|
|
56
167
|
*/
|
|
57
|
-
#getServerFile
|
|
168
|
+
async #getServerFile(uri) {
|
|
58
169
|
let file = undefined;
|
|
59
170
|
return new Promise((resolve) => {
|
|
60
171
|
const xhr = new XMLHttpRequest();
|
|
@@ -75,16 +186,17 @@ export class FileStorage {
|
|
|
75
186
|
};
|
|
76
187
|
xhr.send();
|
|
77
188
|
});
|
|
78
|
-
}
|
|
189
|
+
}
|
|
79
190
|
|
|
80
191
|
/**
|
|
81
192
|
* Get the Last-Modified timestamp of the file on the server without downloading its content.
|
|
193
|
+
* @private
|
|
82
194
|
* @param {String} uri File URI
|
|
83
195
|
* @returns {Promise<string|null>} Resolves with:
|
|
84
196
|
* - ISO 8601 string from the Last-Modified header if available
|
|
85
197
|
* - null if the header is not present or on error
|
|
86
198
|
*/
|
|
87
|
-
#getServerFileTimeStamp
|
|
199
|
+
async #getServerFileTimeStamp(uri) {
|
|
88
200
|
let timeStamp = null;
|
|
89
201
|
return new Promise((resolve) => {
|
|
90
202
|
const xhr = new XMLHttpRequest();
|
|
@@ -103,16 +215,17 @@ export class FileStorage {
|
|
|
103
215
|
};
|
|
104
216
|
xhr.send();
|
|
105
217
|
});
|
|
106
|
-
}
|
|
218
|
+
}
|
|
107
219
|
|
|
108
220
|
/**
|
|
109
221
|
* Get the size of the file on the server without downloading its content.
|
|
222
|
+
* @private
|
|
110
223
|
* @param {String} uri File URI
|
|
111
224
|
* @returns {Promise<number>} Promise that resolves with:
|
|
112
225
|
* - number: Content-Length in bytes when the HEAD request succeeds and the header is present
|
|
113
226
|
* - 0: when the header is missing or the request fails
|
|
114
227
|
*/
|
|
115
|
-
#getServerFileSize
|
|
228
|
+
async #getServerFileSize(uri) {
|
|
116
229
|
let size = 0;
|
|
117
230
|
return new Promise((resolve) => {
|
|
118
231
|
const xhr = new XMLHttpRequest();
|
|
@@ -131,10 +244,11 @@ export class FileStorage {
|
|
|
131
244
|
};
|
|
132
245
|
xhr.send();
|
|
133
246
|
});
|
|
134
|
-
}
|
|
247
|
+
}
|
|
135
248
|
|
|
136
249
|
/**
|
|
137
250
|
* Stores a file record in IndexedDB under the provided key (uri).
|
|
251
|
+
* @private
|
|
138
252
|
* @param {Object|Blob} file The value to store (for example an object {blob, timeStamp} or a Blob).
|
|
139
253
|
* @param {String} uri Key to use for storage (the original URI).
|
|
140
254
|
* @returns {Promise<any|undefined>} Resolves with the value returned by the underlying idb store.put (usually the record key — string or number)
|
|
@@ -142,7 +256,7 @@ export class FileStorage {
|
|
|
142
256
|
* - resolves to undefined if the database could not be opened
|
|
143
257
|
* - rejects if the underlying put operation throws
|
|
144
258
|
*/
|
|
145
|
-
#putFile
|
|
259
|
+
async #putFile(file, uri) {
|
|
146
260
|
if (this.#db === undefined) {
|
|
147
261
|
this.#db = await this.#openDB();
|
|
148
262
|
}
|
|
@@ -152,17 +266,18 @@ export class FileStorage {
|
|
|
152
266
|
const transation = this.#db.transaction(this.osName, "readwrite");
|
|
153
267
|
const store = transation.objectStore(this.osName);
|
|
154
268
|
return store.put(file, uri);
|
|
155
|
-
}
|
|
269
|
+
}
|
|
156
270
|
|
|
157
271
|
/**
|
|
158
272
|
* Retrieve a file record from IndexedDB.
|
|
273
|
+
* @private
|
|
159
274
|
* @param {String} uri File URI
|
|
160
275
|
* @returns {Promise<{blob: Blob, timeStamp: string}|undefined|false>} Resolves with:
|
|
161
276
|
* - {blob, timeStamp}: object with the Blob and ISO timestamp if the entry exists in IndexedDB
|
|
162
277
|
* - undefined: if there is no stored entry for that URI
|
|
163
278
|
* - false: if the database could not be opened (IndexedDB unsupported or open failure)
|
|
164
279
|
*/
|
|
165
|
-
#getFile
|
|
280
|
+
async #getFile(uri) {
|
|
166
281
|
if (this.#db === undefined) {
|
|
167
282
|
this.#db = await this.#openDB();
|
|
168
283
|
}
|
|
@@ -172,75 +287,86 @@ export class FileStorage {
|
|
|
172
287
|
const transation = this.#db.transaction(this.osName, "readonly");
|
|
173
288
|
const store = transation.objectStore(this.osName);
|
|
174
289
|
return store.get(uri);
|
|
175
|
-
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* ---------------------------
|
|
294
|
+
* Public methods
|
|
295
|
+
* ---------------------------
|
|
296
|
+
*/
|
|
176
297
|
|
|
177
298
|
/**
|
|
178
299
|
* Get a URL to the file: either an object URL for the cached Blob, or the original URI if the server copy is available and IndexedDB isn't supported.
|
|
179
|
-
* @
|
|
300
|
+
* @public
|
|
301
|
+
* @param {String} uri - File URI
|
|
180
302
|
* @returns {Promise<string|boolean>} Resolves with:
|
|
181
303
|
* - object URL string for the cached Blob
|
|
182
304
|
* - original URI string if IndexedDB unsupported but server file exists
|
|
183
305
|
* - false if the file is not available on the server
|
|
184
306
|
*/
|
|
185
|
-
|
|
307
|
+
async getURL(uri) {
|
|
186
308
|
if (!this.#supported) {
|
|
187
309
|
return (await this.#getServerFileTimeStamp(uri)) ? uri : false;
|
|
188
310
|
}
|
|
189
311
|
const storedFile = await this.get(uri);
|
|
190
312
|
return storedFile ? URL.createObjectURL(storedFile.blob) : (await this.#getServerFileTimeStamp(uri)) ? uri : false;
|
|
191
|
-
}
|
|
313
|
+
}
|
|
192
314
|
|
|
193
315
|
/**
|
|
194
316
|
* Get the cached or server Last-Modified timestamp for a file.
|
|
195
|
-
* @
|
|
317
|
+
* @public
|
|
318
|
+
* @param {String} uri - File URI
|
|
196
319
|
* @returns {Promise<string|null>} Resolves with:
|
|
197
320
|
* - ISO 8601 string: Last-Modified timestamp from the cached record (when using IndexedDB) or from the server HEAD response
|
|
198
321
|
* - null: when no timestamp is available or on error
|
|
199
322
|
*/
|
|
200
|
-
|
|
323
|
+
async getTimeStamp(uri) {
|
|
201
324
|
if (!this.#supported) {
|
|
202
325
|
const serverFileTimeStamp = await this.#getServerFileTimeStamp(uri);
|
|
203
326
|
return serverFileTimeStamp ? serverFileTimeStamp : null;
|
|
204
327
|
}
|
|
205
328
|
const storedFile = await this.get(uri);
|
|
206
329
|
return storedFile ? storedFile.timeStamp : null;
|
|
207
|
-
}
|
|
330
|
+
}
|
|
208
331
|
|
|
209
332
|
/**
|
|
210
333
|
* Get the cached or server size of a file in bytes.
|
|
211
|
-
* @
|
|
334
|
+
* @public
|
|
335
|
+
* @param {String} uri - File URI
|
|
212
336
|
* @returns {Promise<number|null>} Resolves with:
|
|
213
337
|
* - number: size in bytes when available (from cache or server)
|
|
214
338
|
* - null: when size is not available or on error
|
|
215
339
|
*/
|
|
216
|
-
|
|
340
|
+
async getSize(uri) {
|
|
217
341
|
if (!this.#supported) {
|
|
218
342
|
const serverFileSize = await this.#getServerFileSize(uri);
|
|
219
343
|
return serverFileSize ? serverFileSize : null;
|
|
220
344
|
}
|
|
221
345
|
const storedFile = await this.get(uri);
|
|
222
346
|
return storedFile ? storedFile.blob.size : null;
|
|
223
|
-
}
|
|
347
|
+
}
|
|
224
348
|
|
|
225
349
|
/**
|
|
226
350
|
* Retrieve the Blob for a file from cache or server.
|
|
227
|
-
* @
|
|
351
|
+
* @public
|
|
352
|
+
* @param {String} uri - File URI
|
|
228
353
|
* @returns {Promise<Blob|false>} Resolves with:
|
|
229
354
|
* - Blob: the file blob when available (from cache or server)
|
|
230
355
|
* - false: when the file cannot be retrieved
|
|
231
356
|
*/
|
|
232
|
-
|
|
357
|
+
async getBlob(uri) {
|
|
233
358
|
if (!this.#supported) {
|
|
234
359
|
const serverFile = await this.#getServerFile(uri);
|
|
235
360
|
return serverFile ? serverFile.blob : false;
|
|
236
361
|
}
|
|
237
362
|
const storedFile = await this.get(uri);
|
|
238
363
|
return storedFile ? storedFile.blob : false;
|
|
239
|
-
}
|
|
364
|
+
}
|
|
240
365
|
|
|
241
366
|
/**
|
|
242
367
|
* Get the file Blob from IndexedDB if stored; otherwise download and store it, then return the Blob.
|
|
243
|
-
* @
|
|
368
|
+
* @public
|
|
369
|
+
* @param {String} uri - File URI
|
|
244
370
|
* @returns {Promise<Blob|undefined|false>} Resolves with:
|
|
245
371
|
* - Blob of the stored file
|
|
246
372
|
* - undefined if the download fails
|
|
@@ -250,7 +376,7 @@ export class FileStorage {
|
|
|
250
376
|
* the file is re-downloaded from the server and stored again in IndexedDB.
|
|
251
377
|
* If the server file is not available at that time to check its age but is stored, the stored file is returned.
|
|
252
378
|
*/
|
|
253
|
-
|
|
379
|
+
async get(uri) {
|
|
254
380
|
if (!uri) {
|
|
255
381
|
return false;
|
|
256
382
|
}
|
|
@@ -269,20 +395,21 @@ export class FileStorage {
|
|
|
269
395
|
}
|
|
270
396
|
}
|
|
271
397
|
return storedFile;
|
|
272
|
-
}
|
|
398
|
+
}
|
|
273
399
|
|
|
274
400
|
/**
|
|
275
401
|
* Store the file in IndexedDB.
|
|
276
|
-
* @
|
|
402
|
+
* @public
|
|
403
|
+
* @param {String} uri - File URI
|
|
277
404
|
* @returns {Promise<boolean>} Resolves with:
|
|
278
405
|
* - true if the file was stored successfully
|
|
279
406
|
* - false if IndexedDB is not supported or storing failed
|
|
280
407
|
*/
|
|
281
|
-
|
|
408
|
+
async put(uri) {
|
|
282
409
|
if (!this.#supported) {
|
|
283
410
|
return false;
|
|
284
411
|
}
|
|
285
412
|
const fileToStore = await this.#getServerFile(uri);
|
|
286
413
|
return fileToStore ? !!(await this.#putFile(fileToStore, uri)) : false;
|
|
287
|
-
}
|
|
414
|
+
}
|
|
288
415
|
}
|