jtcsv 2.2.7 → 3.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.md +31 -1
- package/bin/jtcsv.js +891 -821
- package/bin/jtcsv.ts +2534 -0
- package/csv-to-json.js +168 -145
- package/dist/jtcsv-core.cjs.js +1407 -0
- package/dist/jtcsv-core.cjs.js.map +1 -0
- package/dist/jtcsv-core.esm.js +1379 -0
- package/dist/jtcsv-core.esm.js.map +1 -0
- package/dist/jtcsv-core.umd.js +1413 -0
- package/dist/jtcsv-core.umd.js.map +1 -0
- package/dist/jtcsv-full.cjs.js +1912 -0
- package/dist/jtcsv-full.cjs.js.map +1 -0
- package/dist/jtcsv-full.esm.js +1880 -0
- package/dist/jtcsv-full.esm.js.map +1 -0
- package/dist/jtcsv-full.umd.js +1918 -0
- package/dist/jtcsv-full.umd.js.map +1 -0
- package/dist/jtcsv-workers.esm.js +759 -0
- package/dist/jtcsv-workers.esm.js.map +1 -0
- package/dist/jtcsv-workers.umd.js +773 -0
- package/dist/jtcsv-workers.umd.js.map +1 -0
- package/dist/jtcsv.cjs.js +61 -19
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +61 -19
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +61 -19
- package/dist/jtcsv.umd.js.map +1 -1
- package/errors.js +188 -2
- package/examples/advanced/conditional-transformations.js +446 -0
- package/examples/advanced/conditional-transformations.ts +446 -0
- package/examples/advanced/csv-parser.worker.js +89 -0
- package/examples/advanced/csv-parser.worker.ts +89 -0
- package/examples/advanced/nested-objects-example.js +306 -0
- package/examples/advanced/nested-objects-example.ts +306 -0
- package/examples/advanced/performance-optimization.js +504 -0
- package/examples/advanced/performance-optimization.ts +504 -0
- package/examples/advanced/run-demo-server.js +116 -0
- package/examples/advanced/run-demo-server.ts +116 -0
- package/examples/advanced/web-worker-usage.html +874 -0
- package/examples/async-multithreaded-example.ts +335 -0
- package/examples/cli-advanced-usage.md +288 -0
- package/examples/cli-batch-processing.ts +38 -0
- package/examples/cli-tool.js +0 -3
- package/examples/cli-tool.ts +183 -0
- package/examples/error-handling.js +21 -7
- package/examples/error-handling.ts +356 -0
- package/examples/express-api.js +0 -3
- package/examples/express-api.ts +164 -0
- package/examples/large-dataset-example.js +0 -3
- package/examples/large-dataset-example.ts +204 -0
- package/examples/ndjson-processing.js +1 -1
- package/examples/ndjson-processing.ts +456 -0
- package/examples/plugin-excel-exporter.js +3 -4
- package/examples/plugin-excel-exporter.ts +406 -0
- package/examples/react-integration.tsx +637 -0
- package/examples/schema-validation.ts +640 -0
- package/examples/simple-usage.js +254 -254
- package/examples/simple-usage.ts +194 -0
- package/examples/streaming-example.js +4 -5
- package/examples/streaming-example.ts +419 -0
- package/examples/web-workers-advanced.ts +28 -0
- package/index.d.ts +1 -3
- package/index.js +15 -1
- package/json-save.js +9 -3
- package/json-to-csv.js +168 -21
- package/package.json +69 -10
- package/plugins/express-middleware/README.md +21 -2
- package/plugins/express-middleware/example.js +3 -4
- package/plugins/express-middleware/example.ts +135 -0
- package/plugins/express-middleware/index.d.ts +1 -1
- package/plugins/express-middleware/index.js +270 -118
- package/plugins/express-middleware/index.ts +557 -0
- package/plugins/fastify-plugin/index.js +2 -4
- package/plugins/fastify-plugin/index.ts +443 -0
- package/plugins/hono/index.ts +226 -0
- package/plugins/nestjs/index.ts +201 -0
- package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
- package/plugins/nextjs-api/examples/api-convert.js +0 -2
- package/plugins/nextjs-api/examples/api-convert.ts +67 -0
- package/plugins/nextjs-api/index.tsx +339 -0
- package/plugins/nextjs-api/route.js +2 -3
- package/plugins/nextjs-api/route.ts +370 -0
- package/plugins/nuxt/index.ts +94 -0
- package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
- package/plugins/nuxt/runtime/plugin.ts +71 -0
- package/plugins/remix/index.js +1 -1
- package/plugins/remix/index.ts +260 -0
- package/plugins/sveltekit/index.js +1 -1
- package/plugins/sveltekit/index.ts +301 -0
- package/plugins/trpc/index.ts +267 -0
- package/src/browser/browser-functions.ts +402 -0
- package/src/browser/core.js +92 -0
- package/src/browser/core.ts +152 -0
- package/src/browser/csv-to-json-browser.d.ts +3 -0
- package/src/browser/csv-to-json-browser.js +36 -14
- package/src/browser/csv-to-json-browser.ts +264 -0
- package/src/browser/errors-browser.ts +303 -0
- package/src/browser/extensions/plugins.js +92 -0
- package/src/browser/extensions/plugins.ts +93 -0
- package/src/browser/extensions/workers.js +39 -0
- package/src/browser/extensions/workers.ts +39 -0
- package/src/browser/globals.d.ts +5 -0
- package/src/browser/index.ts +192 -0
- package/src/browser/json-to-csv-browser.d.ts +3 -0
- package/src/browser/json-to-csv-browser.js +13 -3
- package/src/browser/json-to-csv-browser.ts +262 -0
- package/src/browser/streams.js +12 -2
- package/src/browser/streams.ts +336 -0
- package/src/browser/workers/csv-parser.worker.ts +377 -0
- package/src/browser/workers/worker-pool.ts +548 -0
- package/src/core/delimiter-cache.js +22 -8
- package/src/core/delimiter-cache.ts +310 -0
- package/src/core/node-optimizations.ts +449 -0
- package/src/core/plugin-system.js +29 -11
- package/src/core/plugin-system.ts +400 -0
- package/src/core/transform-hooks.ts +558 -0
- package/src/engines/fast-path-engine-new.ts +347 -0
- package/src/engines/fast-path-engine.ts +854 -0
- package/src/errors.ts +72 -0
- package/src/formats/ndjson-parser.ts +469 -0
- package/src/formats/tsv-parser.ts +334 -0
- package/src/index-with-plugins.js +16 -9
- package/src/index-with-plugins.ts +395 -0
- package/src/types/index.ts +255 -0
- package/src/utils/bom-utils.js +259 -0
- package/src/utils/bom-utils.ts +373 -0
- package/src/utils/encoding-support.js +124 -0
- package/src/utils/encoding-support.ts +155 -0
- package/src/utils/schema-validator.js +19 -19
- package/src/utils/schema-validator.ts +819 -0
- package/src/utils/transform-loader.js +1 -1
- package/src/utils/transform-loader.ts +389 -0
- package/src/utils/zod-adapter.js +170 -0
- package/src/utils/zod-adapter.ts +280 -0
- package/src/web-server/index.js +10 -10
- package/src/web-server/index.ts +683 -0
- package/src/workers/csv-multithreaded.ts +310 -0
- package/src/workers/csv-parser.worker.ts +227 -0
- package/src/workers/worker-pool.ts +409 -0
- package/stream-csv-to-json.js +26 -8
- package/stream-json-to-csv.js +1 -0
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>jtcsv Web Worker Example</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
16
|
+
line-height: 1.6;
|
|
17
|
+
color: #333;
|
|
18
|
+
max-width: 1200px;
|
|
19
|
+
margin: 0 auto;
|
|
20
|
+
padding: 20px;
|
|
21
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
22
|
+
min-height: 100vh;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.container {
|
|
26
|
+
background: white;
|
|
27
|
+
border-radius: 12px;
|
|
28
|
+
padding: 30px;
|
|
29
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
h1 {
|
|
33
|
+
color: #2d3748;
|
|
34
|
+
margin-bottom: 10px;
|
|
35
|
+
font-size: 2.5rem;
|
|
36
|
+
background: linear-gradient(90deg, #667eea, #764ba2);
|
|
37
|
+
background-clip: text;
|
|
38
|
+
-webkit-background-clip: text;
|
|
39
|
+
color: transparent;
|
|
40
|
+
-webkit-text-fill-color: transparent;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.subtitle {
|
|
44
|
+
color: #718096;
|
|
45
|
+
margin-bottom: 30px;
|
|
46
|
+
font-size: 1.1rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.grid {
|
|
50
|
+
display: grid;
|
|
51
|
+
grid-template-columns: 1fr 1fr;
|
|
52
|
+
gap: 30px;
|
|
53
|
+
margin-bottom: 30px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@media (max-width: 768px) {
|
|
57
|
+
.grid {
|
|
58
|
+
grid-template-columns: 1fr;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.card {
|
|
63
|
+
background: #f7fafc;
|
|
64
|
+
border-radius: 8px;
|
|
65
|
+
padding: 20px;
|
|
66
|
+
border: 1px solid #e2e8f0;
|
|
67
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.card:hover {
|
|
71
|
+
transform: translateY(-2px);
|
|
72
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.card h2 {
|
|
76
|
+
color: #4a5568;
|
|
77
|
+
margin-bottom: 15px;
|
|
78
|
+
font-size: 1.5rem;
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 10px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.card h2 i {
|
|
85
|
+
font-size: 1.2rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.controls {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
gap: 15px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.file-input {
|
|
95
|
+
padding: 12px;
|
|
96
|
+
border: 2px dashed #cbd5e0;
|
|
97
|
+
border-radius: 6px;
|
|
98
|
+
background: white;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
transition: border-color 0.2s;
|
|
101
|
+
text-align: center;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.file-input:hover {
|
|
105
|
+
border-color: #667eea;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.file-input input {
|
|
109
|
+
display: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.file-label {
|
|
113
|
+
display: block;
|
|
114
|
+
color: #4a5568;
|
|
115
|
+
font-weight: 500;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.file-info {
|
|
119
|
+
font-size: 0.9rem;
|
|
120
|
+
color: #718096;
|
|
121
|
+
margin-top: 5px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
button {
|
|
125
|
+
background: linear-gradient(90deg, #667eea, #764ba2);
|
|
126
|
+
color: white;
|
|
127
|
+
border: none;
|
|
128
|
+
padding: 12px 24px;
|
|
129
|
+
border-radius: 6px;
|
|
130
|
+
font-size: 1rem;
|
|
131
|
+
font-weight: 600;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
gap: 8px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
button:hover:not(:disabled) {
|
|
141
|
+
transform: translateY(-1px);
|
|
142
|
+
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
button:disabled {
|
|
146
|
+
opacity: 0.6;
|
|
147
|
+
cursor: not-allowed;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.stats {
|
|
151
|
+
display: grid;
|
|
152
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
153
|
+
gap: 15px;
|
|
154
|
+
margin-top: 20px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.stat {
|
|
158
|
+
background: white;
|
|
159
|
+
padding: 15px;
|
|
160
|
+
border-radius: 6px;
|
|
161
|
+
border-left: 4px solid #667eea;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.stat-label {
|
|
165
|
+
font-size: 0.9rem;
|
|
166
|
+
color: #718096;
|
|
167
|
+
margin-bottom: 5px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.stat-value {
|
|
171
|
+
font-size: 1.5rem;
|
|
172
|
+
font-weight: 700;
|
|
173
|
+
color: #2d3748;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.progress-container {
|
|
177
|
+
margin-top: 20px;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.progress-bar {
|
|
181
|
+
height: 8px;
|
|
182
|
+
background: #e2e8f0;
|
|
183
|
+
border-radius: 4px;
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
margin-bottom: 10px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.progress-fill {
|
|
189
|
+
height: 100%;
|
|
190
|
+
background: linear-gradient(90deg, #667eea, #764ba2);
|
|
191
|
+
width: 0%;
|
|
192
|
+
transition: width 0.3s ease;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.progress-text {
|
|
196
|
+
display: flex;
|
|
197
|
+
justify-content: space-between;
|
|
198
|
+
font-size: 0.9rem;
|
|
199
|
+
color: #718096;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.results {
|
|
203
|
+
margin-top: 20px;
|
|
204
|
+
max-height: 300px;
|
|
205
|
+
overflow-y: auto;
|
|
206
|
+
border: 1px solid #e2e8f0;
|
|
207
|
+
border-radius: 6px;
|
|
208
|
+
padding: 15px;
|
|
209
|
+
background: white;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.result-item {
|
|
213
|
+
padding: 10px;
|
|
214
|
+
border-bottom: 1px solid #edf2f7;
|
|
215
|
+
font-family: 'Courier New', monospace;
|
|
216
|
+
font-size: 0.9rem;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.result-item:last-child {
|
|
220
|
+
border-bottom: none;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.comparison {
|
|
224
|
+
margin-top: 30px;
|
|
225
|
+
padding-top: 20px;
|
|
226
|
+
border-top: 2px solid #e2e8f0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.comparison-grid {
|
|
230
|
+
display: grid;
|
|
231
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
232
|
+
gap: 20px;
|
|
233
|
+
margin-top: 15px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.comparison-item {
|
|
237
|
+
background: white;
|
|
238
|
+
padding: 15px;
|
|
239
|
+
border-radius: 6px;
|
|
240
|
+
border: 1px solid #e2e8f0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.comparison-item.winner {
|
|
244
|
+
border-color: #48bb78;
|
|
245
|
+
background: #f0fff4;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.comparison-title {
|
|
249
|
+
font-weight: 600;
|
|
250
|
+
margin-bottom: 10px;
|
|
251
|
+
color: #4a5568;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.comparison-time {
|
|
255
|
+
font-size: 1.8rem;
|
|
256
|
+
font-weight: 700;
|
|
257
|
+
color: #2d3748;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.comparison-diff {
|
|
261
|
+
font-size: 0.9rem;
|
|
262
|
+
color: #718096;
|
|
263
|
+
margin-top: 5px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.comparison-diff.positive {
|
|
267
|
+
color: #48bb78;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.comparison-diff.negative {
|
|
271
|
+
color: #f56565;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.code-block {
|
|
275
|
+
background: #2d3748;
|
|
276
|
+
color: #e2e8f0;
|
|
277
|
+
padding: 15px;
|
|
278
|
+
border-radius: 6px;
|
|
279
|
+
font-family: 'Courier New', monospace;
|
|
280
|
+
font-size: 0.9rem;
|
|
281
|
+
overflow-x: auto;
|
|
282
|
+
margin-top: 20px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.code-keyword {
|
|
286
|
+
color: #63b3ed;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.code-string {
|
|
290
|
+
color: #68d391;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.code-comment {
|
|
294
|
+
color: #a0aec0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.code-function {
|
|
298
|
+
color: #fbb6ce;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.status {
|
|
302
|
+
padding: 10px;
|
|
303
|
+
border-radius: 6px;
|
|
304
|
+
margin-top: 15px;
|
|
305
|
+
font-weight: 500;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.status.success {
|
|
309
|
+
background: #c6f6d5;
|
|
310
|
+
color: #22543d;
|
|
311
|
+
border: 1px solid #9ae6b4;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.status.error {
|
|
315
|
+
background: #fed7d7;
|
|
316
|
+
color: #742a2a;
|
|
317
|
+
border: 1px solid #fc8181;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.status.info {
|
|
321
|
+
background: #bee3f8;
|
|
322
|
+
color: #2a4365;
|
|
323
|
+
border: 1px solid #90cdf4;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.spinner {
|
|
327
|
+
width: 20px;
|
|
328
|
+
height: 20px;
|
|
329
|
+
border: 3px solid rgba(255, 255, 255, 0.3);
|
|
330
|
+
border-radius: 50%;
|
|
331
|
+
border-top-color: white;
|
|
332
|
+
animation: spin 1s ease-in-out infinite;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@keyframes spin {
|
|
336
|
+
to { transform: rotate(360deg); }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.hidden {
|
|
340
|
+
display: none;
|
|
341
|
+
}
|
|
342
|
+
</style>
|
|
343
|
+
</head>
|
|
344
|
+
<body>
|
|
345
|
+
<div class="container">
|
|
346
|
+
<h1>jtcsv Web Worker Demo</h1>
|
|
347
|
+
<p class="subtitle">Process large CSV files without blocking the main thread</p>
|
|
348
|
+
|
|
349
|
+
<div class="grid">
|
|
350
|
+
<div class="card">
|
|
351
|
+
<h2><i>📁</i> Upload CSV File</h2>
|
|
352
|
+
<div class="controls">
|
|
353
|
+
<div class="file-input" id="fileDropArea">
|
|
354
|
+
<input type="file" id="csvFile" accept=".csv,.txt">
|
|
355
|
+
<label for="csvFile" class="file-label">
|
|
356
|
+
<div>📂 Click to select or drag & drop</div>
|
|
357
|
+
<div class="file-info">Supports CSV files up to 100MB</div>
|
|
358
|
+
</label>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
<div class="stats">
|
|
362
|
+
<div class="stat">
|
|
363
|
+
<div class="stat-label">Selected File</div>
|
|
364
|
+
<div class="stat-value" id="fileName">None</div>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="stat">
|
|
367
|
+
<div class="stat-label">File Size</div>
|
|
368
|
+
<div class="stat-value" id="fileSize">0 KB</div>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<div class="progress-container" id="progressContainer" style="display: none;">
|
|
373
|
+
<div class="progress-bar">
|
|
374
|
+
<div class="progress-fill" id="progressFill"></div>
|
|
375
|
+
</div>
|
|
376
|
+
<div class="progress-text">
|
|
377
|
+
<span>Processing...</span>
|
|
378
|
+
<span id="progressPercent">0%</span>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<button id="processBtn" disabled>
|
|
383
|
+
<span>Process with Web Worker</span>
|
|
384
|
+
</button>
|
|
385
|
+
|
|
386
|
+
<button id="processMainBtn" disabled style="background: linear-gradient(90deg, #ed8936, #dd6b20);">
|
|
387
|
+
<span>Process in Main Thread (Blocking)</span>
|
|
388
|
+
</button>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div id="status" class="status hidden"></div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<div class="card">
|
|
395
|
+
<h2><i>📊</i> Performance Comparison</h2>
|
|
396
|
+
<div class="comparison-grid" id="comparisonResults">
|
|
397
|
+
<div class="comparison-item">
|
|
398
|
+
<div class="comparison-title">Web Worker</div>
|
|
399
|
+
<div class="comparison-time" id="workerTime">-- ms</div>
|
|
400
|
+
<div class="comparison-diff" id="workerDiff">--</div>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="comparison-item">
|
|
403
|
+
<div class="comparison-title">Main Thread</div>
|
|
404
|
+
<div class="comparison-time" id="mainTime">-- ms</div>
|
|
405
|
+
<div class="comparison-diff" id="mainDiff">--</div>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<div class="stats">
|
|
410
|
+
<div class="stat">
|
|
411
|
+
<div class="stat-label">UI Responsiveness</div>
|
|
412
|
+
<div class="stat-value" id="uiResponsiveness">Good</div>
|
|
413
|
+
</div>
|
|
414
|
+
<div class="stat">
|
|
415
|
+
<div class="stat-label">Memory Usage</div>
|
|
416
|
+
<div class="stat-value" id="memoryUsage">-- MB</div>
|
|
417
|
+
</div>
|
|
418
|
+
<div class="stat">
|
|
419
|
+
<div class="stat-label">Rows Processed</div>
|
|
420
|
+
<div class="stat-value" id="rowsProcessed">0</div>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
424
|
+
<div class="results" id="resultsPreview">
|
|
425
|
+
<div class="result-item">Results will appear here...</div>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<div class="card">
|
|
431
|
+
<h2><i>💻</i> Code Example</h2>
|
|
432
|
+
<div class="code-block">
|
|
433
|
+
<pre><code><span class="code-comment">// Using jtcsv with Web Workers</span>
|
|
434
|
+
<span class="code-keyword">import</span> { createWorkerPool } <span class="code-keyword">from</span> <span class="code-string">'jtcsv/browser/workers'</span>;
|
|
435
|
+
|
|
436
|
+
<span class="code-comment">// Create a pool of 4 workers</span>
|
|
437
|
+
<span class="code-keyword">const</span> workerPool = <span class="code-function">createWorkerPool</span>(<span class="code-string">'./csv-parser.worker.js'</span>, {
|
|
438
|
+
maxWorkers: <span class="code-string">4</span>,
|
|
439
|
+
idleTimeout: <span class="code-string">30000</span>
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
<span class="code-comment">// Process CSV in background</span>
|
|
443
|
+
<span class="code-keyword">async</span> <span class="code-keyword">function</span> <span class="code-function">processLargeCsv</span>(csvData) {
|
|
444
|
+
<span class="code-keyword">const</span> startTime = <span class="code-function">performance</span>.<span class="code-function">now</span>();
|
|
445
|
+
|
|
446
|
+
<span class="code-comment">// Submit task to worker pool</span>
|
|
447
|
+
<span class="code-keyword">const</span> result = <span class="code-keyword">await</span> workerPool.<span class="code-function">execute</span>({
|
|
448
|
+
type: <span class="code-string">'parseCsv'</span>,
|
|
449
|
+
csv: csvData,
|
|
450
|
+
options: {
|
|
451
|
+
hasHeaders: <span class="code-keyword">true</span>,
|
|
452
|
+
parseNumbers: <span class="code-keyword">true</span>,
|
|
453
|
+
parseBooleans: <span class="code-keyword">true</span>
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
<span class="code-keyword">const</span> endTime = <span class="code-function">performance</span>.<span class="code-function">now</span>();
|
|
458
|
+
<span class="code-function">console</span>.<span class="code-function">log</span>(<span class="code-string">`Processed </span>${result.data.length}<span class="code-string"> rows in </span>${endTime - startTime}<span class="code-string">ms`</span>);
|
|
459
|
+
|
|
460
|
+
<span class="code-keyword">return</span> result.data;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
<span class="code-comment">// Clean up when done</span>
|
|
464
|
+
workerPool.<span class="code-function">terminate</span>();</code></pre>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<div class="comparison">
|
|
469
|
+
<h2><i>⚡</i> Benefits of Web Workers</h2>
|
|
470
|
+
<div class="comparison-grid">
|
|
471
|
+
<div class="comparison-item winner">
|
|
472
|
+
<div class="comparison-title">With Web Workers</div>
|
|
473
|
+
<ul style="padding-left: 20px; margin-top: 10px; color: #4a5568;">
|
|
474
|
+
<li>UI remains responsive</li>
|
|
475
|
+
<li>Parallel processing</li>
|
|
476
|
+
<li>Better memory management</li>
|
|
477
|
+
<li>No main thread blocking</li>
|
|
478
|
+
</ul>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="comparison-item">
|
|
481
|
+
<div class="comparison-title">Without Web Workers</div>
|
|
482
|
+
<ul style="padding-left: 20px; margin-top: 10px; color: #4a5568;">
|
|
483
|
+
<li>UI freezes during processing</li>
|
|
484
|
+
<li>Single-threaded</li>
|
|
485
|
+
<li>Memory pressure on main thread</li>
|
|
486
|
+
<li>Poor user experience</li>
|
|
487
|
+
</ul>
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<script>
|
|
494
|
+
// Load jtcsv from CDN (in real scenario, you would bundle it)
|
|
495
|
+
const jtcsvScript = document.createElement('script');
|
|
496
|
+
jtcsvScript.src = 'https://unpkg.com/jtcsv@latest/dist/jtcsv.umd.js';
|
|
497
|
+
document.head.appendChild(jtcsvScript);
|
|
498
|
+
|
|
499
|
+
jtcsvScript.onload = () => {
|
|
500
|
+
console.log('jtcsv loaded successfully');
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
jtcsvScript.onerror = () => {
|
|
504
|
+
console.error('Failed to load jtcsv from CDN');
|
|
505
|
+
showStatus('Failed to load jtcsv library. Please check your internet connection.', 'error');
|
|
506
|
+
// Disable processing buttons
|
|
507
|
+
document.getElementById('processBtn').disabled = true;
|
|
508
|
+
document.getElementById('processMainBtn').disabled = true;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// DOM elements
|
|
512
|
+
const fileInput = document.getElementById('csvFile');
|
|
513
|
+
const fileDropArea = document.getElementById('fileDropArea');
|
|
514
|
+
const processBtn = document.getElementById('processBtn');
|
|
515
|
+
const processMainBtn = document.getElementById('processMainBtn');
|
|
516
|
+
const fileName = document.getElementById('fileName');
|
|
517
|
+
const fileSize = document.getElementById('fileSize');
|
|
518
|
+
const progressContainer = document.getElementById('progressContainer');
|
|
519
|
+
const progressFill = document.getElementById('progressFill');
|
|
520
|
+
const progressPercent = document.getElementById('progressPercent');
|
|
521
|
+
const status = document.getElementById('status');
|
|
522
|
+
const resultsPreview = document.getElementById('resultsPreview');
|
|
523
|
+
const workerTime = document.getElementById('workerTime');
|
|
524
|
+
const mainTime = document.getElementById('mainTime');
|
|
525
|
+
const workerDiff = document.getElementById('workerDiff');
|
|
526
|
+
const mainDiff = document.getElementById('mainDiff');
|
|
527
|
+
const uiResponsiveness = document.getElementById('uiResponsiveness');
|
|
528
|
+
const memoryUsage = document.getElementById('memoryUsage');
|
|
529
|
+
const rowsProcessed = document.getElementById('rowsProcessed');
|
|
530
|
+
|
|
531
|
+
// Server availability
|
|
532
|
+
let serverAvailable = false;
|
|
533
|
+
|
|
534
|
+
// Check if server is running (worker file accessible)
|
|
535
|
+
function checkServerAvailability() {
|
|
536
|
+
fetch('./csv-parser.worker.js', { method: 'HEAD', cache: 'no-cache' })
|
|
537
|
+
.then(response => {
|
|
538
|
+
serverAvailable = response.ok;
|
|
539
|
+
updateServerStatus();
|
|
540
|
+
})
|
|
541
|
+
.catch(() => {
|
|
542
|
+
serverAvailable = false;
|
|
543
|
+
updateServerStatus();
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function updateServerStatus() {
|
|
548
|
+
const btn = document.getElementById('processBtn');
|
|
549
|
+
if (!serverAvailable) {
|
|
550
|
+
btn.disabled = true;
|
|
551
|
+
btn.innerHTML = '<span>Start server to enable Web Worker</span>';
|
|
552
|
+
btn.title = 'The demo server is not running. To start it, run: node examples/advanced/run-demo-server.js';
|
|
553
|
+
// Add tooltip styling
|
|
554
|
+
btn.style.position = 'relative';
|
|
555
|
+
btn.style.cursor = 'not-allowed';
|
|
556
|
+
} else {
|
|
557
|
+
btn.disabled = false;
|
|
558
|
+
btn.innerHTML = '<span>Process with Web Worker</span>';
|
|
559
|
+
btn.title = '';
|
|
560
|
+
btn.style.position = '';
|
|
561
|
+
btn.style.cursor = '';
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// State
|
|
566
|
+
let selectedFile = null;
|
|
567
|
+
let csvContent = '';
|
|
568
|
+
let worker = null;
|
|
569
|
+
let isProcessing = false;
|
|
570
|
+
|
|
571
|
+
// File handling
|
|
572
|
+
fileInput.addEventListener('change', handleFileSelect);
|
|
573
|
+
|
|
574
|
+
// Drag and drop
|
|
575
|
+
fileDropArea.addEventListener('dragover', (e) => {
|
|
576
|
+
e.preventDefault();
|
|
577
|
+
fileDropArea.style.borderColor = '#667eea';
|
|
578
|
+
fileDropArea.style.backgroundColor = '#f7fafc';
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
fileDropArea.addEventListener('dragleave', () => {
|
|
582
|
+
fileDropArea.style.borderColor = '#cbd5e0';
|
|
583
|
+
fileDropArea.style.backgroundColor = 'white';
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
fileDropArea.addEventListener('drop', (e) => {
|
|
587
|
+
e.preventDefault();
|
|
588
|
+
fileDropArea.style.borderColor = '#cbd5e0';
|
|
589
|
+
fileDropArea.style.backgroundColor = 'white';
|
|
590
|
+
|
|
591
|
+
if (e.dataTransfer.files.length) {
|
|
592
|
+
fileInput.files = e.dataTransfer.files;
|
|
593
|
+
handleFileSelect();
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
function handleFileSelect() {
|
|
598
|
+
if (!fileInput.files.length) return;
|
|
599
|
+
|
|
600
|
+
selectedFile = fileInput.files[0];
|
|
601
|
+
fileName.textContent = selectedFile.name;
|
|
602
|
+
fileSize.textContent = formatFileSize(selectedFile.size);
|
|
603
|
+
|
|
604
|
+
// Read file content
|
|
605
|
+
const reader = new FileReader();
|
|
606
|
+
reader.onload = (e) => {
|
|
607
|
+
csvContent = e.target.result;
|
|
608
|
+
processBtn.disabled = false;
|
|
609
|
+
processMainBtn.disabled = false;
|
|
610
|
+
showStatus('File loaded successfully!', 'success');
|
|
611
|
+
};
|
|
612
|
+
reader.onerror = () => {
|
|
613
|
+
showStatus('Error reading file', 'error');
|
|
614
|
+
};
|
|
615
|
+
reader.readAsText(selectedFile);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Process with Web Worker
|
|
619
|
+
processBtn.addEventListener('click', async () => {
|
|
620
|
+
if (!csvContent || isProcessing) return;
|
|
621
|
+
|
|
622
|
+
isProcessing = true;
|
|
623
|
+
updateButtons();
|
|
624
|
+
showProgress(true);
|
|
625
|
+
showStatus('Processing with Web Worker...', 'info');
|
|
626
|
+
|
|
627
|
+
// Create worker if not exists
|
|
628
|
+
if (!worker) {
|
|
629
|
+
if (!serverAvailable) {
|
|
630
|
+
showStatus('Web Worker requires the demo server to be running. Please start the server.', 'error');
|
|
631
|
+
resetProcessing();
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
worker = new Worker('./csv-parser.worker.js');
|
|
635
|
+
|
|
636
|
+
worker.onmessage = (e) => {
|
|
637
|
+
const { type, data, progress, error } = e.data;
|
|
638
|
+
|
|
639
|
+
if (type === 'progress') {
|
|
640
|
+
updateProgress(progress);
|
|
641
|
+
} else if (type === 'result') {
|
|
642
|
+
handleWorkerResult(data);
|
|
643
|
+
} else if (type === 'error') {
|
|
644
|
+
clearInterval(uiTestInterval);
|
|
645
|
+
document.title = 'jtcsv Web Worker Demo';
|
|
646
|
+
showStatus(`Worker error: ${error}`, 'error');
|
|
647
|
+
resetProcessing();
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
worker.onerror = (error) => {
|
|
652
|
+
clearInterval(uiTestInterval);
|
|
653
|
+
document.title = 'jtcsv Web Worker Demo';
|
|
654
|
+
showStatus(`Worker error: ${error.message}`, 'error');
|
|
655
|
+
resetProcessing();
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Start processing
|
|
660
|
+
const startTime = performance.now();
|
|
661
|
+
uiResponsiveness.textContent = 'Responsive';
|
|
662
|
+
|
|
663
|
+
// Simulate UI responsiveness test
|
|
664
|
+
let uiUpdates = 0;
|
|
665
|
+
const uiTestInterval = setInterval(() => {
|
|
666
|
+
uiUpdates++;
|
|
667
|
+
document.title = `Processing... ${uiUpdates}`;
|
|
668
|
+
}, 100);
|
|
669
|
+
|
|
670
|
+
worker.postMessage({
|
|
671
|
+
type: 'parseCsv',
|
|
672
|
+
csv: csvContent,
|
|
673
|
+
options: {
|
|
674
|
+
hasHeaders: true,
|
|
675
|
+
parseNumbers: true,
|
|
676
|
+
parseBooleans: true
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// Handle completion
|
|
681
|
+
function handleWorkerResult(data) {
|
|
682
|
+
clearInterval(uiTestInterval);
|
|
683
|
+
document.title = 'jtcsv Web Worker Demo';
|
|
684
|
+
|
|
685
|
+
const endTime = performance.now();
|
|
686
|
+
const processingTime = endTime - startTime;
|
|
687
|
+
|
|
688
|
+
workerTime.textContent = `${processingTime.toFixed(0)} ms`;
|
|
689
|
+
rowsProcessed.textContent = data.length;
|
|
690
|
+
|
|
691
|
+
// Update results preview
|
|
692
|
+
updateResultsPreview(data);
|
|
693
|
+
|
|
694
|
+
// Compare with main thread (if available)
|
|
695
|
+
if (window.mainThreadResult) {
|
|
696
|
+
const diff = processingTime - window.mainThreadResult.time;
|
|
697
|
+
const percentDiff = (diff / window.mainThreadResult.time * 100).toFixed(1);
|
|
698
|
+
|
|
699
|
+
if (diff > 0) {
|
|
700
|
+
workerDiff.textContent = `${percentDiff}% slower`;
|
|
701
|
+
workerDiff.className = 'comparison-diff negative';
|
|
702
|
+
} else {
|
|
703
|
+
workerDiff.textContent = `${Math.abs(percentDiff)}% faster`;
|
|
704
|
+
workerDiff.className = 'comparison-diff positive';
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
showStatus(`Processed ${data.length} rows in ${processingTime.toFixed(0)}ms`, 'success');
|
|
709
|
+
resetProcessing();
|
|
710
|
+
|
|
711
|
+
// Update memory usage (simulated)
|
|
712
|
+
memoryUsage.textContent = `${Math.round((data.length * 0.1) / 1024 * 100) / 100} MB`;
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Process in main thread (blocking)
|
|
717
|
+
processMainBtn.addEventListener('click', async () => {
|
|
718
|
+
if (!csvContent || isProcessing) return;
|
|
719
|
+
|
|
720
|
+
isProcessing = true;
|
|
721
|
+
updateButtons();
|
|
722
|
+
showProgress(true);
|
|
723
|
+
showStatus('Processing in main thread (may freeze UI)...', 'info');
|
|
724
|
+
|
|
725
|
+
const startTime = performance.now();
|
|
726
|
+
uiResponsiveness.textContent = 'May freeze';
|
|
727
|
+
|
|
728
|
+
// Simulate UI freeze test
|
|
729
|
+
let uiUpdates = 0;
|
|
730
|
+
const uiTestInterval = setInterval(() => {
|
|
731
|
+
uiUpdates++;
|
|
732
|
+
document.title = `Processing... ${uiUpdates}`;
|
|
733
|
+
}, 100);
|
|
734
|
+
|
|
735
|
+
try {
|
|
736
|
+
// Use jtcsv directly in main thread
|
|
737
|
+
const result = jtcsv.csvToJson(csvContent, {
|
|
738
|
+
hasHeaders: true,
|
|
739
|
+
parseNumbers: true,
|
|
740
|
+
parseBooleans: true
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
clearInterval(uiTestInterval);
|
|
744
|
+
document.title = 'jtcsv Web Worker Demo';
|
|
745
|
+
|
|
746
|
+
const endTime = performance.now();
|
|
747
|
+
const processingTime = endTime - startTime;
|
|
748
|
+
|
|
749
|
+
mainTime.textContent = `${processingTime.toFixed(0)} ms`;
|
|
750
|
+
rowsProcessed.textContent = result.length;
|
|
751
|
+
|
|
752
|
+
// Update results preview
|
|
753
|
+
updateResultsPreview(result);
|
|
754
|
+
|
|
755
|
+
// Store for comparison
|
|
756
|
+
window.mainThreadResult = {
|
|
757
|
+
time: processingTime,
|
|
758
|
+
data: result
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
showStatus(`Processed ${result.length} rows in ${processingTime.toFixed(0)}ms`, 'success');
|
|
762
|
+
resetProcessing();
|
|
763
|
+
|
|
764
|
+
// Update memory usage (simulated)
|
|
765
|
+
memoryUsage.textContent = `${Math.round((result.length * 0.1) / 1024 * 100) / 100} MB`;
|
|
766
|
+
|
|
767
|
+
} catch (error) {
|
|
768
|
+
clearInterval(uiTestInterval);
|
|
769
|
+
document.title = 'jtcsv Web Worker Demo';
|
|
770
|
+
showStatus(`Error: ${error.message}`, 'error');
|
|
771
|
+
resetProcessing();
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// Helper functions
|
|
776
|
+
function formatFileSize(bytes) {
|
|
777
|
+
if (bytes === 0) return '0 Bytes';
|
|
778
|
+
const k = 1024;
|
|
779
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
780
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
781
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function showStatus(message, type) {
|
|
785
|
+
status.textContent = message;
|
|
786
|
+
status.className = `status ${type}`;
|
|
787
|
+
status.classList.remove('hidden');
|
|
788
|
+
|
|
789
|
+
// Auto-hide success messages
|
|
790
|
+
if (type === 'success') {
|
|
791
|
+
setTimeout(() => {
|
|
792
|
+
status.classList.add('hidden');
|
|
793
|
+
}, 3000);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function showProgress(show) {
|
|
798
|
+
if (show) {
|
|
799
|
+
progressContainer.style.display = 'block';
|
|
800
|
+
progressFill.style.width = '0%';
|
|
801
|
+
progressPercent.textContent = '0%';
|
|
802
|
+
} else {
|
|
803
|
+
progressContainer.style.display = 'none';
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function updateProgress(percent) {
|
|
808
|
+
progressFill.style.width = `${percent}%`;
|
|
809
|
+
progressPercent.textContent = `${percent}%`;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function updateButtons() {
|
|
813
|
+
processBtn.disabled = isProcessing;
|
|
814
|
+
processMainBtn.disabled = isProcessing;
|
|
815
|
+
|
|
816
|
+
if (isProcessing) {
|
|
817
|
+
processBtn.innerHTML = '<div class="spinner"></div><span>Processing...</span>';
|
|
818
|
+
processMainBtn.innerHTML = '<div class="spinner"></div><span>Processing...</span>';
|
|
819
|
+
} else {
|
|
820
|
+
processBtn.innerHTML = '<span>Process with Web Worker</span>';
|
|
821
|
+
processMainBtn.innerHTML = '<span>Process in Main Thread (Blocking)</span>';
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function resetProcessing() {
|
|
826
|
+
isProcessing = false;
|
|
827
|
+
updateButtons();
|
|
828
|
+
showProgress(false);
|
|
829
|
+
uiResponsiveness.textContent = 'Good';
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function updateResultsPreview(data) {
|
|
833
|
+
resultsPreview.innerHTML = '';
|
|
834
|
+
|
|
835
|
+
// Show first 10 rows
|
|
836
|
+
const previewRows = data.slice(0, 10);
|
|
837
|
+
|
|
838
|
+
previewRows.forEach((row, index) => {
|
|
839
|
+
const item = document.createElement('div');
|
|
840
|
+
item.className = 'result-item';
|
|
841
|
+
|
|
842
|
+
// Format row as string
|
|
843
|
+
const rowStr = JSON.stringify(row, null, 2)
|
|
844
|
+
.replace(/\n/g, ' ')
|
|
845
|
+
.replace(/\s+/g, ' ')
|
|
846
|
+
.substring(0, 100);
|
|
847
|
+
|
|
848
|
+
item.textContent = `${index + 1}. ${rowStr}${rowStr.length === 100 ? '...' : ''}`;
|
|
849
|
+
resultsPreview.appendChild(item);
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
if (data.length > 10) {
|
|
853
|
+
const moreItem = document.createElement('div');
|
|
854
|
+
moreItem.className = 'result-item';
|
|
855
|
+
moreItem.textContent = `... and ${data.length - 10} more rows`;
|
|
856
|
+
moreItem.style.fontStyle = 'italic';
|
|
857
|
+
moreItem.style.color = '#718096';
|
|
858
|
+
resultsPreview.appendChild(moreItem);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Initialize
|
|
863
|
+
updateButtons();
|
|
864
|
+
checkServerAvailability();
|
|
865
|
+
|
|
866
|
+
// Clean up worker on page unload
|
|
867
|
+
window.addEventListener('beforeunload', () => {
|
|
868
|
+
if (worker) {
|
|
869
|
+
worker.terminate();
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
</script>
|
|
873
|
+
</body>
|
|
874
|
+
</html>
|