quickwin 1.0.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/README.en.md +197 -0
- package/README.md +196 -0
- package/examples/PdfCanvas.js +67 -0
- package/examples/PdfCanvas.tsx +83 -0
- package/examples/pdf_preview2.js +431 -0
- package/examples/pdf_preview2.ts +375 -0
- package/examples/pdf_viewer.js +17623 -0
- package/examples/pdf_viewer.tsx +279 -0
- package/examples/test_gallery.js +17452 -0
- package/examples/test_gallery.tsx +285 -0
- package/examples/test_react_lazy.js +17336 -0
- package/examples/test_react_lazy.tsx +63 -0
- package/examples/test_react_listbox.js +17339 -0
- package/examples/test_react_listbox.tsx +71 -0
- package/examples/test_root_window.js +17313 -0
- package/examples/test_root_window.tsx +41 -0
- package/examples/test_tab.js +17339 -0
- package/examples/test_tab.tsx +75 -0
- package/examples/tray_demo.js +79 -0
- package/examples/tray_demo.tsx +81 -0
- package/lib/assert.js +8 -0
- package/lib/assert.ts +7 -0
- package/lib/cache-utils.js +43 -0
- package/lib/cache-utils.ts +40 -0
- package/lib/dumpRects.js +25 -0
- package/lib/dumpRects.ts +25 -0
- package/lib/fetch.js +490 -0
- package/lib/fetch.ts +502 -0
- package/lib/http-common.js +342 -0
- package/lib/http-common.ts +399 -0
- package/lib/http-server.js +416 -0
- package/lib/http-server.ts +424 -0
- package/lib/polyfill.js +156 -0
- package/lib/polyfill.ts +171 -0
- package/lib/react-qw/components/Button.js +8 -0
- package/lib/react-qw/components/CheckBox.js +31 -0
- package/lib/react-qw/components/ComboBox.js +42 -0
- package/lib/react-qw/components/DateTimePicker.js +96 -0
- package/lib/react-qw/components/Input.js +50 -0
- package/lib/react-qw/components/Link.js +43 -0
- package/lib/react-qw/components/ListBox.js +48 -0
- package/lib/react-qw/components/ListView.js +328 -0
- package/lib/react-qw/components/PathPicker.js +136 -0
- package/lib/react-qw/components/ProgressBar.js +22 -0
- package/lib/react-qw/components/RadioButton.js +31 -0
- package/lib/react-qw/components/ScrollView.js +139 -0
- package/lib/react-qw/components/Slider.js +45 -0
- package/lib/react-qw/components/Tab.js +61 -0
- package/lib/react-qw/components/Tooltip.js +55 -0
- package/lib/react-qw/components/TreeView.js +130 -0
- package/lib/react-qw/components/index.js +16 -0
- package/lib/react-qw/components/index.ts +17 -0
- package/lib/react-qw/index.js +73 -0
- package/lib/react-qw/index.ts +90 -0
- package/lib/react-qw/jsx.d.ts +34 -0
- package/lib/react-qw/layout.js +82 -0
- package/lib/react-qw/layout.ts +111 -0
- package/lib/react-qw/props.js +27 -0
- package/lib/react-qw/props.ts +36 -0
- package/lib/react-qw/reconciler.js +333 -0
- package/lib/react-qw/reconciler.ts +373 -0
- package/lib/stream.js +2 -0
- package/lib/stream.ts +22 -0
- package/lib/text-codec.js +197 -0
- package/lib/text-codec.ts +171 -0
- package/lib/text-measure.js +52 -0
- package/lib/text-measure.ts +54 -0
- package/lib/url.js +278 -0
- package/lib/url.ts +201 -0
- package/lib/vendor/web-streams/ponyfill.d.mts +829 -0
- package/lib/vendor/web-streams/ponyfill.mjs +8 -0
- package/lib/websocket.js +541 -0
- package/lib/websocket.ts +555 -0
- package/package.json +44 -0
- package/quickwin.d.ts +594 -0
- package/quickwin_const.d.ts +900 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin.exe +0 -0
- package/test/add.wasm +0 -0
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/debug/http_server_e2e.js +31 -0
- package/test/debug/minwasm.js +52 -0
- package/test/debug/mupdf_debug.js +104 -0
- package/test/debug/wasm.js +122 -0
- package/test/debug/wasm_export_debug.js +64 -0
- package/test/debug/wasm_names.js +46 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/run.js +91 -0
- package/test/run.ts +96 -0
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +7 -0
- package/test/test_basic.ts +9 -0
- package/test/test_basic_fetch.js +34 -0
- package/test/test_basic_fetch.ts +33 -0
- package/test/test_brotli.js +48 -0
- package/test/test_brotli.ts +52 -0
- package/test/test_fetch_cache.js +169 -0
- package/test/test_fetch_cache.ts +187 -0
- package/test/test_ffi.js +157 -0
- package/test/test_ffi.ts +174 -0
- package/test/test_frame_encoding.js +128 -0
- package/test/test_frame_encoding.ts +132 -0
- package/test/test_helper.js +84 -0
- package/test/test_helper.ts +80 -0
- package/test/test_http_import.js +78 -0
- package/test/test_http_import.ts +74 -0
- package/test/test_http_server.js +142 -0
- package/test/test_http_server.ts +136 -0
- package/test/test_import_dir.js +4 -0
- package/test/test_import_dir.ts +5 -0
- package/test/test_ipv6.js +134 -0
- package/test/test_ipv6.ts +129 -0
- package/test/test_jsx.js +1314 -0
- package/test/test_meta_url.js +3 -0
- package/test/test_meta_url.ts +4 -0
- package/test/test_mupdf_render.js +69 -0
- package/test/test_mupdf_render.ts +74 -0
- package/test/test_mupdf_twice.js +77 -0
- package/test/test_mupdf_twice.ts +81 -0
- package/test/test_mupdf_wasm.js +33 -0
- package/test/test_mupdf_wasm.ts +30 -0
- package/test/test_net_event.js +57 -0
- package/test/test_net_event.ts +55 -0
- package/test/test_net_fetch.js +259 -0
- package/test/test_net_fetch.ts +238 -0
- package/test/test_net_websocket.js +180 -0
- package/test/test_net_websocket.ts +164 -0
- package/test/test_polyfill.js +144 -0
- package/test/test_polyfill.ts +159 -0
- package/test/test_react_complex.js +17440 -0
- package/test/test_react_counter.js +17331 -0
- package/test/test_react_flex.js +17643 -0
- package/test/test_react_hidden.js +17336 -0
- package/test/test_react_listview_link.js +17371 -0
- package/test/test_react_render.js +17326 -0
- package/test/test_url.js +173 -0
- package/test/test_url.ts +183 -0
- package/test/test_wasm_basic.js +85 -0
- package/test/test_wasm_basic.ts +73 -0
- package/test/test_wasm_import_global.js +41 -0
- package/test/test_wasm_import_global.ts +45 -0
- package/test/test_wasm_sjlj.js +145 -0
- package/test/test_wasm_sjlj.ts +125 -0
- package/test/test_wasm_types.js +97 -0
- package/test/test_wasm_types.ts +124 -0
- package/test/test_worker.js +21 -0
- package/test/test_worker.ts +24 -0
- package/test/test_worker_fetch.js +26 -0
- package/test/test_worker_fetch.ts +27 -0
- package/test/test_worker_net.js +51 -0
- package/test/test_worker_net.ts +54 -0
- package/test/test_worker_wasm_concurrent.js +44 -0
- package/test/test_worker_wasm_concurrent.ts +45 -0
- package/test/types.wasm +0 -0
- package/test/worker_fetch.js +13 -0
- package/test/worker_fetch.ts +13 -0
- package/test/worker_helper.js +1 -0
- package/test/worker_helper.ts +1 -0
- package/test/worker_http.js +18 -0
- package/test/worker_http.ts +20 -0
- package/test/worker_import_lib.js +12 -0
- package/test/worker_import_lib.ts +13 -0
- package/test/worker_module.js +16 -0
- package/test/worker_module.ts +17 -0
- package/test/worker_wasm_concurrent.js +22 -0
- package/test/worker_wasm_concurrent.ts +22 -0
- package/tsconfig.json +27 -0
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
- package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
- package/vendor/mupdf-wasm/mupdf.js +3317 -0
package/lib/polyfill.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as os from 'os'
|
|
2
|
+
import './url.js'
|
|
3
|
+
import './text-codec.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Console {
|
|
8
|
+
error: (...args: unknown[]) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var window: typeof globalThis
|
|
12
|
+
|
|
13
|
+
type TimerId = os.TimerId
|
|
14
|
+
function setTimeout<P extends unknown[]>(fn: (...argsf: P) => void, ms?: number, ...argsr: P): TimerId
|
|
15
|
+
function clearTimeout(id: TimerId): void
|
|
16
|
+
|
|
17
|
+
function btoa(data: string): string
|
|
18
|
+
function atob(data: string): string
|
|
19
|
+
|
|
20
|
+
interface SubtleCrypto {
|
|
21
|
+
digest(algorithm: 'SHA-1', data: ArrayBuffer | Uint8Array): Promise<ArrayBuffer>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Crypto {
|
|
25
|
+
getRandomValues<T extends ArrayBufferView>(array: T): T
|
|
26
|
+
subtle: SubtleCrypto
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var crypto: Crypto
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 1. window — triggers Emscripten browser path
|
|
33
|
+
if (typeof globalThis.window === 'undefined')
|
|
34
|
+
globalThis.window = globalThis
|
|
35
|
+
|
|
36
|
+
// 2. setTimeout polyfill — bridge to os.setTimeout
|
|
37
|
+
if (typeof globalThis.setTimeout === 'undefined') {
|
|
38
|
+
globalThis.setTimeout = ((fn, ms, ...args) => {
|
|
39
|
+
return os.setTimeout(() => fn(...args), ms ?? 0)
|
|
40
|
+
}) satisfies typeof setTimeout
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2b. clearTimeout polyfill — bridge to os.clearTimeout
|
|
44
|
+
if (typeof globalThis.clearTimeout === 'undefined') {
|
|
45
|
+
globalThis.clearTimeout = os.clearTimeout
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. console.error polyfill — QuickJS only has console.log
|
|
49
|
+
if (typeof console.error === 'undefined') {
|
|
50
|
+
console.error = (...args: unknown[]) => { console.log(...args) }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// ── SHA-1 ──
|
|
55
|
+
|
|
56
|
+
function sha1Bytes(bytes: number[]): ArrayBuffer {
|
|
57
|
+
const len = bytes.length
|
|
58
|
+
const ml = len * 8
|
|
59
|
+
|
|
60
|
+
bytes.push(0x80)
|
|
61
|
+
while (bytes.length % 64 !== 56) { bytes.push(0) }
|
|
62
|
+
bytes.push(0)
|
|
63
|
+
bytes.push(0)
|
|
64
|
+
bytes.push(0)
|
|
65
|
+
bytes.push(0)
|
|
66
|
+
bytes.push((ml >>> 24) & 0xFF)
|
|
67
|
+
bytes.push((ml >>> 16) & 0xFF)
|
|
68
|
+
bytes.push((ml >>> 8) & 0xFF)
|
|
69
|
+
bytes.push(ml & 0xFF)
|
|
70
|
+
|
|
71
|
+
let h0 = 0x67452301, h1 = 0xEFCDAB89
|
|
72
|
+
let h2 = 0x98BADCFE, h3 = 0x10325476, h4 = 0xC3D2E1F0
|
|
73
|
+
const rotl = (x: number, n: number): number => ((x << n) | (x >>> (32 - n))) >>> 0
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < bytes.length; i += 64) {
|
|
76
|
+
const w: number[] = []
|
|
77
|
+
for (let t = 0; t < 16; t++) {
|
|
78
|
+
w[t] = (bytes[i + 4 * t]! << 24) | (bytes[i + 4 * t + 1]! << 16) |
|
|
79
|
+
(bytes[i + 4 * t + 2]! << 8) | bytes[i + 4 * t + 3]!
|
|
80
|
+
}
|
|
81
|
+
for (let t = 16; t < 80; t++) {
|
|
82
|
+
w[t] = rotl(w[t - 3]! ^ w[t - 8]! ^ w[t - 14]! ^ w[t - 16]!, 1)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let a = h0, b = h1, c = h2, d = h3, e = h4
|
|
86
|
+
for (let t = 0; t < 80; t++) {
|
|
87
|
+
let f: number, k: number
|
|
88
|
+
if (t < 20) { f = (b & c) | (~b & d); k = 0x5A827999 }
|
|
89
|
+
else if (t < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1 }
|
|
90
|
+
else if (t < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC }
|
|
91
|
+
else { f = b ^ c ^ d; k = 0xCA62C1D6 }
|
|
92
|
+
const temp = (rotl(a, 5) + f + e + k + w[t]!) >>> 0
|
|
93
|
+
e = d; d = c; c = rotl(b, 30); b = a; a = temp
|
|
94
|
+
}
|
|
95
|
+
h0 = (h0 + a) >>> 0; h1 = (h1 + b) >>> 0
|
|
96
|
+
h2 = (h2 + c) >>> 0; h3 = (h3 + d) >>> 0; h4 = (h4 + e) >>> 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = new Uint8Array(20)
|
|
100
|
+
const w32 = (off: number, v: number) => {
|
|
101
|
+
result[off] = (v >>> 24) & 0xFF; result[off + 1] = (v >>> 16) & 0xFF
|
|
102
|
+
result[off + 2] = (v >>> 8) & 0xFF; result[off + 3] = v & 0xFF
|
|
103
|
+
}
|
|
104
|
+
w32(0, h0); w32(4, h1); w32(8, h2); w32(12, h3); w32(16, h4)
|
|
105
|
+
return result.buffer
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Base64 ──
|
|
109
|
+
|
|
110
|
+
const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
111
|
+
|
|
112
|
+
function b64Encode(data: Uint8Array): string {
|
|
113
|
+
let r = ''
|
|
114
|
+
for (let i = 0; i < data.length; i += 3) {
|
|
115
|
+
const b0 = data[i]!, b1 = i + 1 < data.length ? data[i + 1]! : 0, b2 = i + 2 < data.length ? data[i + 2]! : 0
|
|
116
|
+
r += B64[b0 >> 2]! + B64[((b0 & 3) << 4) | (b1 >> 4)]!
|
|
117
|
+
r += i + 1 < data.length ? B64[((b1 & 15) << 2) | (b2 >> 6)]! : '='
|
|
118
|
+
r += i + 2 < data.length ? B64[b2 & 63]! : '='
|
|
119
|
+
}
|
|
120
|
+
return r
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function b64Decode(str: string): Uint8Array {
|
|
124
|
+
str = str.replace(/[^A-Za-z0-9+/=]/g, '')
|
|
125
|
+
const bytes: number[] = []
|
|
126
|
+
for (let i = 0; i < str.length; i += 4) {
|
|
127
|
+
const c0 = B64.indexOf(str[i]!), c1 = B64.indexOf(str[i + 1]!)
|
|
128
|
+
const c2 = B64.indexOf(str[i + 2]!), c3 = B64.indexOf(str[i + 3]!)
|
|
129
|
+
bytes.push((c0 << 2) | (c1 >> 4))
|
|
130
|
+
if (c2 >= 0) bytes.push(((c1 & 15) << 4) | (c2 >> 2))
|
|
131
|
+
if (c3 >= 0) bytes.push(((c2 & 3) << 6) | c3)
|
|
132
|
+
}
|
|
133
|
+
return new Uint8Array(bytes)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── btoa / atob ──
|
|
137
|
+
|
|
138
|
+
if (typeof globalThis.btoa === 'undefined') {
|
|
139
|
+
globalThis.btoa = (data: string): string => {
|
|
140
|
+
const bytes = new Uint8Array(data.length)
|
|
141
|
+
for (let i = 0; i < data.length; i++) bytes[i] = data.charCodeAt(i) & 0xFF
|
|
142
|
+
return b64Encode(bytes)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (typeof globalThis.atob === 'undefined') {
|
|
147
|
+
globalThis.atob = (data: string): string => {
|
|
148
|
+
const bytes = b64Decode(data)
|
|
149
|
+
let r = ''
|
|
150
|
+
for (let i = 0; i < bytes.length; i++) r += String.fromCharCode(bytes[i]!)
|
|
151
|
+
return r
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (typeof globalThis.crypto === 'undefined') {
|
|
156
|
+
globalThis.crypto = {
|
|
157
|
+
getRandomValues: <T extends ArrayBufferView>(array: T): T => {
|
|
158
|
+
const view = new Uint8Array(array.buffer, array.byteOffset, array.byteLength)
|
|
159
|
+
for (let i = 0; i < view.length; i++) view[i] = (Math.random() * 256) | 0
|
|
160
|
+
return array
|
|
161
|
+
},
|
|
162
|
+
subtle: {
|
|
163
|
+
digest: (algorithm: string, data: ArrayBuffer | Uint8Array): Promise<ArrayBuffer> => {
|
|
164
|
+
if (algorithm.toUpperCase() !== 'SHA-1')
|
|
165
|
+
return Promise.reject(new Error('Digest: ' + algorithm + ' not supported'))
|
|
166
|
+
const bytes = data instanceof Uint8Array ? Array.from(data) : Array.from(new Uint8Array(data))
|
|
167
|
+
return Promise.resolve(sha1Bytes(bytes))
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
export const Button = forwardRef(({ children, onClick, style, disabled }, ref) => {
|
|
4
|
+
return (_jsx("w", { type: "BUTTON", text: children != null ? (Array.isArray(children) ? children.join('') : String(children)) : '', ws: 268435456 /* gui.WindowStyle.VISIBLE */, style: style, disabled: disabled, ref: ref, onEvent: (e) => {
|
|
5
|
+
if (e.msg === 514 /* gui.WmMsg.LBUTTONUP */)
|
|
6
|
+
onClick?.();
|
|
7
|
+
} }));
|
|
8
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useState, useEffect, useRef } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
export const CheckBox = forwardRef(({ checked: controlledChecked, defaultChecked = false, onChange, label, style, disabled }, ref) => {
|
|
5
|
+
const [internalChecked, setInternalChecked] = useState(defaultChecked);
|
|
6
|
+
const isControlled = controlledChecked !== undefined;
|
|
7
|
+
const displayChecked = isControlled ? controlledChecked : internalChecked;
|
|
8
|
+
const cbRef = useRef(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const h = cbRef.current;
|
|
11
|
+
if (!h)
|
|
12
|
+
return;
|
|
13
|
+
gui.SendMessage(h, 241 /* gui.ButtonMsg.SETCHECK */, displayChecked ? 1 /* gui.ButtonCheckState.CHECKED */ : 0 /* gui.ButtonCheckState.UNCHECKED */, 0);
|
|
14
|
+
}, [displayChecked]);
|
|
15
|
+
return (_jsx("w", { type: "BUTTON", text: label ?? '', ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 3 /* gui.ButtonStyle.AUTOCHECKBOX */, style: style, disabled: disabled, ref: (h) => {
|
|
16
|
+
cbRef.current = h;
|
|
17
|
+
if (typeof ref === 'function')
|
|
18
|
+
ref(h);
|
|
19
|
+
else if (ref)
|
|
20
|
+
ref.current = h;
|
|
21
|
+
}, onEvent: (e) => {
|
|
22
|
+
if (e.msg === 514 /* gui.WmMsg.LBUTTONUP */) {
|
|
23
|
+
const newChecked = gui.SendMessage(e.hwnd, 240 /* gui.ButtonMsg.GETCHECK */, 0, 0) !== 0;
|
|
24
|
+
if (newChecked !== displayChecked) {
|
|
25
|
+
if (!isControlled)
|
|
26
|
+
setInternalChecked(newChecked);
|
|
27
|
+
onChange?.(newChecked);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} }));
|
|
31
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef, useEffect, useState } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
export const ComboBox = forwardRef(({ items, selectedIndex: controlledIndex, defaultSelectedIndex = -1, onChange, style }, ref) => {
|
|
5
|
+
const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
|
|
6
|
+
const isControlled = controlledIndex !== undefined;
|
|
7
|
+
const sel = isControlled ? controlledIndex : internalIndex;
|
|
8
|
+
const cbRef = useRef(null);
|
|
9
|
+
const cbWs = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */
|
|
10
|
+
| 3 /* gui.ComboBoxStyle.DROPDOWNLIST */ | 512 /* gui.ComboBoxStyle.HASSTRINGS */;
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const h = cbRef.current;
|
|
13
|
+
if (!h)
|
|
14
|
+
return;
|
|
15
|
+
gui.SendMessage(h, 331 /* gui.ComboBoxMsg.RESETCONTENT */, 0, 0);
|
|
16
|
+
for (const item of items)
|
|
17
|
+
gui.SendMessage(h, 323 /* gui.ComboBoxMsg.ADDSTRING */, 0, item);
|
|
18
|
+
if (sel >= 0 && sel < items.length)
|
|
19
|
+
gui.SendMessage(h, 334 /* gui.ComboBoxMsg.SETCURSEL */, sel, 0);
|
|
20
|
+
}, [items]);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const h = cbRef.current;
|
|
23
|
+
if (h)
|
|
24
|
+
gui.SendMessage(h, 334 /* gui.ComboBoxMsg.SETCURSEL */, sel, 0);
|
|
25
|
+
}, [sel]);
|
|
26
|
+
return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
|
|
27
|
+
if (e.msg !== 273 /* gui.WmMsg.COMMAND */)
|
|
28
|
+
return;
|
|
29
|
+
const code = (e.wParam >> 16) & 0xFFFF;
|
|
30
|
+
if (code === 1 /* gui.CbnCode.SELCHANGE */) {
|
|
31
|
+
const h = cbRef.current;
|
|
32
|
+
if (!h)
|
|
33
|
+
return;
|
|
34
|
+
const newSel = gui.SendMessage(h, 327 /* gui.ComboBoxMsg.GETCURSEL */, 0, 0);
|
|
35
|
+
if (newSel !== sel && newSel >= 0) {
|
|
36
|
+
if (!isControlled)
|
|
37
|
+
setInternalIndex(newSel);
|
|
38
|
+
onChange?.(newSel);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, children: _jsx("w", { type: "COMBOBOX", ws: cbWs, style: { flexGrow: 1 }, ref: (h) => { cbRef.current = h; } }) }));
|
|
42
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef, useEffect, useState } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
import * as ffi from 'ffi';
|
|
5
|
+
function readI16(ptr, offset) {
|
|
6
|
+
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8);
|
|
7
|
+
}
|
|
8
|
+
function readI32(ptr, offset) {
|
|
9
|
+
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
10
|
+
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
|
|
11
|
+
}
|
|
12
|
+
function bufPtr(buf) {
|
|
13
|
+
return ffi.bufferPtr(buf);
|
|
14
|
+
}
|
|
15
|
+
function dateToSysTimeBuf(d) {
|
|
16
|
+
const buf = new ArrayBuffer(16);
|
|
17
|
+
const dv = new DataView(buf);
|
|
18
|
+
dv.setUint16(0, d.getFullYear(), true);
|
|
19
|
+
dv.setUint16(2, d.getMonth() + 1, true);
|
|
20
|
+
dv.setUint16(4, 0, true); // wDayOfWeek (ignored on set)
|
|
21
|
+
dv.setUint16(6, d.getDate(), true);
|
|
22
|
+
dv.setUint16(8, d.getHours(), true);
|
|
23
|
+
dv.setUint16(10, d.getMinutes(), true);
|
|
24
|
+
dv.setUint16(12, d.getSeconds(), true);
|
|
25
|
+
dv.setUint16(14, d.getMilliseconds(), true);
|
|
26
|
+
return buf;
|
|
27
|
+
}
|
|
28
|
+
const DateTimePicker = forwardRef(function DateTimePicker({ value, onChange, defaultValue, format = 'short', allowNone, updown, style }, ref) {
|
|
29
|
+
const [internalDate, setInternalDate] = useState(defaultValue ?? null);
|
|
30
|
+
const isControlled = value !== undefined;
|
|
31
|
+
const effectiveDate = isControlled ? value : internalDate;
|
|
32
|
+
const dpRef = useRef(null);
|
|
33
|
+
const onChangeRef = useRef(onChange);
|
|
34
|
+
onChangeRef.current = onChange;
|
|
35
|
+
let dpStyle = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */;
|
|
36
|
+
if (format === 'long')
|
|
37
|
+
dpStyle |= 4 /* gui.DtStyle.LONGDATEFORMAT */;
|
|
38
|
+
else if (format === 'time')
|
|
39
|
+
dpStyle |= 9 /* gui.DtStyle.TIMEFORMAT */;
|
|
40
|
+
if (updown)
|
|
41
|
+
dpStyle |= 1 /* gui.DtStyle.UPDOWN */;
|
|
42
|
+
if (allowNone)
|
|
43
|
+
dpStyle |= 2 /* gui.DtStyle.SHOWNONE */;
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const h = dpRef.current;
|
|
46
|
+
if (!h)
|
|
47
|
+
return;
|
|
48
|
+
const d = effectiveDate;
|
|
49
|
+
if (d) {
|
|
50
|
+
const buf = dateToSysTimeBuf(d);
|
|
51
|
+
gui.SendMessage(h, 4098 /* gui.DtMsg.SETSYSTEMTIME */, 0 /* gui.DtFlag.GDT_VALID */, bufPtr(buf));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
gui.SendMessage(h, 4098 /* gui.DtMsg.SETSYSTEMTIME */, 1 /* gui.DtFlag.GDT_NONE */, 0);
|
|
55
|
+
}
|
|
56
|
+
}, [effectiveDate]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const h = dpRef.current;
|
|
59
|
+
if (!h)
|
|
60
|
+
return;
|
|
61
|
+
gui.InvalidateRect(h, null, true);
|
|
62
|
+
// 发送 WM_SIZE 让控件(尤其是 DTS_UPDOWN)重新布局内部子窗口
|
|
63
|
+
const cr = gui.GetClientRect(h);
|
|
64
|
+
if (cr) {
|
|
65
|
+
const w = cr.right - cr.left;
|
|
66
|
+
const hh = cr.bottom - cr.top;
|
|
67
|
+
gui.SendMessage(h, 5 /* gui.WmMsg.SIZE */, 0, (hh << 16) | w);
|
|
68
|
+
}
|
|
69
|
+
}, []);
|
|
70
|
+
return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
|
|
71
|
+
if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
|
|
72
|
+
const code = readI32(e.lParam, 16);
|
|
73
|
+
if (code === -759 /* gui.DtNotifyCode.DATETIMECHANGE */) {
|
|
74
|
+
const dwFlags = readI32(e.lParam, 24);
|
|
75
|
+
if (dwFlags === 1 /* gui.DtFlag.GDT_NONE */) {
|
|
76
|
+
if (!isControlled)
|
|
77
|
+
setInternalDate(null);
|
|
78
|
+
onChangeRef.current?.(null);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const year = readI16(e.lParam, 28);
|
|
82
|
+
const month = readI16(e.lParam, 30);
|
|
83
|
+
const day = readI16(e.lParam, 34);
|
|
84
|
+
const hour = readI16(e.lParam, 36);
|
|
85
|
+
const min = readI16(e.lParam, 38);
|
|
86
|
+
const sec = readI16(e.lParam, 40);
|
|
87
|
+
const d = new Date(year, month - 1, day, hour, min, sec);
|
|
88
|
+
if (!isControlled)
|
|
89
|
+
setInternalDate(d);
|
|
90
|
+
onChangeRef.current?.(d);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}, children: _jsx("w", { type: "SysDateTimePick32", ws: dpStyle, style: { flexGrow: 1 }, ref: (h) => { dpRef.current = h; } }) }));
|
|
95
|
+
});
|
|
96
|
+
export { DateTimePicker };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useState, useRef, useEffect } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
import * as ffi from 'ffi';
|
|
5
|
+
function textToUtf16(s) {
|
|
6
|
+
const buf = new ArrayBuffer((s.length + 1) * 2);
|
|
7
|
+
const dv = new DataView(buf);
|
|
8
|
+
for (let i = 0; i < s.length; i++)
|
|
9
|
+
dv.setUint16(i * 2, s.charCodeAt(i), true);
|
|
10
|
+
return buf;
|
|
11
|
+
}
|
|
12
|
+
export const Input = forwardRef(({ value: controlledValue, defaultValue = '', onChange, placeholder, password, multiline, readonly, number, disabled, style }, ref) => {
|
|
13
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
14
|
+
const isControlled = controlledValue !== undefined;
|
|
15
|
+
const displayValue = isControlled ? controlledValue : internalValue;
|
|
16
|
+
const inputRef = useRef(null);
|
|
17
|
+
let ws = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */;
|
|
18
|
+
if (multiline)
|
|
19
|
+
ws |= 4 /* gui.EditStyle.MULTILINE */ | 64 /* gui.EditStyle.AUTOVSCROLL */ | 2097152 /* gui.WindowStyle.VSCROLL */;
|
|
20
|
+
if (readonly)
|
|
21
|
+
ws |= 2048 /* gui.EditStyle.READONLY */;
|
|
22
|
+
if (number)
|
|
23
|
+
ws |= 8192 /* gui.EditStyle.NUMBER */;
|
|
24
|
+
if (password)
|
|
25
|
+
ws |= 32 /* gui.EditStyle.PASSWORD */ | 128 /* gui.EditStyle.AUTOHSCROLL */;
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const h = inputRef.current;
|
|
28
|
+
if (!h || !placeholder)
|
|
29
|
+
return;
|
|
30
|
+
const buf = textToUtf16(placeholder);
|
|
31
|
+
gui.SendMessage(h, 5377 /* gui.EditMsg.SETCUEBANNER */, 1, ffi.bufferPtr(buf));
|
|
32
|
+
}, [placeholder]);
|
|
33
|
+
return (_jsx("w", { type: "EDIT", text: displayValue, ws: ws, style: style, disabled: disabled, ref: (h) => {
|
|
34
|
+
inputRef.current = h;
|
|
35
|
+
if (typeof ref === 'function')
|
|
36
|
+
ref(h);
|
|
37
|
+
else if (ref)
|
|
38
|
+
ref.current = h;
|
|
39
|
+
}, onEvent: (e) => {
|
|
40
|
+
if (e.msg === 258 /* gui.WmMsg.CHAR */) {
|
|
41
|
+
const h = inputRef.current;
|
|
42
|
+
if (!h)
|
|
43
|
+
return;
|
|
44
|
+
const newText = gui.GetWindowText(h);
|
|
45
|
+
if (!isControlled)
|
|
46
|
+
setInternalValue(newText);
|
|
47
|
+
onChange?.(newText);
|
|
48
|
+
}
|
|
49
|
+
} }));
|
|
50
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef, useEffect } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
import * as ffi from 'ffi';
|
|
5
|
+
function readI32(ptr, offset) {
|
|
6
|
+
return ffi.readByte(ptr + offset) | (ffi.readByte(ptr + offset + 1) << 8) |
|
|
7
|
+
(ffi.readByte(ptr + offset + 2) << 16) | (ffi.readByte(ptr + offset + 3) << 24);
|
|
8
|
+
}
|
|
9
|
+
function readUtf16(ptr, offset, maxWords) {
|
|
10
|
+
const chars = [];
|
|
11
|
+
for (let i = 0; i < maxWords; i++) {
|
|
12
|
+
const lo = ffi.readByte(ptr + offset + i * 2);
|
|
13
|
+
const hi = ffi.readByte(ptr + offset + i * 2 + 1);
|
|
14
|
+
const code = (hi << 8) | lo;
|
|
15
|
+
if (code === 0)
|
|
16
|
+
break;
|
|
17
|
+
chars.push(String.fromCharCode(code));
|
|
18
|
+
}
|
|
19
|
+
return chars.join('');
|
|
20
|
+
}
|
|
21
|
+
const Link = forwardRef(function Link({ href, children, onClick, style }, ref) {
|
|
22
|
+
const displayText = children != null ? String(children) : (href ?? '');
|
|
23
|
+
const linkText = href ? `<A HREF="${href}">${displayText}</A>` : displayText;
|
|
24
|
+
const linkRef = useRef(null);
|
|
25
|
+
const onClickRef = useRef(onClick);
|
|
26
|
+
onClickRef.current = onClick;
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const h = linkRef.current;
|
|
29
|
+
if (!h)
|
|
30
|
+
return;
|
|
31
|
+
gui.InvalidateRect(h, null, true);
|
|
32
|
+
}, [linkText]);
|
|
33
|
+
return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, ref: ref, onEvent: (e) => {
|
|
34
|
+
if (e.msg === 78 /* gui.WmMsg.NOTIFY */) {
|
|
35
|
+
const code = readI32(e.lParam, 16);
|
|
36
|
+
if (code === -2 /* gui.SysLinkNotifyCode.CLICK */ || code === -4 /* gui.SysLinkNotifyCode.RETURN */) {
|
|
37
|
+
const url = readUtf16(e.lParam, 136, 2048);
|
|
38
|
+
onClickRef.current?.(url);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, children: _jsx("w", { type: "SysLink", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 65536 /* gui.WindowStyle.TABSTOP */, text: linkText, style: { flexGrow: 1 }, ref: (h) => { linkRef.current = h; } }) }));
|
|
42
|
+
});
|
|
43
|
+
export { Link };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useRef, useEffect, useState, useImperativeHandle } from 'react';
|
|
3
|
+
import * as gui from 'gui';
|
|
4
|
+
export const ListBox = forwardRef(({ items, selectedIndex: controlledIndex, defaultSelectedIndex = -1, onChange, style, sort, scrollToBottom }, ref) => {
|
|
5
|
+
const [internalIndex, setInternalIndex] = useState(defaultSelectedIndex);
|
|
6
|
+
const isControlled = controlledIndex !== undefined;
|
|
7
|
+
const sel = isControlled ? controlledIndex : internalIndex;
|
|
8
|
+
const lbRef = useRef(null);
|
|
9
|
+
useImperativeHandle(ref, () => lbRef.current);
|
|
10
|
+
const lbWs = 268435456 /* gui.WindowStyle.VISIBLE */ | 8388608 /* gui.WindowStyle.BORDER */ | 2097152 /* gui.WindowStyle.VSCROLL */
|
|
11
|
+
| 64 /* gui.ListBoxStyle.HASSTRINGS */ | 1 /* gui.ListBoxStyle.NOTIFY */ | 256 /* gui.ListBoxStyle.NOINTEGRALHEIGHT */
|
|
12
|
+
| (sort ? 2 /* gui.ListBoxStyle.SORT */ : 0);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const h = lbRef.current;
|
|
15
|
+
if (!h)
|
|
16
|
+
return;
|
|
17
|
+
gui.SendMessage(h, 388 /* gui.LbMsg.RESETCONTENT */, 0, 0);
|
|
18
|
+
for (const item of items)
|
|
19
|
+
gui.SendMessage(h, 384 /* gui.LbMsg.ADDSTRING */, 0, item);
|
|
20
|
+
if (sel >= 0 && sel < items.length)
|
|
21
|
+
gui.SendMessage(h, 390 /* gui.LbMsg.SETCURSEL */, sel, 0);
|
|
22
|
+
if (scrollToBottom && items.length > 0)
|
|
23
|
+
gui.SendMessage(h, 407 /* gui.LbMsg.SETTOPINDEX */, items.length - 1, 0);
|
|
24
|
+
}, [items]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const h = lbRef.current;
|
|
27
|
+
if (h)
|
|
28
|
+
gui.SendMessage(h, 390 /* gui.LbMsg.SETCURSEL */, sel, 0);
|
|
29
|
+
}, [sel]);
|
|
30
|
+
return (_jsx("w", { type: "STATIC", ws: 268435456 /* gui.WindowStyle.VISIBLE */ | 33554432 /* gui.WindowStyle.CLIPCHILDREN */, style: { ...style, flexDirection: 'column', alignItems: 'stretch' }, onEvent: (e) => {
|
|
31
|
+
if (e.msg !== 273 /* gui.WmMsg.COMMAND */)
|
|
32
|
+
return;
|
|
33
|
+
const code = (e.wParam >> 16) & 0xFFFF;
|
|
34
|
+
if (code === 1 /* gui.LbnCode.SELCHANGE */) {
|
|
35
|
+
const h = lbRef.current;
|
|
36
|
+
if (!h)
|
|
37
|
+
return;
|
|
38
|
+
const newSel = gui.SendMessage(h, 392 /* gui.LbMsg.GETCURSEL */, 0, 0);
|
|
39
|
+
if (newSel !== sel && newSel >= 0) {
|
|
40
|
+
if (!isControlled)
|
|
41
|
+
setInternalIndex(newSel);
|
|
42
|
+
onChange?.(newSel);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, children: _jsx("w", { type: "LISTBOX", ws: lbWs, style: { flexGrow: 1 }, ref: (h) => {
|
|
46
|
+
lbRef.current = h;
|
|
47
|
+
} }) }));
|
|
48
|
+
});
|