sliftutils 0.55.0 → 0.57.0
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/index.d.ts
CHANGED
|
@@ -231,7 +231,32 @@ declare module "sliftutils/render-utils/Input" {
|
|
|
231
231
|
|
|
232
232
|
declare module "sliftutils/render-utils/InputLabel" {
|
|
233
233
|
import preact from "preact";
|
|
234
|
-
|
|
234
|
+
type InputProps = (preact.JSX.HTMLAttributes<HTMLInputElement> & {
|
|
235
|
+
/** ONLY throttles onChangeValue */
|
|
236
|
+
throttle?: number;
|
|
237
|
+
flavor?: "large" | "small" | "none";
|
|
238
|
+
focusOnMount?: boolean;
|
|
239
|
+
textarea?: boolean;
|
|
240
|
+
/** Update on key stroke, not on blur (just does onInput = onChange, as onInput already does this) */
|
|
241
|
+
hot?: boolean;
|
|
242
|
+
/** Updates arrow keys with modifier behavior to use larger numbers, instead of decimals. */
|
|
243
|
+
integer?: boolean;
|
|
244
|
+
/** Only works with number/integer */
|
|
245
|
+
reverseArrowKeyDirection?: boolean;
|
|
246
|
+
inputRef?: (x: HTMLInputElement | null) => void;
|
|
247
|
+
/** Don't blur on enter key */
|
|
248
|
+
noEnterKeyBlur?: boolean;
|
|
249
|
+
noFocusSelect?: boolean;
|
|
250
|
+
inputKey?: string;
|
|
251
|
+
fillWidth?: boolean;
|
|
252
|
+
autocompleteValues?: string[];
|
|
253
|
+
/** Forces the input to update when focused. Usually we hold updates, to prevent the user's
|
|
254
|
+
* typing to be interrupted by background updates.
|
|
255
|
+
* NOTE: "hot" is usually required when using this.
|
|
256
|
+
*/
|
|
257
|
+
forceInputValueUpdatesWhenFocused?: boolean;
|
|
258
|
+
onChangeValue?: (value: string) => void;
|
|
259
|
+
});
|
|
235
260
|
export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
|
|
236
261
|
label?: preact.ComponentChild;
|
|
237
262
|
number?: boolean;
|
|
@@ -266,6 +291,7 @@ declare module "sliftutils/render-utils/InputLabel" {
|
|
|
266
291
|
}> {
|
|
267
292
|
render(): preact.JSX.Element;
|
|
268
293
|
}
|
|
294
|
+
export {};
|
|
269
295
|
|
|
270
296
|
}
|
|
271
297
|
|
|
@@ -924,7 +950,8 @@ declare module "sliftutils/storage/TransactionStorage" {
|
|
|
924
950
|
private updatePendingAppends;
|
|
925
951
|
getKeys(): Promise<string[]>;
|
|
926
952
|
private loadAllTransactions;
|
|
927
|
-
private
|
|
953
|
+
private parseTransactionFile;
|
|
954
|
+
private applyTransactionEntries;
|
|
928
955
|
private readTransactionEntry;
|
|
929
956
|
private serializeTransactionEntry;
|
|
930
957
|
private getHeader;
|
package/package.json
CHANGED
package/render-utils/Input.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import { throttleFunction } from "socket-function/src/misc";
|
|
|
9
9
|
// This is useful for inputs which you want to run an action on, such as "add new item",
|
|
10
10
|
// as it allows you to remove a local state value to cache the value, by just
|
|
11
11
|
// doing the add on "onChangeValue".
|
|
12
|
+
// IMPORTANT! InputProps is in both InputLabel.tsx and Input.tsx, so the types export correctly
|
|
12
13
|
export type InputProps = (
|
|
13
14
|
preact.JSX.HTMLAttributes<HTMLInputElement>
|
|
14
15
|
& {
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import preact from "preact";
|
|
2
|
-
|
|
2
|
+
type InputProps = (preact.JSX.HTMLAttributes<HTMLInputElement> & {
|
|
3
|
+
/** ONLY throttles onChangeValue */
|
|
4
|
+
throttle?: number;
|
|
5
|
+
flavor?: "large" | "small" | "none";
|
|
6
|
+
focusOnMount?: boolean;
|
|
7
|
+
textarea?: boolean;
|
|
8
|
+
/** Update on key stroke, not on blur (just does onInput = onChange, as onInput already does this) */
|
|
9
|
+
hot?: boolean;
|
|
10
|
+
/** Updates arrow keys with modifier behavior to use larger numbers, instead of decimals. */
|
|
11
|
+
integer?: boolean;
|
|
12
|
+
/** Only works with number/integer */
|
|
13
|
+
reverseArrowKeyDirection?: boolean;
|
|
14
|
+
inputRef?: (x: HTMLInputElement | null) => void;
|
|
15
|
+
/** Don't blur on enter key */
|
|
16
|
+
noEnterKeyBlur?: boolean;
|
|
17
|
+
noFocusSelect?: boolean;
|
|
18
|
+
inputKey?: string;
|
|
19
|
+
fillWidth?: boolean;
|
|
20
|
+
autocompleteValues?: string[];
|
|
21
|
+
/** Forces the input to update when focused. Usually we hold updates, to prevent the user's
|
|
22
|
+
* typing to be interrupted by background updates.
|
|
23
|
+
* NOTE: "hot" is usually required when using this.
|
|
24
|
+
*/
|
|
25
|
+
forceInputValueUpdatesWhenFocused?: boolean;
|
|
26
|
+
onChangeValue?: (value: string) => void;
|
|
27
|
+
});
|
|
3
28
|
export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
|
|
4
29
|
label?: preact.ComponentChild;
|
|
5
30
|
number?: boolean;
|
|
@@ -34,3 +59,4 @@ export declare class InputLabelURL extends preact.Component<InputLabelProps & {
|
|
|
34
59
|
}> {
|
|
35
60
|
render(): preact.JSX.Element;
|
|
36
61
|
}
|
|
62
|
+
export {};
|
|
@@ -1,10 +1,50 @@
|
|
|
1
1
|
import preact from "preact";
|
|
2
|
-
import { Input
|
|
2
|
+
import { Input } from "./Input";
|
|
3
3
|
import { css } from "typesafecss";
|
|
4
4
|
import { lazy } from "socket-function/src/caching";
|
|
5
5
|
import { observer } from "./observer";
|
|
6
6
|
import { observable } from "mobx";
|
|
7
7
|
|
|
8
|
+
// IMPORTANT! InputProps is in both InputLabel.tsx and Input.tsx, so the types export correctly
|
|
9
|
+
type InputProps = (
|
|
10
|
+
preact.JSX.HTMLAttributes<HTMLInputElement>
|
|
11
|
+
& {
|
|
12
|
+
/** ONLY throttles onChangeValue */
|
|
13
|
+
throttle?: number;
|
|
14
|
+
|
|
15
|
+
flavor?: "large" | "small" | "none";
|
|
16
|
+
focusOnMount?: boolean;
|
|
17
|
+
textarea?: boolean;
|
|
18
|
+
/** Update on key stroke, not on blur (just does onInput = onChange, as onInput already does this) */
|
|
19
|
+
hot?: boolean;
|
|
20
|
+
/** Updates arrow keys with modifier behavior to use larger numbers, instead of decimals. */
|
|
21
|
+
integer?: boolean;
|
|
22
|
+
|
|
23
|
+
/** Only works with number/integer */
|
|
24
|
+
reverseArrowKeyDirection?: boolean;
|
|
25
|
+
|
|
26
|
+
inputRef?: (x: HTMLInputElement | null) => void;
|
|
27
|
+
/** Don't blur on enter key */
|
|
28
|
+
noEnterKeyBlur?: boolean;
|
|
29
|
+
noFocusSelect?: boolean;
|
|
30
|
+
inputKey?: string;
|
|
31
|
+
fillWidth?: boolean;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
autocompleteValues?: string[];
|
|
35
|
+
|
|
36
|
+
/** Forces the input to update when focused. Usually we hold updates, to prevent the user's
|
|
37
|
+
* typing to be interrupted by background updates.
|
|
38
|
+
* NOTE: "hot" is usually required when using this.
|
|
39
|
+
*/
|
|
40
|
+
forceInputValueUpdatesWhenFocused?: boolean;
|
|
41
|
+
|
|
42
|
+
// NOTE: We trigger onChange (and onChangeValue) whenever
|
|
43
|
+
// e.ctrlKey && (e.code.startsWith("Key") || e.code === "Enter") || e.code === "Enter" && e.shiftKey
|
|
44
|
+
// This is because ctrl usually means a hotkey, and hotkeys usually want committed values.
|
|
45
|
+
onChangeValue?: (value: string) => void;
|
|
46
|
+
}
|
|
47
|
+
)
|
|
8
48
|
|
|
9
49
|
export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
|
|
10
50
|
label?: preact.ComponentChild;
|
|
@@ -34,7 +34,8 @@ export declare class TransactionStorage implements IStorage<Buffer> {
|
|
|
34
34
|
private updatePendingAppends;
|
|
35
35
|
getKeys(): Promise<string[]>;
|
|
36
36
|
private loadAllTransactions;
|
|
37
|
-
private
|
|
37
|
+
private parseTransactionFile;
|
|
38
|
+
private applyTransactionEntries;
|
|
38
39
|
private readTransactionEntry;
|
|
39
40
|
private serializeTransactionEntry;
|
|
40
41
|
private getHeader;
|
|
@@ -73,13 +73,6 @@ let seqNum = 0;
|
|
|
73
73
|
function getNextChunkPath(): string {
|
|
74
74
|
return `${Date.now()}_${seqNum++}_${ourId}.chunk`;
|
|
75
75
|
}
|
|
76
|
-
function sortChunks(chunks: string[]): string[] {
|
|
77
|
-
function getChunkParts(chunk: string): unknown[] {
|
|
78
|
-
const parts = chunk.split("_");
|
|
79
|
-
return parts.map(part => +part);
|
|
80
|
-
}
|
|
81
|
-
return chunks.sort((a, b) => compareArray(getChunkParts(a), getChunkParts(b)));
|
|
82
|
-
}
|
|
83
76
|
|
|
84
77
|
export class TransactionStorage implements IStorage<Buffer> {
|
|
85
78
|
public cache: Map<string, TransactionEntry> = new Map();
|
|
@@ -108,7 +101,7 @@ export class TransactionStorage implements IStorage<Buffer> {
|
|
|
108
101
|
});
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
private init: Promise<
|
|
104
|
+
private init: Promise<unknown> | undefined = this.loadAllTransactions();
|
|
112
105
|
|
|
113
106
|
private getCurrentChunk(): string {
|
|
114
107
|
if (this.currentChunk && this.currentChunk.timeCreated < Date.now() - FILE_MAX_LIFETIME) {
|
|
@@ -247,33 +240,37 @@ export class TransactionStorage implements IStorage<Buffer> {
|
|
|
247
240
|
}
|
|
248
241
|
|
|
249
242
|
|
|
250
|
-
|
|
251
|
-
|
|
243
|
+
// NOTE: This is either called in init (which blocks all other calls), or inside of the global file lock, so it is safe to load.
|
|
244
|
+
private async loadAllTransactions(): Promise<string[]> {
|
|
245
|
+
if (isInBuild()) return [];
|
|
246
|
+
|
|
252
247
|
let time = Date.now();
|
|
253
248
|
const keys = await this.rawStorage.getKeys();
|
|
254
249
|
const transactionFiles = keys.filter(key => key.endsWith(CHUNK_EXT));
|
|
255
250
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
for (const file of transactionFiles) {
|
|
260
|
-
let curSize = await this.loadTransactionFile(file);
|
|
261
|
-
size += curSize;
|
|
251
|
+
let entryList: TransactionEntry[][] = [];
|
|
252
|
+
for (let file of transactionFiles) {
|
|
253
|
+
entryList.push(await this.parseTransactionFile(file));
|
|
262
254
|
}
|
|
255
|
+
let entries = entryList.flat();
|
|
256
|
+
this.applyTransactionEntries(entries);
|
|
257
|
+
|
|
263
258
|
time = Date.now() - time;
|
|
264
259
|
if (time > 50) {
|
|
265
|
-
console.log(`Loaded ${this.debugName} in ${formatTime(time)}, ${formatNumber(this.cache.size)} keys, from ${formatNumber(transactionFiles.length)} files, ${formatNumber(
|
|
260
|
+
console.log(`Loaded ${this.debugName} in ${formatTime(time)}, ${formatNumber(this.cache.size)} keys, from ${formatNumber(transactionFiles.length)} files, entries ${formatNumber(entries.length)}B`, transactionFiles);
|
|
266
261
|
}
|
|
267
262
|
|
|
268
263
|
this.init = undefined;
|
|
264
|
+
return transactionFiles;
|
|
269
265
|
}
|
|
270
266
|
|
|
271
|
-
|
|
267
|
+
// ONLY call this inside of loadAllTransactions
|
|
268
|
+
private async parseTransactionFile(filename: string): Promise<TransactionEntry[]> {
|
|
272
269
|
const fullFile = await this.rawStorage.get(filename);
|
|
273
|
-
if (!fullFile) return
|
|
270
|
+
if (!fullFile) return [];
|
|
274
271
|
if (fullFile.length < 4) {
|
|
275
272
|
//console.error(`Transaction in ${this.debugName} file ${filename} is too small, skipping`);
|
|
276
|
-
return
|
|
273
|
+
return [];
|
|
277
274
|
}
|
|
278
275
|
let headerSize = fullFile.readUInt32LE(0);
|
|
279
276
|
let headerBuffer = fullFile.slice(4, 4 + headerSize);
|
|
@@ -282,22 +279,13 @@ export class TransactionStorage implements IStorage<Buffer> {
|
|
|
282
279
|
header = JSON.parse(headerBuffer.toString());
|
|
283
280
|
} catch (e) {
|
|
284
281
|
console.error(`Failed to parse header of transaction file in ${this.debugName}, ${filename}`);
|
|
285
|
-
return
|
|
282
|
+
return [];
|
|
286
283
|
}
|
|
287
284
|
let content = fullFile.slice(4 + headerSize);
|
|
288
285
|
if (header.zipped) {
|
|
289
286
|
content = await Zip.gunzip(content);
|
|
290
287
|
}
|
|
291
288
|
|
|
292
|
-
let pendingWriteTimes = new Map<string, number>();
|
|
293
|
-
for (const entry of this.pendingAppends) {
|
|
294
|
-
let prevTime = pendingWriteTimes.get(entry.key);
|
|
295
|
-
if (prevTime && prevTime > entry.time) {
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
pendingWriteTimes.set(entry.key, entry.time);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
289
|
let offset = 0;
|
|
302
290
|
let entries: TransactionEntry[] = [];
|
|
303
291
|
while (offset < content.length) {
|
|
@@ -330,7 +318,21 @@ export class TransactionStorage implements IStorage<Buffer> {
|
|
|
330
318
|
let { entry } = entryObj;
|
|
331
319
|
offset = entryObj.offset;
|
|
332
320
|
entries.push(entry);
|
|
321
|
+
}
|
|
322
|
+
return entries;
|
|
323
|
+
}
|
|
324
|
+
private applyTransactionEntries(entries: TransactionEntry[]): void {
|
|
325
|
+
let pendingWriteTimes = new Map<string, number>();
|
|
326
|
+
for (const entry of this.pendingAppends) {
|
|
327
|
+
let prevTime = pendingWriteTimes.get(entry.key);
|
|
328
|
+
if (prevTime && prevTime > entry.time) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
pendingWriteTimes.set(entry.key, entry.time);
|
|
332
|
+
}
|
|
333
333
|
|
|
334
|
+
sort(entries, x => x.time);
|
|
335
|
+
for (let entry of entries) {
|
|
334
336
|
let time = entry.time;
|
|
335
337
|
let prevTime = pendingWriteTimes.get(entry.key);
|
|
336
338
|
if (prevTime && prevTime > time) {
|
|
@@ -347,7 +349,6 @@ export class TransactionStorage implements IStorage<Buffer> {
|
|
|
347
349
|
this.cache.set(entry.key, entry);
|
|
348
350
|
}
|
|
349
351
|
}
|
|
350
|
-
return fullFile.length;
|
|
351
352
|
}
|
|
352
353
|
|
|
353
354
|
private readTransactionEntry(buffer: Buffer, offset: number): {
|
|
@@ -469,11 +470,7 @@ export class TransactionStorage implements IStorage<Buffer> {
|
|
|
469
470
|
|
|
470
471
|
// Load off disk, in case there are other writes. We still race with them, but at least
|
|
471
472
|
// this reduces the race condition considerably
|
|
472
|
-
|
|
473
|
-
sortChunks(existingDiskEntries);
|
|
474
|
-
for (let entry of existingDiskEntries) {
|
|
475
|
-
await this.loadTransactionFile(entry);
|
|
476
|
-
}
|
|
473
|
+
existingDiskEntries = await this.loadAllTransactions();
|
|
477
474
|
|
|
478
475
|
this.entryCount = this.cache.size;
|
|
479
476
|
|