@effindomv2/fui-as 0.1.27 → 0.1.30
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/browser/src/common-harness/managed-harness-file-host.ts +123 -158
- package/browser/src/common-harness/managed-harness-file-types.ts +2 -37
- package/browser/src/common-harness/managed-harness.ts +4 -0
- package/browser/src/worker-bootstrap.ts +406 -0
- package/browser/src/worker-manager.ts +4 -0
- package/browser/src/worker-types.ts +18 -0
- package/package.json +3 -2
- package/src/Fui.ts +1 -0
- package/src/FuiWorker.ts +5 -1
- package/src/controls/Button.ts +39 -6
- package/src/controls/ButtonColors.ts +73 -0
- package/src/controls/Checkbox.ts +1 -0
- package/src/controls/LabeledControlColors.ts +36 -0
- package/src/controls/RadioButton.ts +1 -0
- package/src/controls/Switch.ts +1 -0
- package/src/controls/index.ts +1 -0
- package/src/controls/internal/ButtonPresenter.ts +3 -2
- package/src/controls/internal/CheckboxIndicatorPresenter.ts +10 -8
- package/src/controls/internal/PressableLabeledControl.ts +2 -1
- package/src/controls/internal/RadioIndicatorPresenter.ts +9 -5
- package/src/controls/internal/SwitchIndicatorPresenter.ts +16 -7
- package/src/core/File.ts +5 -2
- package/src/core/event_exports.ts +15 -1
- package/src/worker/ffi.ts +6 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
WorkerBootstrapFileProcessStartMessage,
|
|
2
3
|
WorkerBootstrapInboundMessage,
|
|
3
4
|
WorkerBootstrapOutboundMessage,
|
|
4
5
|
WorkerHostServicesBundleConfig,
|
|
@@ -22,10 +23,13 @@ const encoder = new TextEncoder();
|
|
|
22
23
|
let activeWorkerId: number | null = null;
|
|
23
24
|
let activeCancellationRequested = false;
|
|
24
25
|
let pendingCancellationRequested = false;
|
|
26
|
+
let activeFile: File | null = null;
|
|
25
27
|
|
|
26
28
|
const allowedWorkerHostImports = new Set([
|
|
27
29
|
'fui_fetch_start',
|
|
28
30
|
'fui_fetch_cancel',
|
|
31
|
+
'fui_file_read_chunk',
|
|
32
|
+
'fui_file_worker_write_chunk',
|
|
29
33
|
'fui_worker_input_length',
|
|
30
34
|
'fui_worker_copy_input',
|
|
31
35
|
'fui_worker_report_progress',
|
|
@@ -369,6 +373,45 @@ async function startWorker(message: WorkerBootstrapStartMessage): Promise<void>
|
|
|
369
373
|
yieldRequested = true;
|
|
370
374
|
requestedYieldDelayMs = Number.isFinite(delayMs) && delayMs > 0 ? Math.floor(delayMs) : 0;
|
|
371
375
|
},
|
|
376
|
+
fui_file_read_chunk(offsetLow: number, offsetHigh: number, length: number): number {
|
|
377
|
+
if (activeFile === null) {
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
380
|
+
const offset = Number(BigInt(offsetLow >>> 0) | (BigInt(offsetHigh >>> 0) << 32n));
|
|
381
|
+
const safeLength = Math.max(0, length | 0);
|
|
382
|
+
if (offset >= activeFile.size || safeLength <= 0) {
|
|
383
|
+
return 0;
|
|
384
|
+
}
|
|
385
|
+
const blob = activeFile.slice(offset, Math.min(offset + safeLength, activeFile.size));
|
|
386
|
+
const reader = new FileReaderSync();
|
|
387
|
+
const buffer = reader.readAsArrayBuffer(blob);
|
|
388
|
+
const bytes = new Uint8Array(buffer);
|
|
389
|
+
const written = bytes.length;
|
|
390
|
+
if (written <= 0) {
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
if (memory === null || callbackBufferSize <= 0) {
|
|
394
|
+
return 0;
|
|
395
|
+
}
|
|
396
|
+
if (written > callbackBufferSize) {
|
|
397
|
+
throw new Error('File chunk exceeds the worker callback buffer.');
|
|
398
|
+
}
|
|
399
|
+
new Uint8Array(memory.buffer, callbackBufferPtr, written).set(bytes);
|
|
400
|
+
return written;
|
|
401
|
+
},
|
|
402
|
+
fui_file_worker_write_chunk(ptr: number, len: number): void {
|
|
403
|
+
if (memory === null || len <= 0) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const bytes = new Uint8Array(len);
|
|
407
|
+
bytes.set(new Uint8Array(memory.buffer, ptr, len));
|
|
408
|
+
const buffer = bytes.buffer.slice(0, bytes.byteLength) as ArrayBuffer;
|
|
409
|
+
workerScope.postMessage({
|
|
410
|
+
type: 'file-process-chunk',
|
|
411
|
+
workerId: message.workerId,
|
|
412
|
+
bytes: buffer,
|
|
413
|
+
}, [buffer]);
|
|
414
|
+
},
|
|
372
415
|
},
|
|
373
416
|
fui_fetch_host: {
|
|
374
417
|
fui_fetch_start(
|
|
@@ -494,12 +537,375 @@ async function startWorker(message: WorkerBootstrapStartMessage): Promise<void>
|
|
|
494
537
|
}
|
|
495
538
|
}
|
|
496
539
|
|
|
540
|
+
async function startFileProcessWorker(message: WorkerBootstrapFileProcessStartMessage): Promise<void> {
|
|
541
|
+
let memory: WebAssembly.Memory | null = null;
|
|
542
|
+
let terminalSent = false;
|
|
543
|
+
let callbackBufferPtr = 0;
|
|
544
|
+
let callbackBufferSize = 0;
|
|
545
|
+
let wasmExports: (Record<string, unknown> & {
|
|
546
|
+
memory?: WebAssembly.Memory;
|
|
547
|
+
__fui_worker_text_buffer?: () => number;
|
|
548
|
+
__fui_worker_text_buffer_size?: () => number;
|
|
549
|
+
}) | null = null;
|
|
550
|
+
const activeFetchRequests = new Map<number, AbortController>();
|
|
551
|
+
let entry: (() => void) | null = null;
|
|
552
|
+
activeWorkerId = message.workerId;
|
|
553
|
+
activeCancellationRequested = pendingCancellationRequested;
|
|
554
|
+
activeFile = message.file;
|
|
555
|
+
pendingCancellationRequested = false;
|
|
556
|
+
|
|
557
|
+
function readCancelFlag(): boolean {
|
|
558
|
+
return activeWorkerId === message.workerId && activeCancellationRequested;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function cancelAllFetchRequests(): void {
|
|
562
|
+
for (const controller of activeFetchRequests.values()) {
|
|
563
|
+
controller.abort();
|
|
564
|
+
}
|
|
565
|
+
activeFetchRequests.clear();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function writeCallbackBytes(bytes: Uint8Array, context: string): { ptr: number; len: number } {
|
|
569
|
+
if (callbackBufferSize <= 0) {
|
|
570
|
+
throw new Error(`${context} requires the worker callback buffer.`);
|
|
571
|
+
}
|
|
572
|
+
if (bytes.length > callbackBufferSize) {
|
|
573
|
+
throw new Error(`${context} exceeds the worker callback buffer.`);
|
|
574
|
+
}
|
|
575
|
+
if (memory === null) {
|
|
576
|
+
throw new Error(`${context} requires worker memory.`);
|
|
577
|
+
}
|
|
578
|
+
if (bytes.length > 0) {
|
|
579
|
+
new Uint8Array(memory.buffer, callbackBufferPtr, bytes.length).set(bytes);
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
ptr: bytes.length > 0 ? callbackBufferPtr : 0,
|
|
583
|
+
len: bytes.length,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function emitFetchComplete(
|
|
588
|
+
requestId: number,
|
|
589
|
+
ok: boolean,
|
|
590
|
+
status: number,
|
|
591
|
+
statusText: string,
|
|
592
|
+
url: string,
|
|
593
|
+
exports: Record<string, unknown>,
|
|
594
|
+
): void {
|
|
595
|
+
const callback = exports.__fui_on_fetch_complete;
|
|
596
|
+
if (typeof callback !== 'function') {
|
|
597
|
+
throw new Error('Worker module is missing __fui_on_fetch_complete.');
|
|
598
|
+
}
|
|
599
|
+
const payload = writeCallbackBytes(encodeTextPartsPayload([statusText, url]), 'Worker fetch completion payload');
|
|
600
|
+
(callback as (requestId: number, ok: boolean, status: number, payloadPtr: number, payloadLen: number) => void)(
|
|
601
|
+
requestId,
|
|
602
|
+
ok,
|
|
603
|
+
status,
|
|
604
|
+
payload.ptr,
|
|
605
|
+
payload.len,
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function emitFetchError(
|
|
610
|
+
requestId: number,
|
|
611
|
+
message: string,
|
|
612
|
+
exports: Record<string, unknown>,
|
|
613
|
+
): void {
|
|
614
|
+
const callback = exports.__fui_on_fetch_error;
|
|
615
|
+
if (typeof callback !== 'function') {
|
|
616
|
+
throw new Error('Worker module is missing __fui_on_fetch_error.');
|
|
617
|
+
}
|
|
618
|
+
const payload = writeCallbackBytes(encoder.encode(message), 'Worker fetch failure payload');
|
|
619
|
+
(callback as (requestId: number, payloadPtr: number, payloadLen: number) => void)(
|
|
620
|
+
requestId,
|
|
621
|
+
payload.ptr,
|
|
622
|
+
payload.len,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function runEntry(): void {
|
|
627
|
+
if (entry === null || wasmExports === null) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
entry();
|
|
632
|
+
} catch (error: unknown) {
|
|
633
|
+
if (terminalSent) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
terminalSent = true;
|
|
637
|
+
cancelAllFetchRequests();
|
|
638
|
+
activeWorkerId = null;
|
|
639
|
+
activeCancellationRequested = false;
|
|
640
|
+
activeFile = null;
|
|
641
|
+
workerScope.postMessage({
|
|
642
|
+
type: 'error',
|
|
643
|
+
workerId: message.workerId,
|
|
644
|
+
text: describeError(error),
|
|
645
|
+
});
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (terminalSent) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function cleanupTerminal(): void {
|
|
654
|
+
terminalSent = true;
|
|
655
|
+
cancelAllFetchRequests();
|
|
656
|
+
activeWorkerId = null;
|
|
657
|
+
activeCancellationRequested = false;
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
const response = await fetch(message.wasmUrl, {
|
|
661
|
+
cache: 'no-store',
|
|
662
|
+
credentials: 'same-origin',
|
|
663
|
+
});
|
|
664
|
+
if (!response.ok) {
|
|
665
|
+
throw new Error(`Failed to load worker wasm from ${message.wasmUrl}.`);
|
|
666
|
+
}
|
|
667
|
+
const bytes = await response.arrayBuffer();
|
|
668
|
+
const module = await WebAssembly.compile(bytes);
|
|
669
|
+
const hostServices = loadWorkerHostServices(message.workerHostServices);
|
|
670
|
+
validateWorkerImports(module, hostServices);
|
|
671
|
+
const instance = await WebAssembly.instantiate(module, {
|
|
672
|
+
env: {
|
|
673
|
+
abort(_message?: number, _fileName?: number, line?: number, column?: number): never {
|
|
674
|
+
throw new Error(`Worker aborted at ${String(line ?? 0)}:${String(column ?? 0)}.`);
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
fui_host_service: createHostServiceImportModule(hostServices, {
|
|
678
|
+
readString: (ptr, len) => readUtf8(memory, ptr, len),
|
|
679
|
+
writeString: (ptr, capacity, text, context) => writeUtf8(memory, ptr, capacity, text, context),
|
|
680
|
+
readBytes: (ptr, len) => readBytes(memory, ptr, len),
|
|
681
|
+
writeBytes: (ptr, capacity, bytes, context) => writeBytes(memory, ptr, capacity, bytes, context),
|
|
682
|
+
}),
|
|
683
|
+
fui_worker_host: {
|
|
684
|
+
fui_worker_input_length(): number {
|
|
685
|
+
return 0;
|
|
686
|
+
},
|
|
687
|
+
fui_worker_copy_input(_ptr: number, _capacity: number): number {
|
|
688
|
+
return 0;
|
|
689
|
+
},
|
|
690
|
+
fui_worker_report_progress(ptr: number, len: number): void {
|
|
691
|
+
if (terminalSent) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
workerScope.postMessage({
|
|
695
|
+
type: 'progress',
|
|
696
|
+
workerId: message.workerId,
|
|
697
|
+
text: readUtf8(memory, ptr, len),
|
|
698
|
+
});
|
|
699
|
+
},
|
|
700
|
+
fui_worker_complete_string(ptr: number, len: number): void {
|
|
701
|
+
if (terminalSent) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
cleanupTerminal();
|
|
705
|
+
activeFile = null;
|
|
706
|
+
workerScope.postMessage({
|
|
707
|
+
type: 'complete',
|
|
708
|
+
workerId: message.workerId,
|
|
709
|
+
text: readUtf8(memory, ptr, len),
|
|
710
|
+
});
|
|
711
|
+
},
|
|
712
|
+
fui_worker_fail(ptr: number, len: number): void {
|
|
713
|
+
if (terminalSent) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
cleanupTerminal();
|
|
717
|
+
activeFile = null;
|
|
718
|
+
workerScope.postMessage({
|
|
719
|
+
type: 'error',
|
|
720
|
+
workerId: message.workerId,
|
|
721
|
+
text: readUtf8(memory, ptr, len),
|
|
722
|
+
});
|
|
723
|
+
},
|
|
724
|
+
fui_worker_is_cancelled(): number {
|
|
725
|
+
return readCancelFlag() ? 1 : 0;
|
|
726
|
+
},
|
|
727
|
+
fui_worker_request_yield(): void {
|
|
728
|
+
// File processing is synchronous — yield is a no-op.
|
|
729
|
+
},
|
|
730
|
+
fui_worker_request_yield_delay(_delayMs: number): void {
|
|
731
|
+
// File processing is synchronous — yield is a no-op.
|
|
732
|
+
},
|
|
733
|
+
fui_file_read_chunk(offsetLow: number, offsetHigh: number, length: number): number {
|
|
734
|
+
if (activeFile === null) {
|
|
735
|
+
return 0;
|
|
736
|
+
}
|
|
737
|
+
const offset = Number(BigInt(offsetLow >>> 0) | (BigInt(offsetHigh >>> 0) << 32n));
|
|
738
|
+
const safeLength = Math.max(0, length | 0);
|
|
739
|
+
if (offset >= activeFile.size || safeLength <= 0) {
|
|
740
|
+
return 0;
|
|
741
|
+
}
|
|
742
|
+
const blob = activeFile.slice(offset, Math.min(offset + safeLength, activeFile.size));
|
|
743
|
+
const reader = new FileReaderSync();
|
|
744
|
+
const buffer = reader.readAsArrayBuffer(blob);
|
|
745
|
+
const readBytes = new Uint8Array(buffer);
|
|
746
|
+
const written = readBytes.length;
|
|
747
|
+
if (written <= 0) {
|
|
748
|
+
return 0;
|
|
749
|
+
}
|
|
750
|
+
if (memory === null || callbackBufferSize <= 0) {
|
|
751
|
+
return 0;
|
|
752
|
+
}
|
|
753
|
+
if (written > callbackBufferSize) {
|
|
754
|
+
throw new Error('File chunk exceeds the worker callback buffer.');
|
|
755
|
+
}
|
|
756
|
+
new Uint8Array(memory.buffer, callbackBufferPtr, written).set(readBytes);
|
|
757
|
+
return written;
|
|
758
|
+
},
|
|
759
|
+
fui_file_worker_write_chunk(ptr: number, len: number): void {
|
|
760
|
+
if (memory === null || len <= 0) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const chunkBytes = new Uint8Array(len);
|
|
764
|
+
chunkBytes.set(new Uint8Array(memory.buffer, ptr, len));
|
|
765
|
+
const buffer = chunkBytes.buffer.slice(0, chunkBytes.byteLength) as ArrayBuffer;
|
|
766
|
+
workerScope.postMessage({
|
|
767
|
+
type: 'file-process-chunk',
|
|
768
|
+
workerId: message.workerId,
|
|
769
|
+
bytes: buffer,
|
|
770
|
+
}, [buffer]);
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
fui_fetch_host: {
|
|
774
|
+
fui_fetch_start(
|
|
775
|
+
requestId: number,
|
|
776
|
+
methodPtr: number,
|
|
777
|
+
methodLen: number,
|
|
778
|
+
urlPtr: number,
|
|
779
|
+
urlLen: number,
|
|
780
|
+
headersPtr: number,
|
|
781
|
+
headersLen: number,
|
|
782
|
+
bodyPtr: number,
|
|
783
|
+
bodyLen: number,
|
|
784
|
+
): void {
|
|
785
|
+
const controller = new AbortController();
|
|
786
|
+
const method = readUtf8(memory, methodPtr, methodLen);
|
|
787
|
+
const url = readUtf8(memory, urlPtr, urlLen);
|
|
788
|
+
const headerBytes = memory === null || headersLen <= 0
|
|
789
|
+
? new Uint8Array(0)
|
|
790
|
+
: new Uint8Array(memory.buffer.slice(headersPtr, headersPtr + headersLen));
|
|
791
|
+
if (headerBytes.byteLength < 4 && headersLen > 0) {
|
|
792
|
+
throw new Error('Worker fetch header payload was truncated.');
|
|
793
|
+
}
|
|
794
|
+
const headers = new Headers();
|
|
795
|
+
if (headerBytes.byteLength >= 4) {
|
|
796
|
+
const dataView = new DataView(headerBytes.buffer, headerBytes.byteOffset, headerBytes.byteLength);
|
|
797
|
+
let byteOffset = 0;
|
|
798
|
+
const count = dataView.getUint32(byteOffset, true);
|
|
799
|
+
byteOffset += 4;
|
|
800
|
+
const values: string[] = [];
|
|
801
|
+
for (let index = 0; index < count; index += 1) {
|
|
802
|
+
if (byteOffset + 4 > headerBytes.byteLength) {
|
|
803
|
+
throw new Error('Worker fetch header length was truncated.');
|
|
804
|
+
}
|
|
805
|
+
const partLen = dataView.getUint32(byteOffset, true);
|
|
806
|
+
byteOffset += 4;
|
|
807
|
+
if (byteOffset + partLen > headerBytes.byteLength) {
|
|
808
|
+
throw new Error('Worker fetch header value was truncated.');
|
|
809
|
+
}
|
|
810
|
+
values.push(partLen > 0 ? decoder.decode(headerBytes.subarray(byteOffset, byteOffset + partLen)) : '');
|
|
811
|
+
byteOffset += partLen;
|
|
812
|
+
}
|
|
813
|
+
if ((values.length & 1) != 0) {
|
|
814
|
+
throw new Error('Worker fetch headers were malformed.');
|
|
815
|
+
}
|
|
816
|
+
for (let index = 0; index < values.length; index += 2) {
|
|
817
|
+
headers.append(values[index] ?? '', values[index + 1] ?? '');
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const bodyBytes = memory === null || bodyLen <= 0
|
|
821
|
+
? new Uint8Array(0)
|
|
822
|
+
: new Uint8Array(memory.buffer, bodyPtr, bodyLen);
|
|
823
|
+
const body = bodyBytes.length > 0 ? bodyBytes : undefined;
|
|
824
|
+
activeFetchRequests.set(requestId, controller);
|
|
825
|
+
void fetch(url, {
|
|
826
|
+
method,
|
|
827
|
+
headers,
|
|
828
|
+
body,
|
|
829
|
+
signal: controller.signal,
|
|
830
|
+
}).then((response) => {
|
|
831
|
+
const active = activeFetchRequests.get(requestId);
|
|
832
|
+
if (active === undefined || active !== controller || terminalSent) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
activeFetchRequests.delete(requestId);
|
|
836
|
+
if (wasmExports === null) {
|
|
837
|
+
throw new Error('Worker fetch completed before wasm exports were ready.');
|
|
838
|
+
}
|
|
839
|
+
emitFetchComplete(requestId, response.ok, response.status, response.statusText, response.url, wasmExports);
|
|
840
|
+
}).catch((error: unknown) => {
|
|
841
|
+
const active = activeFetchRequests.get(requestId);
|
|
842
|
+
if (active === undefined || active !== controller) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
activeFetchRequests.delete(requestId);
|
|
846
|
+
if (controller.signal.aborted || terminalSent) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (wasmExports === null) {
|
|
850
|
+
throw new Error('Worker fetch failed before wasm exports were ready.');
|
|
851
|
+
}
|
|
852
|
+
emitFetchError(requestId, describeError(error), wasmExports);
|
|
853
|
+
});
|
|
854
|
+
},
|
|
855
|
+
fui_fetch_cancel(requestId: number): void {
|
|
856
|
+
const controller = activeFetchRequests.get(requestId);
|
|
857
|
+
if (controller === undefined) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
activeFetchRequests.delete(requestId);
|
|
861
|
+
controller.abort();
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
const exports = instance.exports as Record<string, unknown> & {
|
|
866
|
+
memory?: WebAssembly.Memory;
|
|
867
|
+
__fui_worker_text_buffer?: () => number;
|
|
868
|
+
__fui_worker_text_buffer_size?: () => number;
|
|
869
|
+
};
|
|
870
|
+
wasmExports = exports;
|
|
871
|
+
if (!(exports.memory instanceof WebAssembly.Memory)) {
|
|
872
|
+
throw new Error('Worker module did not export memory.');
|
|
873
|
+
}
|
|
874
|
+
memory = exports.memory;
|
|
875
|
+
if (typeof exports.__fui_worker_text_buffer !== 'function' || typeof exports.__fui_worker_text_buffer_size !== 'function') {
|
|
876
|
+
throw new Error('Worker module did not export the fetch callback buffer.');
|
|
877
|
+
}
|
|
878
|
+
callbackBufferPtr = exports.__fui_worker_text_buffer();
|
|
879
|
+
callbackBufferSize = exports.__fui_worker_text_buffer_size();
|
|
880
|
+
const exportedEntry = exports[message.entryName];
|
|
881
|
+
if (typeof exportedEntry !== 'function') {
|
|
882
|
+
throw new Error(`Worker export "${message.entryName}" is missing.`);
|
|
883
|
+
}
|
|
884
|
+
entry = exportedEntry as () => void;
|
|
885
|
+
runEntry();
|
|
886
|
+
} catch (error: unknown) {
|
|
887
|
+
cancelAllFetchRequests();
|
|
888
|
+
activeWorkerId = null;
|
|
889
|
+
activeCancellationRequested = false;
|
|
890
|
+
activeFile = null;
|
|
891
|
+
workerScope.postMessage({
|
|
892
|
+
type: 'error',
|
|
893
|
+
workerId: message.workerId,
|
|
894
|
+
text: describeError(error),
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
497
899
|
workerScope.onmessage = (event: MessageEvent<WorkerBootstrapInboundMessage>) => {
|
|
498
900
|
const message = event.data;
|
|
499
901
|
if (message.type === 'start') {
|
|
500
902
|
void startWorker(message);
|
|
501
903
|
return;
|
|
502
904
|
}
|
|
905
|
+
if (message.type === 'start-file-process') {
|
|
906
|
+
void startFileProcessWorker(message);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
503
909
|
if (message.type === 'cancel') {
|
|
504
910
|
if (activeWorkerId === message.workerId) {
|
|
505
911
|
activeCancellationRequested = true;
|
|
@@ -121,6 +121,10 @@ export function createWorkerManager(options: WorkerManagerOptions): WorkerManage
|
|
|
121
121
|
finishWorker(workerId);
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
+
// File-process chunk messages are routed through the file host, not the worker manager.
|
|
125
|
+
if (message.type === 'file-process-chunk') {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
124
128
|
emitToSession(workerId, 'error', message.text);
|
|
125
129
|
finishWorker(workerId);
|
|
126
130
|
}
|
|
@@ -17,6 +17,16 @@ export interface WorkerBootstrapStartMessage {
|
|
|
17
17
|
readonly workerHostServices?: WorkerHostServicesBundleConfig;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface WorkerBootstrapFileProcessStartMessage {
|
|
21
|
+
readonly type: "start-file-process";
|
|
22
|
+
readonly workerId: number;
|
|
23
|
+
readonly file: File;
|
|
24
|
+
readonly wasmUrl: string;
|
|
25
|
+
readonly entryName: string;
|
|
26
|
+
readonly chunkSize: number;
|
|
27
|
+
readonly workerHostServices?: WorkerHostServicesBundleConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
export interface WorkerBootstrapCancelMessage {
|
|
21
31
|
readonly type: "cancel";
|
|
22
32
|
readonly workerId: number;
|
|
@@ -24,6 +34,7 @@ export interface WorkerBootstrapCancelMessage {
|
|
|
24
34
|
|
|
25
35
|
export type WorkerBootstrapInboundMessage =
|
|
26
36
|
| WorkerBootstrapStartMessage
|
|
37
|
+
| WorkerBootstrapFileProcessStartMessage
|
|
27
38
|
| WorkerBootstrapCancelMessage;
|
|
28
39
|
|
|
29
40
|
export interface WorkerBootstrapProgressMessage {
|
|
@@ -38,6 +49,12 @@ export interface WorkerBootstrapCompleteMessage {
|
|
|
38
49
|
readonly text: string;
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
export interface WorkerBootstrapFileProcessChunkMessage {
|
|
53
|
+
readonly type: "file-process-chunk";
|
|
54
|
+
readonly workerId: number;
|
|
55
|
+
readonly bytes: ArrayBuffer;
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
export interface WorkerBootstrapErrorMessage {
|
|
42
59
|
readonly type: "error";
|
|
43
60
|
readonly workerId: number;
|
|
@@ -47,4 +64,5 @@ export interface WorkerBootstrapErrorMessage {
|
|
|
47
64
|
export type WorkerBootstrapOutboundMessage =
|
|
48
65
|
| WorkerBootstrapProgressMessage
|
|
49
66
|
| WorkerBootstrapCompleteMessage
|
|
67
|
+
| WorkerBootstrapFileProcessChunkMessage
|
|
50
68
|
| WorkerBootstrapErrorMessage;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effindomv2/fui-as",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial",
|
|
6
6
|
"description": "EffinDom v2 AssemblyScript frontend framework SDK and browser harness",
|
|
@@ -86,7 +86,8 @@
|
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"@assemblyscript/loader": "^0.28.17",
|
|
89
|
-
"@
|
|
89
|
+
"@devcycle/assemblyscript-json": "^2.0.0",
|
|
90
|
+
"@effindomv2/runtime": "0.1.13"
|
|
90
91
|
},
|
|
91
92
|
"devDependencies": {
|
|
92
93
|
"@as-pect/assembly": "8.1.0",
|
package/src/Fui.ts
CHANGED
package/src/FuiWorker.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { Fetch, FetchRequest, FetchResponse } from "./core/Fetch";
|
|
2
|
-
export { Worker as WorkerRuntime } from "./worker/Worker";
|
|
2
|
+
export { Worker, Worker as WorkerRuntime } from "./worker/Worker";
|
|
3
3
|
export { WorkerJob } from "./worker/WorkerJob";
|
|
4
|
+
export {
|
|
5
|
+
fui_file_read_chunk,
|
|
6
|
+
fui_file_worker_write_chunk,
|
|
7
|
+
} from "./worker/ffi";
|
package/src/controls/Button.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { Signal } from "../core/Signal";
|
|
|
20
20
|
import { Theme, activeTheme } from "../core/Theme";
|
|
21
21
|
import { FontFamily, FontStyle, FontWeight } from "../core/Typography";
|
|
22
22
|
import { FlexBox, TextCore } from "../nodes";
|
|
23
|
+
import { ButtonColors } from "./ButtonColors";
|
|
23
24
|
import { getControlTemplates } from "./ControlTemplateSet";
|
|
24
25
|
import {
|
|
25
26
|
ButtonPresenter,
|
|
@@ -119,6 +120,7 @@ export class Button extends FlexBox {
|
|
|
119
120
|
private shadowOffsetYValue: f32 = 0.0;
|
|
120
121
|
private shadowBlurValue: f32 = 0.0;
|
|
121
122
|
private shadowSpreadValue: f32 = 0.0;
|
|
123
|
+
private colorsValue: ButtonColors | null = null;
|
|
122
124
|
private templateValue: ButtonTemplate | null = null;
|
|
123
125
|
private presenterNeedsRefresh: bool = false;
|
|
124
126
|
|
|
@@ -203,6 +205,12 @@ export class Button extends FlexBox {
|
|
|
203
205
|
return this;
|
|
204
206
|
}
|
|
205
207
|
|
|
208
|
+
colors(colors: ButtonColors | null): this {
|
|
209
|
+
this.colorsValue = colors;
|
|
210
|
+
this.handleThemeSignalChanged();
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
|
|
206
214
|
bgColor(color: u32): this {
|
|
207
215
|
this.backgroundOverridden = true;
|
|
208
216
|
this.normalBackgroundColorValue = color;
|
|
@@ -499,14 +507,31 @@ export class Button extends FlexBox {
|
|
|
499
507
|
}
|
|
500
508
|
|
|
501
509
|
private syncThemeState(theme: Theme): void {
|
|
510
|
+
const colors = this.colorsValue;
|
|
502
511
|
if (!this.backgroundOverridden) {
|
|
503
|
-
this.normalBackgroundColorValue =
|
|
512
|
+
this.normalBackgroundColorValue = colors !== null && colors.hasBackground
|
|
513
|
+
? colors.backgroundColor
|
|
514
|
+
: theme.colors.accent;
|
|
504
515
|
}
|
|
505
516
|
if (!this.hoverBackgroundOverridden) {
|
|
506
|
-
|
|
517
|
+
if (colors !== null && colors.hasBackgroundHover) {
|
|
518
|
+
this.hoverBackgroundColorValue = colors.backgroundHoverColor;
|
|
519
|
+
} else if (colors !== null && colors.hasBackground) {
|
|
520
|
+
this.hoverBackgroundColorValue = colors.backgroundColor;
|
|
521
|
+
} else {
|
|
522
|
+
this.hoverBackgroundColorValue = theme.colors.accentHovered;
|
|
523
|
+
}
|
|
507
524
|
}
|
|
508
525
|
if (!this.pressedBackgroundOverridden) {
|
|
509
|
-
|
|
526
|
+
if (colors !== null && colors.hasBackgroundPressed) {
|
|
527
|
+
this.pressedBackgroundColorValue = colors.backgroundPressedColor;
|
|
528
|
+
} else if (colors !== null && colors.hasBackgroundHover) {
|
|
529
|
+
this.pressedBackgroundColorValue = colors.backgroundHoverColor;
|
|
530
|
+
} else if (colors !== null && colors.hasBackground) {
|
|
531
|
+
this.pressedBackgroundColorValue = colors.backgroundColor;
|
|
532
|
+
} else {
|
|
533
|
+
this.pressedBackgroundColorValue = theme.colors.accentPressed;
|
|
534
|
+
}
|
|
510
535
|
}
|
|
511
536
|
if (!this.cornerRadiusOverridden) {
|
|
512
537
|
this.focusCornerTopLeft = theme.spacing.sm;
|
|
@@ -516,7 +541,9 @@ export class Button extends FlexBox {
|
|
|
516
541
|
}
|
|
517
542
|
if (!this.borderOverridden) {
|
|
518
543
|
this.borderWidthValue = 1.0;
|
|
519
|
-
this.borderColorValue =
|
|
544
|
+
this.borderColorValue = colors !== null && colors.hasBorder
|
|
545
|
+
? colors.borderColor
|
|
546
|
+
: theme.colors.border;
|
|
520
547
|
this.borderStyleValue = BorderStyle.Solid;
|
|
521
548
|
this.borderDashedValue = false;
|
|
522
549
|
}
|
|
@@ -543,7 +570,13 @@ export class Button extends FlexBox {
|
|
|
543
570
|
this.fontSizeValue = theme.fonts.sizeBody;
|
|
544
571
|
}
|
|
545
572
|
if (!this.textColorOverridden) {
|
|
546
|
-
this.
|
|
573
|
+
if (!this.isEnabled && colors !== null && colors.hasTextMuted) {
|
|
574
|
+
this.textColorValue = colors.textMutedColor;
|
|
575
|
+
} else if (colors !== null && colors.hasTextPrimary) {
|
|
576
|
+
this.textColorValue = colors.textPrimaryColor;
|
|
577
|
+
} else {
|
|
578
|
+
this.textColorValue = theme.colors.textOnAccent;
|
|
579
|
+
}
|
|
547
580
|
}
|
|
548
581
|
const presenterHostState = new ButtonPresenterHostState(
|
|
549
582
|
this.backgroundOverridden,
|
|
@@ -572,7 +605,7 @@ export class Button extends FlexBox {
|
|
|
572
605
|
this.paddingRightValue,
|
|
573
606
|
this.paddingBottomValue,
|
|
574
607
|
);
|
|
575
|
-
this.presenter.apply(theme, this.createVisualState());
|
|
608
|
+
this.presenter.apply(theme, this.createVisualState(), this.colorsValue);
|
|
576
609
|
this.backgroundOverridden = presenterHostState.backgroundOverridden;
|
|
577
610
|
this.normalBackgroundColorValue = presenterHostState.normalBackgroundColorValue;
|
|
578
611
|
this.cornerRadiusOverridden = presenterHostState.cornerRadiusOverridden;
|