Skip to content

Commit eb55e26

Browse files
authored
Merge pull request #118 from x0k/file-actions
File actions
2 parents b349ed9 + e904c7f commit eb55e26

File tree

3 files changed

+112
-46
lines changed

3 files changed

+112
-46
lines changed

.changeset/cute-apples-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sjsf/form": patch
3+
---
4+
5+
Refactor async logic in file fields

packages/form/src/fields/extra-fields/file.svelte

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
</script>
88

99
<script lang="ts">
10+
import { untrack } from "svelte";
11+
1012
import { fileToDataURL } from "@/lib/file.js";
11-
import { asyncProxy } from "@/lib/svelte.svelte";
13+
import { abortPrevious, createAction } from "@/lib/action.svelte.js";
1214
import {
1315
makeEventHandlers,
1416
getErrors,
@@ -22,7 +24,11 @@
2224
2325
import "../extra-widgets/file.js";
2426
25-
let { config, value = $bindable(), uiOption }: ComponentProps["fileField"] = $props();
27+
let {
28+
config,
29+
value = $bindable(),
30+
uiOption,
31+
}: ComponentProps["fileField"] = $props();
2632
2733
const ctx = getFormContext();
2834
@@ -34,28 +40,47 @@
3440
validateField(ctx, config, value)
3541
);
3642
37-
const files = asyncProxy(
38-
async (isRegOnly, signal) => {
39-
if (!value || isRegOnly) {
40-
return;
41-
}
43+
let lastValueUpdate: string | undefined;
44+
const toValue = createAction({
45+
combinator: abortPrevious,
46+
async execute(
47+
signal,
48+
files: FileList | undefined
49+
) {
50+
return files === undefined || files.length === 0
51+
? undefined
52+
: fileToDataURL(signal, files[0]!);
53+
},
54+
onSuccess(result: string | undefined) {
55+
lastValueUpdate = result;
56+
value = result;
57+
},
58+
});
59+
60+
let files = $state.raw<FileList>();
61+
const toFiles = createAction({
62+
combinator: abortPrevious,
63+
async execute(signal, value: string | undefined) {
4264
const data = new DataTransfer();
43-
await addFile(ctx, signal, data, value);
65+
if (value !== undefined) {
66+
await addFile(ctx, signal, data, value);
67+
}
4468
return data.files;
4569
},
46-
async (v, signal) => {
47-
if (v === undefined || v.length === 0) {
48-
value = undefined;
49-
return;
50-
}
51-
try {
52-
value = await fileToDataURL(signal, v[0]!);
53-
} catch (e) {
54-
console.error("Failed to read file", e);
55-
}
70+
onSuccess(list: FileList) {
71+
files = list;
5672
},
57-
(v) => v
58-
);
73+
});
74+
75+
$effect(() => {
76+
if (value === lastValueUpdate) {
77+
return;
78+
}
79+
untrack(() => {
80+
toValue.abort();
81+
toFiles.run(value);
82+
});
83+
});
5984
6085
const errors = $derived(getErrors(ctx, config.id));
6186
</script>
@@ -72,9 +97,15 @@
7297
>
7398
<Widget
7499
type="widget"
75-
bind:value={files.value}
76-
processing={files.inputProcessing}
77-
loading={files.outputProcessing}
100+
bind:value={
101+
() => files,
102+
(files) => {
103+
toFiles.abort();
104+
toValue.run(files);
105+
}
106+
}
107+
processing={toValue.isProcessed}
108+
loading={toFiles.isProcessed}
78109
{uiOption}
79110
{handlers}
80111
{errors}

packages/form/src/fields/extra-fields/files.svelte

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
</script>
88

99
<script lang="ts">
10+
import { untrack } from "svelte";
11+
1012
import { fileToDataURL } from "@/lib/file.js";
11-
import { asyncProxy } from "@/lib/svelte.svelte";
13+
import { abortPrevious, createAction } from "@/lib/action.svelte.js";
1214
import {
1315
makeEventHandlers,
1416
getErrors,
@@ -38,30 +40,52 @@
3840
validateField(ctx, config, value)
3941
);
4042
41-
const files = asyncProxy(
42-
async (isRegOnly, signal) => {
43-
if (!value || isRegOnly) {
44-
return;
43+
let lastValueUpdate: string[] | undefined;
44+
const toValue = createAction({
45+
combinator: abortPrevious,
46+
async execute(signal, files: FileList | undefined) {
47+
if (files === undefined) {
48+
return undefined;
4549
}
50+
return Promise.all(
51+
Array.from(files).map((f) => fileToDataURL(signal, f))
52+
);
53+
},
54+
onSuccess(result: string[] | undefined) {
55+
lastValueUpdate = result;
56+
value = result;
57+
},
58+
});
59+
60+
let files = $state.raw<FileList>();
61+
const toFiles = createAction({
62+
combinator: abortPrevious,
63+
async execute(signal, value: string[] | undefined) {
4664
const data = new DataTransfer();
47-
await addFiles(ctx, signal, data, value);
65+
if (value !== undefined) {
66+
await addFiles(ctx, signal, data, value);
67+
}
4868
return data.files;
4969
},
50-
async (v, signal) => {
51-
if (v === undefined || v.length === 0) {
52-
value = [];
53-
return;
54-
}
55-
try {
56-
value = await Promise.all(
57-
Array.from(v).map((f) => fileToDataURL(signal, f))
58-
);
59-
} catch (e) {
60-
console.error("Failed to read file", e);
61-
}
70+
onSuccess(list: FileList) {
71+
files = list;
6272
},
63-
(v) => v
64-
);
73+
});
74+
75+
$effect(() => {
76+
if (
77+
Array.isArray(value) &&
78+
Array.isArray(lastValueUpdate) &&
79+
value.length === lastValueUpdate.length &&
80+
value.every((v, i) => v === lastValueUpdate![i])
81+
) {
82+
return;
83+
}
84+
untrack(() => {
85+
toValue.abort();
86+
toFiles.run(value);
87+
});
88+
});
6589
6690
const errors = $derived(getErrors(ctx, config.id));
6791
</script>
@@ -78,9 +102,15 @@
78102
>
79103
<Widget
80104
type="widget"
81-
bind:value={files.value}
82-
processing={files.inputProcessing}
83-
loading={files.outputProcessing}
105+
bind:value={
106+
() => files,
107+
(files) => {
108+
toFiles.abort();
109+
toValue.run(files);
110+
}
111+
}
112+
processing={toValue.isProcessed}
113+
loading={toFiles.isProcessed}
84114
{uiOption}
85115
{handlers}
86116
{errors}

0 commit comments

Comments
 (0)