@overmap-ai/core 1.0.53-fix-outbox.1 → 1.0.53-fix-outbox.3
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.
|
@@ -5,8 +5,8 @@ var __publicField = (obj, key, value) => {
|
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
7
|
(function(global2, factory) {
|
|
8
|
-
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react"), require("react/jsx-runtime"), require("@overmap-ai/blocks"), require("dependency-graph"), require("@redux-offline/redux-offline"), require("@redux-offline/redux-offline/lib/defaults"), require("localforage"), require("redux-persist-migrate"), require("@reduxjs/toolkit"), require("superagent"), require("react-redux"), require("
|
|
9
|
-
})(this, function(exports2, React, jsxRuntime, blocks, dependencyGraph, reduxOffline, offlineConfig, localforage, createMigration, toolkit, request, reactRedux,
|
|
8
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react"), require("react/jsx-runtime"), require("@overmap-ai/blocks"), require("dependency-graph"), require("file-saver"), require("@redux-offline/redux-offline"), require("@redux-offline/redux-offline/lib/defaults"), require("localforage"), require("redux-persist-migrate"), require("@reduxjs/toolkit"), require("superagent"), require("react-redux"), require("uuid"), require("color"), require("jwt-decode"), require("@redux-offline/redux-offline/lib/constants"), require("idb"), require("formik"), require("react-dom"), require("lodash.get"), require("linkify-react"), require("@hello-pangea/dnd"), require("qr-scanner"), require("xlsx"), require("react-pdf"), require("react-pdf/dist/Page/AnnotationLayer.css"), require("react-pdf/dist/Page/TextLayer.css"), require("react-sketch-canvas"), require("lodash.set"), require("lodash.clonedeep")) : typeof define === "function" && define.amd ? define(["exports", "react", "react/jsx-runtime", "@overmap-ai/blocks", "dependency-graph", "file-saver", "@redux-offline/redux-offline", "@redux-offline/redux-offline/lib/defaults", "localforage", "redux-persist-migrate", "@reduxjs/toolkit", "superagent", "react-redux", "uuid", "color", "jwt-decode", "@redux-offline/redux-offline/lib/constants", "idb", "formik", "react-dom", "lodash.get", "linkify-react", "@hello-pangea/dnd", "qr-scanner", "xlsx", "react-pdf", "react-pdf/dist/Page/AnnotationLayer.css", "react-pdf/dist/Page/TextLayer.css", "react-sketch-canvas", "lodash.set", "lodash.clonedeep"], factory) : (global2 = typeof globalThis !== "undefined" ? globalThis : global2 || self, factory(global2["overmap-core"] = {}, global2.React, global2.jsxRuntime, global2.blocks, global2.dependencyGraph, global2.saveAs, global2.reduxOffline, global2.offlineConfig, global2.localforage, global2.createMigration, global2.toolkit, global2.request, global2.reactRedux, global2.uuid, global2.ColorCls, global2.jwtDecode, global2.constants, global2.idb, global2.formik, global2.ReactDOM, global2.get, global2.Linkify, global2.dnd, global2.QrScannerAPI, global2.xlsx, global2.reactPdf, null, null, global2.reactSketchCanvas, global2.set, global2.cloneDeep));
|
|
9
|
+
})(this, function(exports2, React, jsxRuntime, blocks, dependencyGraph, saveAs, reduxOffline, offlineConfig, localforage, createMigration, toolkit, request, reactRedux, uuid, ColorCls, jwtDecode, constants, idb, formik, ReactDOM, get, Linkify, dnd, QrScannerAPI, xlsx, reactPdf, AnnotationLayer_css, TextLayer_css, reactSketchCanvas, set, cloneDeep) {
|
|
10
10
|
var _a;
|
|
11
11
|
"use strict";
|
|
12
12
|
function _interopNamespaceDefault(e) {
|
|
@@ -26,6 +26,354 @@ var __publicField = (obj, key, value) => {
|
|
|
26
26
|
return Object.freeze(n);
|
|
27
27
|
}
|
|
28
28
|
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
|
|
29
|
+
class OutboxCoordinator {
|
|
30
|
+
constructor() {
|
|
31
|
+
__publicField(this, "graph");
|
|
32
|
+
__publicField(this, "requestAttemptCounter");
|
|
33
|
+
this.graph = new dependencyGraph.DepGraph();
|
|
34
|
+
this.requestAttemptCounter = {};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Used when the app is loaded. Reconstructs the dependency graph based on an outbox from the redux-offline store.
|
|
38
|
+
*/
|
|
39
|
+
static _fromOutbox(outbox) {
|
|
40
|
+
const ret = new OutboxCoordinator();
|
|
41
|
+
for (let i = 0; i < outbox.length; i++) {
|
|
42
|
+
const outboxItem = outbox[i];
|
|
43
|
+
if (!outboxItem) {
|
|
44
|
+
console.error("Outbox item was undefined");
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
ret.sneakRequest(outboxItem);
|
|
48
|
+
for (let j = 0; j < i; j++) {
|
|
49
|
+
const previousOutboxItem = outbox[j];
|
|
50
|
+
if (!previousOutboxItem) {
|
|
51
|
+
console.error("Previous outbox item was undefined");
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (previousOutboxItem.payload.uuid === outboxItem.payload.uuid) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (previousOutboxItem.payload.blocks.some((block) => outboxItem.payload.blockers.includes(block))) {
|
|
58
|
+
OutboxCoordinator._addDependency(
|
|
59
|
+
outboxItem.payload.uuid,
|
|
60
|
+
previousOutboxItem.payload.uuid,
|
|
61
|
+
ret.graph
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return ret;
|
|
67
|
+
}
|
|
68
|
+
_addDependency(from, to) {
|
|
69
|
+
OutboxCoordinator._addDependency(from, to, this.graph);
|
|
70
|
+
}
|
|
71
|
+
static _addDependency(from, to, graph) {
|
|
72
|
+
if (from === to) {
|
|
73
|
+
throw new Error(`Tried to add dependency from node to itself: ${from}`);
|
|
74
|
+
}
|
|
75
|
+
const fromExists = graph.hasNode(from);
|
|
76
|
+
if (!fromExists) {
|
|
77
|
+
throw new Error(`Tried to add dependency from non-existent node: ${from} (to node: ${to})`);
|
|
78
|
+
}
|
|
79
|
+
const toExists = graph.hasNode(to);
|
|
80
|
+
if (!toExists) {
|
|
81
|
+
throw new Error(`Tried to add dependency to non-existent node: ${to} (from node: ${from})`);
|
|
82
|
+
}
|
|
83
|
+
graph.addDependency(from, to);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* If there are one or more nodes in the graph, we find all nodes that match a dependency of the request and add a
|
|
87
|
+
* dependency from the new request node to that node.
|
|
88
|
+
*/
|
|
89
|
+
addRequest(request2) {
|
|
90
|
+
this.graph.addNode(request2.payload.uuid, request2);
|
|
91
|
+
if (request2.payload.blockers.length === 0 || this.graph.size() === 1) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const node of this.graph.overallOrder()) {
|
|
95
|
+
if (node === request2.payload.uuid)
|
|
96
|
+
continue;
|
|
97
|
+
const details = this.graph.getNodeData(node);
|
|
98
|
+
if (request2.payload.blockers.some((blocker) => details.payload.blocks.includes(blocker))) {
|
|
99
|
+
this._addDependency(request2.payload.uuid, node);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Inserts a request at the beginning of the queue. This could be used for requests that were popped, then failed,
|
|
105
|
+
* and need to be re-enqueued at the front of the queue. Any requests that were previously blocked by this request
|
|
106
|
+
* will be blocked by this request again. No blockers will be added to this request because it is assumed that the
|
|
107
|
+
* request was already unblocked when it was popped.
|
|
108
|
+
* @param request The request to insert at the beginning of the queue.
|
|
109
|
+
*/
|
|
110
|
+
insertRequest(request2) {
|
|
111
|
+
this.graph.addNode(request2.payload.uuid, request2);
|
|
112
|
+
for (const node of this.graph.overallOrder()) {
|
|
113
|
+
if (node === request2.payload.uuid)
|
|
114
|
+
continue;
|
|
115
|
+
const details = this.graph.getNodeData(node);
|
|
116
|
+
if (details.payload.blockers.some((blocker) => request2.payload.blocks.includes(blocker))) {
|
|
117
|
+
this._addDependency(node, request2.payload.uuid);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Sneaks a request into the dependency graph without creating any blockers. Useful for reconstructing the graph
|
|
123
|
+
* (from the offline store's outbox state) when the app is loaded.
|
|
124
|
+
*/
|
|
125
|
+
sneakRequest(request2) {
|
|
126
|
+
this.graph.addNode(request2.payload.uuid, request2);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Returns the next node in line to be sent. This is the unblocked node with the fewest number of attempts. If there
|
|
130
|
+
* are multiple nodes with the same number of attempts, the first one (by insertion order) will be returned.
|
|
131
|
+
*/
|
|
132
|
+
_getNextNode() {
|
|
133
|
+
const leafNodes = this.graph.overallOrder(true);
|
|
134
|
+
let minAttempts = Infinity;
|
|
135
|
+
let minAttemptsNode;
|
|
136
|
+
for (const node of leafNodes) {
|
|
137
|
+
const attempts = this.requestAttemptCounter[node] || 0;
|
|
138
|
+
if (attempts < minAttempts) {
|
|
139
|
+
minAttempts = attempts;
|
|
140
|
+
minAttemptsNode = node;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return minAttemptsNode;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Returns the next request in line to be sent without removing it.
|
|
147
|
+
*/
|
|
148
|
+
peek() {
|
|
149
|
+
const nextNode = this._getNextNode();
|
|
150
|
+
if (!nextNode)
|
|
151
|
+
return void 0;
|
|
152
|
+
return this.graph.getNodeData(nextNode);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Removes a request from the graph. This should be called when a request is successfully sent.
|
|
156
|
+
* @param uuid The UUID of the request to remove.
|
|
157
|
+
*/
|
|
158
|
+
remove(uuid2) {
|
|
159
|
+
this.graph.removeNode(uuid2);
|
|
160
|
+
delete this.requestAttemptCounter[uuid2];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Returns the next request in line to be sent and removes it from the graph.
|
|
164
|
+
*/
|
|
165
|
+
pop() {
|
|
166
|
+
const nextRequestDetails = this.peek();
|
|
167
|
+
if (nextRequestDetails) {
|
|
168
|
+
this.graph.removeNode(nextRequestDetails.payload.uuid);
|
|
169
|
+
}
|
|
170
|
+
return nextRequestDetails;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Gets the current queue for the outbox. Should be called to get a new value for the outbox slice every time a
|
|
174
|
+
* request is enqueued. It will be used to render the outbox items in a single lane.
|
|
175
|
+
*/
|
|
176
|
+
getQueue() {
|
|
177
|
+
const ret = this.graph.overallOrder().map((nodeName) => this.graph.getNodeData(nodeName));
|
|
178
|
+
const nextNode = this._getNextNode();
|
|
179
|
+
if (nextNode) {
|
|
180
|
+
const nextRequestDetails = this.graph.getNodeData(nextNode);
|
|
181
|
+
const nextRequestIndex = ret.findIndex(
|
|
182
|
+
(request2) => request2.payload.uuid === nextRequestDetails.payload.uuid
|
|
183
|
+
);
|
|
184
|
+
if (nextRequestIndex !== -1) {
|
|
185
|
+
ret.splice(nextRequestIndex, 1);
|
|
186
|
+
ret.unshift(nextRequestDetails);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return ret;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Gets a list of requests that can currently be sent (requests with no unresolved dependencies). Used to process
|
|
193
|
+
* the next ready request in case the request at the front of the queue is failing.
|
|
194
|
+
*/
|
|
195
|
+
getReady() {
|
|
196
|
+
let ret = this.graph.overallOrder(true).map((nodeName) => this.graph.getNodeData(nodeName));
|
|
197
|
+
ret = ret.sort((a, b) => {
|
|
198
|
+
return a.meta.offline.effect.timestamp.localeCompare(b.meta.offline.effect.timestamp);
|
|
199
|
+
});
|
|
200
|
+
ret = ret.sort((a, b) => {
|
|
201
|
+
const aAttempts = this.requestAttemptCounter[a.payload.uuid] || 0;
|
|
202
|
+
const bAttempts = this.requestAttemptCounter[b.payload.uuid] || 0;
|
|
203
|
+
return aAttempts - bAttempts;
|
|
204
|
+
});
|
|
205
|
+
return ret;
|
|
206
|
+
}
|
|
207
|
+
registerRetry(uuid2) {
|
|
208
|
+
this.requestAttemptCounter[uuid2] = (this.requestAttemptCounter[uuid2] || 0) + 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const UNKNOWN_ERROR_MESSAGE = "An unknown error occurred";
|
|
212
|
+
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
213
|
+
const _SPECIAL_KEYS = ["non_field_errors", "detail"];
|
|
214
|
+
function extractErrorMessage(errorRes, err) {
|
|
215
|
+
let ret;
|
|
216
|
+
if (errorRes == null ? void 0 : errorRes.body) {
|
|
217
|
+
if (typeof errorRes.body === "object") {
|
|
218
|
+
const responseBody = errorRes.body;
|
|
219
|
+
if (typeof responseBody.error === "string") {
|
|
220
|
+
ret = responseBody.error;
|
|
221
|
+
} else if (typeof responseBody.message === "string") {
|
|
222
|
+
ret = responseBody.message;
|
|
223
|
+
} else if (responseBody.body) {
|
|
224
|
+
try {
|
|
225
|
+
ret = Object.entries(responseBody.body).map(([key, value]) => {
|
|
226
|
+
if (typeof value === "string") {
|
|
227
|
+
if (_SPECIAL_KEYS.includes(key))
|
|
228
|
+
return value;
|
|
229
|
+
return `${key}: ${value}`;
|
|
230
|
+
}
|
|
231
|
+
if (Array.isArray(value)) {
|
|
232
|
+
if (_SPECIAL_KEYS.includes(key))
|
|
233
|
+
return value.join("\n");
|
|
234
|
+
return value.map((v) => `${key}: ${v}`).join("\n");
|
|
235
|
+
}
|
|
236
|
+
return `${key}: ${JSON.stringify(value)}`;
|
|
237
|
+
}).join("\n");
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.error("Failed to extract error message from response body", e);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} else if (typeof errorRes.body === "string") {
|
|
243
|
+
ret = errorRes.body;
|
|
244
|
+
}
|
|
245
|
+
} else if (errorRes == null ? void 0 : errorRes.text) {
|
|
246
|
+
ret = errorRes.text;
|
|
247
|
+
} else if (err instanceof Error) {
|
|
248
|
+
ret = err.message;
|
|
249
|
+
}
|
|
250
|
+
if (!ret || ret.length > MAX_ERROR_MESSAGE_LENGTH) {
|
|
251
|
+
return UNKNOWN_ERROR_MESSAGE;
|
|
252
|
+
}
|
|
253
|
+
return ret;
|
|
254
|
+
}
|
|
255
|
+
class APIError extends Error {
|
|
256
|
+
constructor(options) {
|
|
257
|
+
super(UNKNOWN_ERROR_MESSAGE);
|
|
258
|
+
// NOTE: Needs to conform to NetworkError in @redux-offline/redux-offline, which has `status` and `response`.
|
|
259
|
+
__publicField(this, "status");
|
|
260
|
+
__publicField(this, "response");
|
|
261
|
+
__publicField(this, "message");
|
|
262
|
+
__publicField(this, "options");
|
|
263
|
+
const { response, innerError } = options;
|
|
264
|
+
this.message = options.message ?? extractErrorMessage(response, innerError) ?? UNKNOWN_ERROR_MESSAGE;
|
|
265
|
+
this.status = (response == null ? void 0 : response.status) ?? 0;
|
|
266
|
+
this.response = response;
|
|
267
|
+
options.discard = options.discard ?? false;
|
|
268
|
+
this.options = options;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
class BaseApiService {
|
|
272
|
+
constructor(sdk) {
|
|
273
|
+
__publicField(this, "client");
|
|
274
|
+
this.client = sdk;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function hex(buffer) {
|
|
278
|
+
const hashArray = new Uint8Array(buffer);
|
|
279
|
+
return hashArray.reduce((data, byte) => data + byte.toString(16).padStart(2, "0"), "");
|
|
280
|
+
}
|
|
281
|
+
const getFileS3Key = async (file, hash) => {
|
|
282
|
+
if (!hash) {
|
|
283
|
+
hash = await hashFile(file);
|
|
284
|
+
}
|
|
285
|
+
let fileType = file.type;
|
|
286
|
+
if (fileType.includes("/")) {
|
|
287
|
+
fileType = fileType.split("/")[1];
|
|
288
|
+
}
|
|
289
|
+
if (!fileType) {
|
|
290
|
+
throw new Error(`Could not extract file type from ${file.type}`);
|
|
291
|
+
}
|
|
292
|
+
return `${hash}.${fileType}`;
|
|
293
|
+
};
|
|
294
|
+
function hashFile(file) {
|
|
295
|
+
return new Promise((resolve, reject) => {
|
|
296
|
+
const reader = new FileReader();
|
|
297
|
+
reader.onload = () => {
|
|
298
|
+
const fileResult = reader.result;
|
|
299
|
+
if (!fileResult) {
|
|
300
|
+
reject();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
void crypto.subtle.digest("SHA-1", fileResult).then((hash) => {
|
|
304
|
+
const sha1result = hex(hash);
|
|
305
|
+
resolve(sha1result);
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
reader.readAsArrayBuffer(file);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
function getFileIdentifier(file) {
|
|
312
|
+
if (!file.name || !file.type || !file.size) {
|
|
313
|
+
const message = "File has no name, type, and/or size";
|
|
314
|
+
console.error(`${message}`, file);
|
|
315
|
+
throw new Error(`${message}.`);
|
|
316
|
+
}
|
|
317
|
+
return `${file.name}&${file.type}${file.size}`;
|
|
318
|
+
}
|
|
319
|
+
function getRenamedFile(file, newName) {
|
|
320
|
+
return new File([file], newName, { type: file.type });
|
|
321
|
+
}
|
|
322
|
+
function downloadInMemoryFile(filename, text) {
|
|
323
|
+
const element = document.createElement("a");
|
|
324
|
+
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
|
|
325
|
+
element.setAttribute("download", filename);
|
|
326
|
+
element.style.display = "none";
|
|
327
|
+
document.body.appendChild(element);
|
|
328
|
+
element.click();
|
|
329
|
+
document.body.removeChild(element);
|
|
330
|
+
}
|
|
331
|
+
const constructUploadedFilePayloads = async (files) => {
|
|
332
|
+
const filePayloads = {};
|
|
333
|
+
for (const file of files) {
|
|
334
|
+
const sha1 = await hashFile(file);
|
|
335
|
+
filePayloads[sha1] = {
|
|
336
|
+
sha1,
|
|
337
|
+
extension: file.name.split(".").pop() || "",
|
|
338
|
+
file_type: file.type,
|
|
339
|
+
size: file.size
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return Object.values(filePayloads);
|
|
343
|
+
};
|
|
344
|
+
const fileToBlob = async (dataUrl) => {
|
|
345
|
+
return (await fetch(dataUrl)).blob();
|
|
346
|
+
};
|
|
347
|
+
const blobToBase64 = (blob) => {
|
|
348
|
+
return new Promise((resolve, _) => {
|
|
349
|
+
const reader = new FileReader();
|
|
350
|
+
reader.onloadend = () => {
|
|
351
|
+
var _a2;
|
|
352
|
+
resolve(((_a2 = reader.result) == null ? void 0 : _a2.toString()) || "");
|
|
353
|
+
};
|
|
354
|
+
reader.readAsDataURL(blob);
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
const useFileSrc = (props) => {
|
|
358
|
+
const { file, fileSha1, placeholder } = props;
|
|
359
|
+
const [src, setSrc] = React.useState(placeholder);
|
|
360
|
+
const { sdk } = useSDK();
|
|
361
|
+
React.useEffect(() => {
|
|
362
|
+
if (!fileSha1 || !file)
|
|
363
|
+
return;
|
|
364
|
+
sdk.files.fetchFileFromUrl(file, fileSha1).then((file2) => {
|
|
365
|
+
setSrc(URL.createObjectURL(file2));
|
|
366
|
+
}).catch((reason) => {
|
|
367
|
+
console.error(`Failed to fetch file ${file} (${fileSha1}):
|
|
368
|
+
`, reason);
|
|
369
|
+
});
|
|
370
|
+
}, [file, fileSha1, sdk.files]);
|
|
371
|
+
return src;
|
|
372
|
+
};
|
|
373
|
+
function downloadFile(file) {
|
|
374
|
+
const blob = new Blob([file]);
|
|
375
|
+
saveAs.saveAs(blob, file.name);
|
|
376
|
+
}
|
|
29
377
|
const global$1 = typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
|
|
30
378
|
function defaultSetTimout() {
|
|
31
379
|
throw new Error("setTimeout has not been defined");
|
|
@@ -502,108 +850,8 @@ var __publicField = (obj, key, value) => {
|
|
|
502
850
|
}
|
|
503
851
|
}
|
|
504
852
|
}
|
|
505
|
-
}
|
|
506
|
-
return classes.join(" ");
|
|
507
|
-
}
|
|
508
|
-
function hex(buffer) {
|
|
509
|
-
const hashArray = new Uint8Array(buffer);
|
|
510
|
-
return hashArray.reduce((data, byte) => data + byte.toString(16).padStart(2, "0"), "");
|
|
511
|
-
}
|
|
512
|
-
const getFileS3Key = async (file, hash) => {
|
|
513
|
-
if (!hash) {
|
|
514
|
-
hash = await hashFile(file);
|
|
515
|
-
}
|
|
516
|
-
let fileType = file.type;
|
|
517
|
-
if (fileType.includes("/")) {
|
|
518
|
-
fileType = fileType.split("/")[1];
|
|
519
|
-
}
|
|
520
|
-
if (!fileType) {
|
|
521
|
-
throw new Error(`Could not extract file type from ${file.type}`);
|
|
522
|
-
}
|
|
523
|
-
return `${hash}.${fileType}`;
|
|
524
|
-
};
|
|
525
|
-
function hashFile(file) {
|
|
526
|
-
return new Promise((resolve, reject) => {
|
|
527
|
-
const reader = new FileReader();
|
|
528
|
-
reader.onload = () => {
|
|
529
|
-
const fileResult = reader.result;
|
|
530
|
-
if (!fileResult) {
|
|
531
|
-
reject();
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
void crypto.subtle.digest("SHA-1", fileResult).then((hash) => {
|
|
535
|
-
const sha1result = hex(hash);
|
|
536
|
-
resolve(sha1result);
|
|
537
|
-
});
|
|
538
|
-
};
|
|
539
|
-
reader.readAsArrayBuffer(file);
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
function getFileIdentifier(file) {
|
|
543
|
-
if (!file.name || !file.type || !file.size) {
|
|
544
|
-
const message = "File has no name, type, and/or size";
|
|
545
|
-
console.error(`${message}`, file);
|
|
546
|
-
throw new Error(`${message}.`);
|
|
547
|
-
}
|
|
548
|
-
return `${file.name}&${file.type}${file.size}`;
|
|
549
|
-
}
|
|
550
|
-
function getRenamedFile(file, newName) {
|
|
551
|
-
return new File([file], newName, { type: file.type });
|
|
552
|
-
}
|
|
553
|
-
function downloadInMemoryFile(filename, text) {
|
|
554
|
-
const element = document.createElement("a");
|
|
555
|
-
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text));
|
|
556
|
-
element.setAttribute("download", filename);
|
|
557
|
-
element.style.display = "none";
|
|
558
|
-
document.body.appendChild(element);
|
|
559
|
-
element.click();
|
|
560
|
-
document.body.removeChild(element);
|
|
561
|
-
}
|
|
562
|
-
const constructUploadedFilePayloads = async (files) => {
|
|
563
|
-
const filePayloads = {};
|
|
564
|
-
for (const file of files) {
|
|
565
|
-
const sha1 = await hashFile(file);
|
|
566
|
-
filePayloads[sha1] = {
|
|
567
|
-
sha1,
|
|
568
|
-
extension: file.name.split(".").pop() || "",
|
|
569
|
-
file_type: file.type,
|
|
570
|
-
size: file.size
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
return Object.values(filePayloads);
|
|
574
|
-
};
|
|
575
|
-
const fileToBlob = async (dataUrl) => {
|
|
576
|
-
return (await fetch(dataUrl)).blob();
|
|
577
|
-
};
|
|
578
|
-
const blobToBase64 = (blob) => {
|
|
579
|
-
return new Promise((resolve, _) => {
|
|
580
|
-
const reader = new FileReader();
|
|
581
|
-
reader.onloadend = () => {
|
|
582
|
-
var _a2;
|
|
583
|
-
resolve(((_a2 = reader.result) == null ? void 0 : _a2.toString()) || "");
|
|
584
|
-
};
|
|
585
|
-
reader.readAsDataURL(blob);
|
|
586
|
-
});
|
|
587
|
-
};
|
|
588
|
-
const useFileSrc = (props) => {
|
|
589
|
-
const { file, fileSha1, placeholder } = props;
|
|
590
|
-
const [src, setSrc] = React.useState(placeholder);
|
|
591
|
-
const { sdk } = useSDK();
|
|
592
|
-
React.useEffect(() => {
|
|
593
|
-
if (!fileSha1 || !file)
|
|
594
|
-
return;
|
|
595
|
-
sdk.files.fetchFileFromUrl(file, fileSha1).then((file2) => {
|
|
596
|
-
setSrc(URL.createObjectURL(file2));
|
|
597
|
-
}).catch((reason) => {
|
|
598
|
-
console.error(`Failed to fetch file ${file} (${fileSha1}):
|
|
599
|
-
`, reason);
|
|
600
|
-
});
|
|
601
|
-
}, [file, fileSha1, sdk.files]);
|
|
602
|
-
return src;
|
|
603
|
-
};
|
|
604
|
-
function downloadFile(file) {
|
|
605
|
-
const blob = new Blob([file]);
|
|
606
|
-
saveAs.saveAs(blob, file.name);
|
|
853
|
+
}
|
|
854
|
+
return classes.join(" ");
|
|
607
855
|
}
|
|
608
856
|
const logCache = {};
|
|
609
857
|
function logOnlyOnce(logId, objId, level, ...args) {
|
|
@@ -4383,9 +4631,17 @@ var __publicField = (obj, key, value) => {
|
|
|
4383
4631
|
return overmapReducer(mutatedState, action);
|
|
4384
4632
|
};
|
|
4385
4633
|
let __OUTBOX_COORDINATOR = null;
|
|
4386
|
-
function
|
|
4634
|
+
function getOutboxCoordinator() {
|
|
4387
4635
|
if (!__OUTBOX_COORDINATOR) {
|
|
4388
|
-
|
|
4636
|
+
const clientStore2 = getClientStore();
|
|
4637
|
+
if (!clientStore2) {
|
|
4638
|
+
console.error(
|
|
4639
|
+
"Cannot reload outbox because there is no client store yet. If there is an outbox, it will be broken."
|
|
4640
|
+
);
|
|
4641
|
+
return new OutboxCoordinator();
|
|
4642
|
+
}
|
|
4643
|
+
const outbox = clientStore2.getState().offline.outbox;
|
|
4644
|
+
__OUTBOX_COORDINATOR = OutboxCoordinator._fromOutbox(outbox);
|
|
4389
4645
|
}
|
|
4390
4646
|
return __OUTBOX_COORDINATOR;
|
|
4391
4647
|
}
|
|
@@ -4400,12 +4656,12 @@ var __publicField = (obj, key, value) => {
|
|
|
4400
4656
|
}
|
|
4401
4657
|
};
|
|
4402
4658
|
const enqueue = (_array, item, _context) => {
|
|
4403
|
-
const coordinator =
|
|
4659
|
+
const coordinator = getOutboxCoordinator();
|
|
4404
4660
|
coordinator.addRequest(item);
|
|
4405
4661
|
return coordinator.getQueue();
|
|
4406
4662
|
};
|
|
4407
4663
|
const dequeue = (_array, item, _context) => {
|
|
4408
|
-
const coordinator =
|
|
4664
|
+
const coordinator = getOutboxCoordinator();
|
|
4409
4665
|
const meta = item.meta;
|
|
4410
4666
|
const uuid2 = meta.offlineAction.payload.uuid;
|
|
4411
4667
|
coordinator.remove(uuid2);
|
|
@@ -4697,7 +4953,7 @@ var __publicField = (obj, key, value) => {
|
|
|
4697
4953
|
const uuid2 = action.payload.uuid;
|
|
4698
4954
|
function rollbackAndThrow() {
|
|
4699
4955
|
clientStore2.dispatch(markAsDeleted(uuid2));
|
|
4700
|
-
|
|
4956
|
+
getOutboxCoordinator().remove(action.payload.uuid);
|
|
4701
4957
|
const rollbackAction = action.meta.offline.rollback;
|
|
4702
4958
|
if (rollbackAction) {
|
|
4703
4959
|
console.warn("Rolling back request due to SDK error:", action);
|
|
@@ -4728,21 +4984,17 @@ var __publicField = (obj, key, value) => {
|
|
|
4728
4984
|
console.error(`Could not display toast for status ${status} because there is no toast handle.`);
|
|
4729
4985
|
}
|
|
4730
4986
|
}
|
|
4731
|
-
|
|
4987
|
+
getOutboxCoordinator().remove(action.payload.uuid);
|
|
4732
4988
|
reason.options.discard = true;
|
|
4733
4989
|
rollbackAndThrow();
|
|
4734
4990
|
}
|
|
4735
4991
|
}
|
|
4736
4992
|
console.debug("Registering a retry for request:", action.payload.uuid);
|
|
4737
|
-
|
|
4993
|
+
getOutboxCoordinator().registerRetry(action.payload.uuid);
|
|
4738
4994
|
return false;
|
|
4739
4995
|
}
|
|
4740
4996
|
function peek(_array, _item, _context) {
|
|
4741
|
-
|
|
4742
|
-
if (!ret) {
|
|
4743
|
-
throw new Error(`Peek returned ${ret}`);
|
|
4744
|
-
}
|
|
4745
|
-
return ret;
|
|
4997
|
+
return getOutboxCoordinator().peek();
|
|
4746
4998
|
}
|
|
4747
4999
|
function retry(_action, _retries) {
|
|
4748
5000
|
getClientStore().dispatch(_setLatestRetryTime((/* @__PURE__ */ new Date()).getTime()));
|
|
@@ -4750,258 +5002,6 @@ var __publicField = (obj, key, value) => {
|
|
|
4750
5002
|
}
|
|
4751
5003
|
const useAppDispatch = () => reactRedux.useDispatch();
|
|
4752
5004
|
const useAppSelector = reactRedux.useSelector;
|
|
4753
|
-
class OutboxCoordinator {
|
|
4754
|
-
constructor() {
|
|
4755
|
-
__publicField(this, "graph");
|
|
4756
|
-
__publicField(this, "requestAttemptCounter");
|
|
4757
|
-
this.graph = new dependencyGraph.DepGraph();
|
|
4758
|
-
this.requestAttemptCounter = {};
|
|
4759
|
-
}
|
|
4760
|
-
/**
|
|
4761
|
-
* Used when the app is loaded. Reconstructs the dependency graph based on an outbox from the redux-offline store.
|
|
4762
|
-
*/
|
|
4763
|
-
static fromOutbox(outbox, client) {
|
|
4764
|
-
const ret = new OutboxCoordinator();
|
|
4765
|
-
for (let i = 0; i < outbox.length; i++) {
|
|
4766
|
-
const outboxItem = outbox[i];
|
|
4767
|
-
if (!outboxItem) {
|
|
4768
|
-
console.error("Outbox item was undefined");
|
|
4769
|
-
continue;
|
|
4770
|
-
}
|
|
4771
|
-
ret.sneakRequest(outboxItem);
|
|
4772
|
-
for (let j = 0; j < i; j++) {
|
|
4773
|
-
const previousOutboxItem = outbox[j];
|
|
4774
|
-
if (!previousOutboxItem) {
|
|
4775
|
-
console.error("Previous outbox item was undefined");
|
|
4776
|
-
continue;
|
|
4777
|
-
}
|
|
4778
|
-
if (previousOutboxItem.payload.uuid === outboxItem.payload.uuid) {
|
|
4779
|
-
continue;
|
|
4780
|
-
}
|
|
4781
|
-
if (previousOutboxItem.payload.blocks.some((block) => outboxItem.payload.blockers.includes(block))) {
|
|
4782
|
-
OutboxCoordinator._addDependency(
|
|
4783
|
-
outboxItem.payload.uuid,
|
|
4784
|
-
previousOutboxItem.payload.uuid,
|
|
4785
|
-
ret.graph
|
|
4786
|
-
);
|
|
4787
|
-
}
|
|
4788
|
-
}
|
|
4789
|
-
}
|
|
4790
|
-
const firstRequest = ret.peek();
|
|
4791
|
-
if (firstRequest) {
|
|
4792
|
-
void performRequest(ret.pop(), client);
|
|
4793
|
-
}
|
|
4794
|
-
return ret;
|
|
4795
|
-
}
|
|
4796
|
-
_addDependency(from, to) {
|
|
4797
|
-
OutboxCoordinator._addDependency(from, to, this.graph);
|
|
4798
|
-
}
|
|
4799
|
-
static _addDependency(from, to, graph) {
|
|
4800
|
-
if (from === to) {
|
|
4801
|
-
throw new Error(`Tried to add dependency from node to itself: ${from}`);
|
|
4802
|
-
}
|
|
4803
|
-
const fromExists = graph.hasNode(from);
|
|
4804
|
-
if (!fromExists) {
|
|
4805
|
-
throw new Error(`Tried to add dependency from non-existent node: ${from} (to node: ${to})`);
|
|
4806
|
-
}
|
|
4807
|
-
const toExists = graph.hasNode(to);
|
|
4808
|
-
if (!toExists) {
|
|
4809
|
-
throw new Error(`Tried to add dependency to non-existent node: ${to} (from node: ${from})`);
|
|
4810
|
-
}
|
|
4811
|
-
graph.addDependency(from, to);
|
|
4812
|
-
}
|
|
4813
|
-
/**
|
|
4814
|
-
* If there are one or more nodes in the graph, we find all nodes that match a dependency of the request and add a
|
|
4815
|
-
* dependency from the new request node to that node.
|
|
4816
|
-
*/
|
|
4817
|
-
addRequest(request2) {
|
|
4818
|
-
this.graph.addNode(request2.payload.uuid, request2);
|
|
4819
|
-
if (request2.payload.blockers.length === 0 || this.graph.size() === 1) {
|
|
4820
|
-
return;
|
|
4821
|
-
}
|
|
4822
|
-
for (const node of this.graph.overallOrder()) {
|
|
4823
|
-
if (node === request2.payload.uuid)
|
|
4824
|
-
continue;
|
|
4825
|
-
const details = this.graph.getNodeData(node);
|
|
4826
|
-
if (request2.payload.blockers.some((blocker) => details.payload.blocks.includes(blocker))) {
|
|
4827
|
-
this._addDependency(request2.payload.uuid, node);
|
|
4828
|
-
}
|
|
4829
|
-
}
|
|
4830
|
-
}
|
|
4831
|
-
/**
|
|
4832
|
-
* Inserts a request at the beginning of the queue. This could be used for requests that were popped, then failed,
|
|
4833
|
-
* and need to be re-enqueued at the front of the queue. Any requests that were previously blocked by this request
|
|
4834
|
-
* will be blocked by this request again. No blockers will be added to this request because it is assumed that the
|
|
4835
|
-
* request was already unblocked when it was popped.
|
|
4836
|
-
* @param request The request to insert at the beginning of the queue.
|
|
4837
|
-
*/
|
|
4838
|
-
insertRequest(request2) {
|
|
4839
|
-
this.graph.addNode(request2.payload.uuid, request2);
|
|
4840
|
-
for (const node of this.graph.overallOrder()) {
|
|
4841
|
-
if (node === request2.payload.uuid)
|
|
4842
|
-
continue;
|
|
4843
|
-
const details = this.graph.getNodeData(node);
|
|
4844
|
-
if (details.payload.blockers.some((blocker) => request2.payload.blocks.includes(blocker))) {
|
|
4845
|
-
this._addDependency(node, request2.payload.uuid);
|
|
4846
|
-
}
|
|
4847
|
-
}
|
|
4848
|
-
}
|
|
4849
|
-
/**
|
|
4850
|
-
* Sneaks a request into the dependency graph without creating any blockers. Useful for reconstructing the graph
|
|
4851
|
-
* (from the offline store's outbox state) when the app is loaded.
|
|
4852
|
-
*/
|
|
4853
|
-
sneakRequest(request2) {
|
|
4854
|
-
this.graph.addNode(request2.payload.uuid, request2);
|
|
4855
|
-
}
|
|
4856
|
-
/**
|
|
4857
|
-
* Returns the next node in line to be sent. This is the unblocked node with the fewest number of attempts. If there
|
|
4858
|
-
* are multiple nodes with the same number of attempts, the first one (by insertion order) will be returned.
|
|
4859
|
-
*/
|
|
4860
|
-
_getNextNode() {
|
|
4861
|
-
const leafNodes = this.graph.overallOrder(true);
|
|
4862
|
-
let minAttempts = Infinity;
|
|
4863
|
-
let minAttemptsNode;
|
|
4864
|
-
for (const node of leafNodes) {
|
|
4865
|
-
const attempts = this.requestAttemptCounter[node] || 0;
|
|
4866
|
-
if (attempts < minAttempts) {
|
|
4867
|
-
minAttempts = attempts;
|
|
4868
|
-
minAttemptsNode = node;
|
|
4869
|
-
}
|
|
4870
|
-
}
|
|
4871
|
-
return minAttemptsNode;
|
|
4872
|
-
}
|
|
4873
|
-
/**
|
|
4874
|
-
* Returns the next request in line to be sent without removing it.
|
|
4875
|
-
*/
|
|
4876
|
-
peek() {
|
|
4877
|
-
const nextNode = this._getNextNode();
|
|
4878
|
-
if (!nextNode)
|
|
4879
|
-
return void 0;
|
|
4880
|
-
return this.graph.getNodeData(nextNode);
|
|
4881
|
-
}
|
|
4882
|
-
/**
|
|
4883
|
-
* Removes a request from the graph. This should be called when a request is successfully sent.
|
|
4884
|
-
* @param uuid The UUID of the request to remove.
|
|
4885
|
-
*/
|
|
4886
|
-
remove(uuid2) {
|
|
4887
|
-
this.graph.removeNode(uuid2);
|
|
4888
|
-
delete this.requestAttemptCounter[uuid2];
|
|
4889
|
-
}
|
|
4890
|
-
/**
|
|
4891
|
-
* Returns the next request in line to be sent and removes it from the graph.
|
|
4892
|
-
*/
|
|
4893
|
-
pop() {
|
|
4894
|
-
const nextRequestDetails = this.peek();
|
|
4895
|
-
if (nextRequestDetails) {
|
|
4896
|
-
this.graph.removeNode(nextRequestDetails.payload.uuid);
|
|
4897
|
-
}
|
|
4898
|
-
return nextRequestDetails;
|
|
4899
|
-
}
|
|
4900
|
-
/**
|
|
4901
|
-
* Gets the current queue for the outbox. Should be called to get a new value for the outbox slice every time a
|
|
4902
|
-
* request is enqueued. It will be used to render the outbox items in a single lane.
|
|
4903
|
-
*/
|
|
4904
|
-
getQueue() {
|
|
4905
|
-
const ret = this.graph.overallOrder().map((nodeName) => this.graph.getNodeData(nodeName));
|
|
4906
|
-
const nextNode = this._getNextNode();
|
|
4907
|
-
if (nextNode) {
|
|
4908
|
-
const nextRequestDetails = this.graph.getNodeData(nextNode);
|
|
4909
|
-
const nextRequestIndex = ret.findIndex(
|
|
4910
|
-
(request2) => request2.payload.uuid === nextRequestDetails.payload.uuid
|
|
4911
|
-
);
|
|
4912
|
-
if (nextRequestIndex !== -1) {
|
|
4913
|
-
ret.splice(nextRequestIndex, 1);
|
|
4914
|
-
ret.unshift(nextRequestDetails);
|
|
4915
|
-
}
|
|
4916
|
-
}
|
|
4917
|
-
return ret;
|
|
4918
|
-
}
|
|
4919
|
-
/**
|
|
4920
|
-
* Gets a list of requests that can currently be sent (requests with no unresolved dependencies). Used to process
|
|
4921
|
-
* the next ready request in case the request at the front of the queue is failing.
|
|
4922
|
-
*/
|
|
4923
|
-
getReady() {
|
|
4924
|
-
let ret = this.graph.overallOrder(true).map((nodeName) => this.graph.getNodeData(nodeName));
|
|
4925
|
-
ret = ret.sort((a, b) => {
|
|
4926
|
-
return a.meta.offline.effect.timestamp.localeCompare(b.meta.offline.effect.timestamp);
|
|
4927
|
-
});
|
|
4928
|
-
ret = ret.sort((a, b) => {
|
|
4929
|
-
const aAttempts = this.requestAttemptCounter[a.payload.uuid] || 0;
|
|
4930
|
-
const bAttempts = this.requestAttemptCounter[b.payload.uuid] || 0;
|
|
4931
|
-
return aAttempts - bAttempts;
|
|
4932
|
-
});
|
|
4933
|
-
return ret;
|
|
4934
|
-
}
|
|
4935
|
-
registerRetry(uuid2) {
|
|
4936
|
-
this.requestAttemptCounter[uuid2] = (this.requestAttemptCounter[uuid2] || 0) + 1;
|
|
4937
|
-
}
|
|
4938
|
-
}
|
|
4939
|
-
const UNKNOWN_ERROR_MESSAGE = "An unknown error occurred";
|
|
4940
|
-
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
4941
|
-
const _SPECIAL_KEYS = ["non_field_errors", "detail"];
|
|
4942
|
-
function extractErrorMessage(errorRes, err) {
|
|
4943
|
-
let ret;
|
|
4944
|
-
if (errorRes == null ? void 0 : errorRes.body) {
|
|
4945
|
-
if (typeof errorRes.body === "object") {
|
|
4946
|
-
const responseBody = errorRes.body;
|
|
4947
|
-
if (typeof responseBody.error === "string") {
|
|
4948
|
-
ret = responseBody.error;
|
|
4949
|
-
} else if (typeof responseBody.message === "string") {
|
|
4950
|
-
ret = responseBody.message;
|
|
4951
|
-
} else if (responseBody.body) {
|
|
4952
|
-
try {
|
|
4953
|
-
ret = Object.entries(responseBody.body).map(([key, value]) => {
|
|
4954
|
-
if (typeof value === "string") {
|
|
4955
|
-
if (_SPECIAL_KEYS.includes(key))
|
|
4956
|
-
return value;
|
|
4957
|
-
return `${key}: ${value}`;
|
|
4958
|
-
}
|
|
4959
|
-
if (Array.isArray(value)) {
|
|
4960
|
-
if (_SPECIAL_KEYS.includes(key))
|
|
4961
|
-
return value.join("\n");
|
|
4962
|
-
return value.map((v) => `${key}: ${v}`).join("\n");
|
|
4963
|
-
}
|
|
4964
|
-
return `${key}: ${JSON.stringify(value)}`;
|
|
4965
|
-
}).join("\n");
|
|
4966
|
-
} catch (e) {
|
|
4967
|
-
console.error("Failed to extract error message from response body", e);
|
|
4968
|
-
}
|
|
4969
|
-
}
|
|
4970
|
-
} else if (typeof errorRes.body === "string") {
|
|
4971
|
-
ret = errorRes.body;
|
|
4972
|
-
}
|
|
4973
|
-
} else if (errorRes == null ? void 0 : errorRes.text) {
|
|
4974
|
-
ret = errorRes.text;
|
|
4975
|
-
} else if (err instanceof Error) {
|
|
4976
|
-
ret = err.message;
|
|
4977
|
-
}
|
|
4978
|
-
if (!ret || ret.length > MAX_ERROR_MESSAGE_LENGTH) {
|
|
4979
|
-
return UNKNOWN_ERROR_MESSAGE;
|
|
4980
|
-
}
|
|
4981
|
-
return ret;
|
|
4982
|
-
}
|
|
4983
|
-
class APIError extends Error {
|
|
4984
|
-
constructor(options) {
|
|
4985
|
-
super(UNKNOWN_ERROR_MESSAGE);
|
|
4986
|
-
// NOTE: Needs to conform to NetworkError in @redux-offline/redux-offline, which has `status` and `response`.
|
|
4987
|
-
__publicField(this, "status");
|
|
4988
|
-
__publicField(this, "response");
|
|
4989
|
-
__publicField(this, "message");
|
|
4990
|
-
__publicField(this, "options");
|
|
4991
|
-
const { response, innerError } = options;
|
|
4992
|
-
this.message = options.message ?? extractErrorMessage(response, innerError) ?? UNKNOWN_ERROR_MESSAGE;
|
|
4993
|
-
this.status = (response == null ? void 0 : response.status) ?? 0;
|
|
4994
|
-
this.response = response;
|
|
4995
|
-
options.discard = options.discard ?? false;
|
|
4996
|
-
this.options = options;
|
|
4997
|
-
}
|
|
4998
|
-
}
|
|
4999
|
-
class BaseApiService {
|
|
5000
|
-
constructor(sdk) {
|
|
5001
|
-
__publicField(this, "client");
|
|
5002
|
-
this.client = sdk;
|
|
5003
|
-
}
|
|
5004
|
-
}
|
|
5005
5005
|
const EXPIRING_SOON_THRESHOLD = 1800;
|
|
5006
5006
|
function parseTokens(response) {
|
|
5007
5007
|
if (!response.access)
|
|
@@ -16920,6 +16920,7 @@ var __publicField = (obj, key, value) => {
|
|
|
16920
16920
|
exports2.getLocalDateString = getLocalDateString;
|
|
16921
16921
|
exports2.getLocalRelativeDateString = getLocalRelativeDateString;
|
|
16922
16922
|
exports2.getMarkerCoordinates = getMarkerCoordinates;
|
|
16923
|
+
exports2.getOutboxCoordinator = getOutboxCoordinator;
|
|
16923
16924
|
exports2.getRenamedFile = getRenamedFile;
|
|
16924
16925
|
exports2.getStageColor = getStageColor;
|
|
16925
16926
|
exports2.hashFile = hashFile;
|