Skip to content

Commit 6e0ea4a

Browse files
committed
feat: add uploading support
1 parent 08f7782 commit 6e0ea4a

File tree

5 files changed

+104
-9
lines changed

5 files changed

+104
-9
lines changed

frontend/src/api/api.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { checkPwd } from "../utils/useAuthorization";
2-
import { getOrMock } from "./basic";
2+
import { getOrMock, post } from "./basic";
33
import { listPicturesOk, partitionsOk } from "./mocks";
44
import { ListResponse, Resolve } from "./models";
55

@@ -46,3 +46,11 @@ export function auth(password: string) {
4646
}
4747
);
4848
}
49+
50+
export function upload(image: File, partition: string) {
51+
const param = new FormData();
52+
param.append("file", image, "new-image");
53+
return post<Resolve>(`/${partition}/upload`, null, param, Number.MAX_VALUE, {
54+
"Content-Type": "multipart/form-data",
55+
});
56+
}

frontend/src/api/basic.ts

+31
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,34 @@ export function getOrMock<T>(
6060
}
6161
});
6262
}
63+
64+
export function post<T>(
65+
path: string,
66+
query?: QueryParams,
67+
data?: any,
68+
timeout?: number,
69+
headers?: Record<string, string>
70+
): Promise<T> {
71+
return new Promise((resolve, reject) => {
72+
myAxios({
73+
url: path,
74+
method: "post",
75+
params: query,
76+
data,
77+
headers,
78+
timeout,
79+
})
80+
.then((res) => {
81+
if (res.status == 403) {
82+
// Re-login
83+
refresh();
84+
return;
85+
}
86+
const data = (res.data as any).data;
87+
resolve(data);
88+
})
89+
.catch((e) => {
90+
reject(e);
91+
});
92+
});
93+
}

frontend/src/view/Main.tsx

+42-5
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ import {
55
createSignal,
66
ErrorBoundary,
77
For,
8+
Show,
89
Suspense,
9-
untrack,
1010
} from "solid-js";
1111
import { createStore, SetStoreFunction } from "solid-js/store";
12-
import { listPartitions, listPictures } from "../api/api";
12+
import { listPartitions, listPictures, upload } from "../api/api";
1313
import Loading from "../components/Loading";
1414
import Pagination from "../components/Pagination";
1515
import PictureCard from "../components/PictureCard";
1616
import Placeholder from "../components/Placeholder";
17+
import Button from "../components/Button";
1718

1819
const PartitionSelector = (props: {
1920
current: string;
@@ -24,7 +25,7 @@ const PartitionSelector = (props: {
2425
}) => {
2526
const [partitions] = createResource(listPartitions);
2627
return (
27-
<div class="relative pt-4">
28+
<div class="relative">
2829
<Suspense fallback={<Loading />}>
2930
<select
3031
onChange={(event) => {
@@ -50,6 +51,33 @@ focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none"
5051
);
5152
};
5253

54+
const Uploader = (props: {partition: string}) => {
55+
const [image, setImage] = createSignal<File>();
56+
const fetcher = (file: File) => upload(file, props.partition);
57+
58+
const [data] = createResource(image, fetcher);
59+
60+
return <form class="flex items-start gap-4" onSubmit={async (event) => {
61+
event.preventDefault();
62+
const file = event.target["image"].files[0];
63+
setImage(file);
64+
}}>
65+
<div class="flex flex-col items-start gap-4">
66+
<input name="image" type="file" />
67+
<Button type="submit" disabled={data.loading}>Upload</Button>
68+
</div>
69+
70+
<Show when={data()}>
71+
<div>
72+
<h3>Success!</h3>
73+
<ul>
74+
<For each={Array.from(Object.entries(data()))}>{([key, value]) => <li>{key}: <a href={value["url"]}>{value["url"]}</a></li>}</For>
75+
</ul>
76+
</div>
77+
</Show>
78+
</form>
79+
}
80+
5381
export default function Main() {
5482
const navigate = useNavigate();
5583
const param = useParams();
@@ -71,8 +99,17 @@ export default function Main() {
7199
return <Placeholder text={e.toString()} />
72100
}}>
73101
<Suspense fallback={<Loading />}>
74-
<section class="flex justify-end min-h-10">
75-
<PartitionSelector current={store.partition} setter={setStore} />
102+
<section class="flex justify-between items-end min-h-10 gap-4 pt-4">
103+
<div>
104+
<ErrorBoundary fallback={(e) => <p>{`${e}`}</p>}>
105+
<Suspense fallback={<Loading />}>
106+
<Uploader partition={store.partition} />
107+
</Suspense>
108+
</ErrorBoundary>
109+
</div>
110+
<div>
111+
<PartitionSelector current={store.partition} setter={setStore} />
112+
</div>
76113
</section>
77114
<section class="mt-4 min-h-96">
78115
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-6 ">

frontend/uno.config.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// uno.config.ts
2+
import {
3+
defineConfig,
4+
presetUno,
5+
presetWind } from 'unocss'
6+
7+
export default defineConfig({
8+
shortcuts: [
9+
// ...
10+
],
11+
theme: {
12+
colors: {
13+
// ...
14+
}
15+
},
16+
presets: [
17+
presetUno(),
18+
presetWind(),
19+
],
20+
})

frontend/vite.config.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { defineConfig } from "vite";
22
import solidPlugin from "vite-plugin-solid";
33
import UnoCSS from "unocss/vite";
4-
import presetWind from "@unocss/preset-wind";
54
import TOML from "@ltd/j-toml";
65
import fs from "fs";
76

@@ -12,7 +11,7 @@ export default defineConfig({
1211
plugins: [
1312
solidPlugin(),
1413
UnoCSS({
15-
presets: [presetWind()],
14+
configFile: "./uno.config.ts"
1615
}),
1716
],
1817

@@ -24,7 +23,7 @@ export default defineConfig({
2423
server: {
2524
port: 3000,
2625
proxy: {
27-
"/api/pictures": "http://localhost:8080",
26+
"/api/pictures": "http://localhost:7709",
2827
},
2928
},
3029
build: {

0 commit comments

Comments
 (0)