Skip to content

Commit dd0e713

Browse files
committed
New form for asset creation, some form components fixed
1 parent 178379a commit dd0e713

6 files changed

Lines changed: 183 additions & 191 deletions

File tree

Site/src/components/forms/Input.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
label?: string
2323
help?: string
2424
after?: string
25-
type?: HTMLInputTypeAttribute
25+
// type?: HTMLInputTypeAttribute
26+
type?: "text" | "number" | "color" | "date" | "checkbox" | "file" | "password"
2627
inline?: boolean
2728
column?: boolean
2829
disabled?: boolean

Site/src/components/forms/Select.svelte

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
<script lang="ts">
1+
<script lang="ts" generics="T extends RemoteFormInput">
22
// could make forms based on a js object or something instead of components but concerns with forms that require more interactivity/customisation
33
44
import { Select } from "melt/builders"
55
import NoScript from "$components/NoScript.svelte"
66
import YesScript from "$components/YesScript.svelte"
7+
import type { ClientForm, ExtractId } from "$lib/validate"
78
89
const {
910
name,
@@ -17,27 +18,24 @@
1718
formData,
1819
...rest
1920
}: {
20-
name: string
21+
name: ExtractId<T>
2122
label?: string
2223
help?: string
2324
disabled?: boolean
2425
// multiple?: boolean
2526
options: readonly string[]
2627
selected?: string
27-
formData: import("$lib/validate").SuperForm<any>
28+
formData: ClientForm<T>
2829
} = $props()
2930
30-
let { form, errors, constraints } = $derived(formData)
31+
let issues = $derived(formData.fields[name]?.issues() || [])
3132
3233
let trigger = $state<HTMLElement>()
3334
// let dropdown = $state<HTMLElement>()
3435
3536
let select = $derived(
3637
new Select<string>({
37-
value: selected,
38-
onValueChange: val => {
39-
$form[name] = val
40-
}
38+
value: selected
4139
4240
// floatingConfig: {
4341
// onCompute: a => {
@@ -57,12 +55,11 @@
5755
<NoScript>
5856
<!-- fallback to standard select (i don't *think* we need to bind here, and sometimes bugs) -->
5957
<select
58+
{...formData.fields[name].as("text")}
6059
{disabled}
6160
{...rest}
62-
{...$constraints[name]}
63-
{name}
64-
id={name}
65-
class={{ "is-invalid": $errors[name] }}>
61+
name={name.toString()}
62+
id={name.toString()}>
6663
{#each options as opt}
6764
<option
6865
value={opt}
@@ -73,7 +70,12 @@
7370
</select>
7471
</NoScript>
7572
<YesScript>
76-
<input type="hidden" {name} value={select.value || options[0]} />
73+
<input
74+
type="hidden"
75+
{...formData.fields[name].as("text")}
76+
name={name.toString()}
77+
id={name.toString()}
78+
value={select.value || options[0]} />
7779

7880
<button
7981
{...select.trigger}
@@ -101,8 +103,10 @@
101103
</small>
102104
{/if}
103105

104-
<small class="pb-4 text-red-500">
105-
{$errors[name] || ""}
106-
</small>
106+
{#each issues as issue}
107+
<small class="block text-red-500">
108+
{issue.message}
109+
</small>
110+
{/each}
107111
</div>
108112
</div>

Site/src/components/forms/SubInput.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
// Imported into Input.svelte to prevent code duplication
1616
name: ExtractId<T>
1717
disabled: boolean
18-
type: HTMLInputTypeAttribute
18+
// type: HTMLInputTypeAttribute
19+
type: "text" | "number" | "color" | "date" | "checkbox" | "file" | "password"
1920
formData: ClientForm<T>
2021
} & HTMLInputAttributes = $props()
2122
</script>
@@ -30,7 +31,7 @@
3031
{disabled} />
3132
{:else}
3233
<input
33-
{...formData.fields[name].as("text")}
34+
{...formData.fields[name].as(type)}
3435
{...rest}
3536
{name}
3637
id={name}
Lines changed: 1 addition & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,9 @@
1-
import fs from "node:fs"
2-
import { redirect } from "@sveltejs/kit"
3-
import { type } from "arktype"
4-
import { typeToNumber } from "$lib/assetTypes"
5-
import { authorise } from "$lib/server/auth"
6-
import { createAsset, getAssetPrice } from "$lib/server/economy"
7-
import formError from "$lib/server/formError"
8-
import { randomAssetId } from "$lib/server/id"
9-
import {
10-
clothingAsset,
11-
imageAsset,
12-
thumbnail,
13-
tShirt,
14-
tShirtThumbnail,
15-
} from "$lib/server/imageAsset"
16-
import ratelimit from "$lib/server/ratelimit"
17-
import requestRender from "$lib/server/requestRender"
18-
import { db, Record } from "$lib/server/surreal"
19-
import { arktype, superValidate } from "$lib/server/validate"
20-
import { graphicAsset } from "$lib/server/xmlAsset"
21-
import { encode } from "$lib/urlName"
22-
import assetTypes from "./assetTypes"
23-
import createAssetQuery from "./createAsset.surql"
24-
25-
const schema = type({
26-
// Object.keys(assets) doesn't work
27-
type: type.enumerated(...assetTypes).configure({
28-
problem: "must be a valid asset type",
29-
}),
30-
name: "3 <= string <= 50",
31-
description: "(0 <= string <= 1000) | undefined",
32-
price: "0 <= number.integer <= 999",
33-
asset: "File",
34-
})
35-
36-
// type assetType = (typeof assetTypes)[number]
1+
import { getAssetPrice } from "$lib/server/economy"
372

383
export async function load({ url }) {
394
const price = getAssetPrice()
40-
// const urlType = url.searchParams.get("type") as assetType | null
415
return {
42-
form: await superValidate(
43-
arktype(
44-
schema
45-
// probably not needed
46-
47-
// {
48-
// defaults: {
49-
// // ...(urlType &&
50-
// // assetTypes.includes(urlType) && { type: urlType }),
51-
// type:
52-
// urlType && assetTypes.includes(urlType)
53-
// ? urlType
54-
// : assetTypes[0],
55-
// name: "",
56-
// description: "",
57-
// price: 0,
58-
// asset: null as unknown as File,
59-
// },
60-
// }
61-
)
62-
),
636
assetType: url.searchParams.get("asset"),
647
price,
658
}
669
}
67-
68-
export const actions: import("./$types").Actions = {}
69-
actions.default = async ({ fetch: f, locals, request, getClientAddress }) => {
70-
const { user } = await authorise(locals)
71-
const form = await superValidate(request, arktype(schema))
72-
if (!form.valid) return formError(form)
73-
74-
const { type, name, description, price, asset } = form.data
75-
form.data.asset = null as unknown as File // make sure to return as a POJO
76-
77-
if (asset.size === 0)
78-
return formError(form, ["asset"], ["You must upload an asset"])
79-
if (asset.size > 20e6)
80-
return formError(
81-
form,
82-
["asset"],
83-
["Asset must be less than 20MB in size"]
84-
)
85-
86-
if (user.permissionLevel < 3) {
87-
const limit = ratelimit(form, "assetCreation", getClientAddress, 30)
88-
if (limit) return limit
89-
}
90-
91-
if (!fs.existsSync("../data/assets")) fs.mkdirSync("../data/assets")
92-
if (!fs.existsSync("../data/thumbnails")) fs.mkdirSync("../data/thumbnails")
93-
94-
let saveImages: ((id: number) => Promise<number> | Promise<void>)[] = []
95-
96-
try {
97-
switch (type) {
98-
case "T-Shirt":
99-
saveImages = await Promise.all([
100-
tShirt(asset),
101-
tShirtThumbnail(await asset.arrayBuffer()),
102-
])
103-
break
104-
105-
case "Shirt":
106-
case "Pants":
107-
saveImages[0] = await clothingAsset(asset)
108-
saveImages[1] = (id: number) => requestRender(f, "Clothing", id)
109-
break
110-
111-
case "Decal":
112-
saveImages = await Promise.all([
113-
imageAsset(asset),
114-
thumbnail(await asset.arrayBuffer()),
115-
])
116-
break
117-
118-
case "Face":
119-
if (user.permissionLevel < 3)
120-
return formError(
121-
form,
122-
["type"],
123-
[
124-
"You do not have permission to upload this type of asset",
125-
]
126-
)
127-
saveImages = await Promise.all([
128-
imageAsset(asset),
129-
thumbnail(await asset.arrayBuffer()),
130-
])
131-
break
132-
default:
133-
}
134-
} catch (e) {
135-
console.log(e)
136-
return formError(form, ["asset"], ["Asset failed to upload"])
137-
}
138-
139-
const slug = encode(name)
140-
const imageAssetId = randomAssetId()
141-
const id = randomAssetId()
142-
143-
if (user.permissionLevel < 3) {
144-
const created = await createAsset(f, user.id, id, name, slug)
145-
if (!created.ok) return formError(form, ["other"], [created.msg])
146-
}
147-
148-
await db.query(createAssetQuery, {
149-
name,
150-
assetType: typeToNumber[type],
151-
price,
152-
description,
153-
user: Record("user", user.id),
154-
imageAssetId,
155-
id,
156-
visibility: user.permissionLevel < 3 ? "Pending" : "Visible",
157-
})
158-
159-
await graphicAsset(type, imageAssetId, id)
160-
161-
try {
162-
await Promise.all([saveImages[0](imageAssetId), saveImages[1](id)])
163-
} catch (e) {
164-
console.log("Rendering images failed!")
165-
console.error(e)
166-
}
167-
168-
redirect(302, `/catalog/${id}/${name}`)
169-
}

Site/src/routes/(main)/develop/create/+page.svelte

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,24 @@
55
import Textarea from "$components/forms/Textarea.svelte"
66
import Head from "$components/Head.svelte"
77
import beautifyCurrency from "$lib/beautifyCurrency"
8-
import { superForm } from "$lib/validate"
8+
import type { ClientForm } from "$lib/validate"
99
import assetTypes from "./assetTypes"
10+
import { formData } from "./create.remote.js"
11+
12+
type CreateFormInput = {
13+
type: "T-Shirt" | "Shirt" | "Pants" | "Decal" | "Face"
14+
name: string
15+
description: string
16+
price: number
17+
asset: File
18+
}
19+
20+
const remoteForm = formData as unknown as ClientForm<CreateFormInput>
1021
1122
const { data } = $props()
1223
1324
let { user } = $derived(data)
1425
15-
let formData = $derived(superForm(data.form))
16-
export const snapshot = formData
17-
1826
let [, c1, c2] = $derived(beautifyCurrency(data.price))
1927
2028
let currency = $derived(
@@ -33,31 +41,35 @@
3341
</div>
3442

3543
<Form
36-
{formData}
44+
formData={remoteForm}
3745
nopad
3846
enctype="multipart/form-data"
3947
submit="Create{user.permissionLevel < 3 ? currency : ''}"
4048
class="ctnr pt-8 max-w-200 light-text">
4149
<Select
42-
{formData}
50+
formData={remoteForm}
4351
options={assetTypes}
44-
selected={data.assetType}
52+
selected={data.assetType ?? undefined}
4553
name="type"
4654
label="Asset type" />
4755
<Input
48-
{formData}
56+
formData={remoteForm}
4957
name="name"
5058
label="Asset name"
5159
placeholder="Make sure to make it accurate" />
5260
<Textarea
53-
{formData}
61+
formData={remoteForm}
5462
name="description"
5563
label="Asset description"
5664
placeholder="Up to 1000 characters" />
57-
<Input {formData} name="price" label="Asset price" type="number" />
65+
<Input
66+
formData={remoteForm}
67+
name="price"
68+
label="Asset price"
69+
type="number" />
5870
{#if data.assetType !== "Hat"}
5971
<Input
60-
{formData}
72+
formData={remoteForm}
6173
type="file"
6274
name="asset"
6375
label="Asset"

0 commit comments

Comments
 (0)