@@ -35,6 +35,7 @@ mime_guess = "2.0.4"
 | 
				
			|||||||
zip = { version = "0.6.2", default-features = false }
 | 
					zip = { version = "0.6.2", default-features = false }
 | 
				
			||||||
base64 = "0.13.0"
 | 
					base64 = "0.13.0"
 | 
				
			||||||
image = "0.24.4"
 | 
					image = "0.24.4"
 | 
				
			||||||
 | 
					fast_image_resize = "1.0.0"
 | 
				
			||||||
stretto = "0.7.1"
 | 
					stretto = "0.7.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rustracing = "0.6.0"
 | 
					rustracing = "0.6.0"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
pub mod routes;
 | 
					pub mod routes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
 | 
					    cmp::max,
 | 
				
			||||||
    collections::VecDeque,
 | 
					    collections::VecDeque,
 | 
				
			||||||
    iter::Iterator,
 | 
					    iter::Iterator,
 | 
				
			||||||
    sync::{
 | 
					    sync::{
 | 
				
			||||||
@@ -232,3 +233,15 @@ pub fn generate_path_dto(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    get_path
 | 
					    get_path
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn resize_dimensions(width: u32, height: u32) -> (u32, u32) {
 | 
				
			||||||
 | 
					    let wratio = 300.0 / width as f64;
 | 
				
			||||||
 | 
					    let hratio = 300.0 / height as f64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let ratio = f64::min(wratio, hratio);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        max((width as f64 * ratio).round() as u64, 1) as u32,
 | 
				
			||||||
 | 
					        max((height as f64 * ratio).round() as u64, 1) as u32
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,14 @@ use std::{
 | 
				
			|||||||
    collections::{BTreeSet, HashMap},
 | 
					    collections::{BTreeSet, HashMap},
 | 
				
			||||||
    fs::File,
 | 
					    fs::File,
 | 
				
			||||||
    io::{Read, Write},
 | 
					    io::{Read, Write},
 | 
				
			||||||
 | 
					    num::NonZeroU32,
 | 
				
			||||||
    ops::DerefMut,
 | 
					    ops::DerefMut,
 | 
				
			||||||
    sync::atomic::Ordering
 | 
					    sync::atomic::Ordering
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					use image::DynamicImage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use fast_image_resize as fir;
 | 
				
			||||||
 | 
					use fast_image_resize::PixelType;
 | 
				
			||||||
use rustracing_jaeger::Span;
 | 
					use rustracing_jaeger::Span;
 | 
				
			||||||
use tiny_http::{Request, Response, ResponseBox, StatusCode};
 | 
					use tiny_http::{Request, Response, ResponseBox, StatusCode};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,7 +19,7 @@ use crate::{
 | 
				
			|||||||
    dto,
 | 
					    dto,
 | 
				
			||||||
    header,
 | 
					    header,
 | 
				
			||||||
    metrics,
 | 
					    metrics,
 | 
				
			||||||
    routes::{filters::UserInfo, get_reply, AppError, ChannelReader}
 | 
					    routes::{filters::UserInfo, fs::resize_dimensions, get_reply, AppError, ChannelReader}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn root(_: &Span, _: &mut Request, _: &mut DBConnection, info: UserInfo) -> Result<ResponseBox, AppError> {
 | 
					pub fn root(_: &Span, _: &mut Request, _: &mut DBConnection, info: UserInfo) -> Result<ResponseBox, AppError> {
 | 
				
			||||||
@@ -164,6 +168,7 @@ pub fn upload(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let mut file_size = 0_i64;
 | 
					    let mut file_size = 0_i64;
 | 
				
			||||||
    let file_name = format!("./files/{}", node.id);
 | 
					    let file_name = format!("./files/{}", node.id);
 | 
				
			||||||
 | 
					    let mut file_buf = Vec::<u8>::new();
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        let _span = metrics::span("receive_file", span);
 | 
					        let _span = metrics::span("receive_file", span);
 | 
				
			||||||
        let mut buf = vec![0_u8; 8 * 1024 * 1024];
 | 
					        let mut buf = vec![0_u8; 8 * 1024 * 1024];
 | 
				
			||||||
@@ -176,6 +181,9 @@ pub fn upload(
 | 
				
			|||||||
            if r == 0 {
 | 
					            if r == 0 {
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            if file_size < 20 * 1024 * 1024 {
 | 
				
			||||||
 | 
					                file_buf.write_all(&buf[..r]).unwrap();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            file.write_all(&buf[..r]).unwrap();
 | 
					            file.write_all(&buf[..r]).unwrap();
 | 
				
			||||||
            file_size += r as i64;
 | 
					            file_size += r as i64;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -185,19 +193,73 @@ pub fn upload(
 | 
				
			|||||||
        .with_label_values(&[node.owner_id.to_string().as_str()])
 | 
					        .with_label_values(&[node.owner_id.to_string().as_str()])
 | 
				
			||||||
        .add(file_size - node.size.unwrap_or(0));
 | 
					        .add(file_size - node.size.unwrap_or(0));
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        let _span = metrics::span("generate_preview", span);
 | 
					        let prev_span = metrics::span("generate_preview", span);
 | 
				
			||||||
        node.has_preview = (|| {
 | 
					        node.has_preview = (|| {
 | 
				
			||||||
            if file_size > 20 * 1024 * 1024 {
 | 
					            if file_size > file_buf.len() as i64 {
 | 
				
			||||||
                return None;
 | 
					                return None;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            let mime = mime_guess::from_path(std::path::Path::new(&node.name)).first()?.to_string();
 | 
					            let mime = mime_guess::from_path(std::path::Path::new(&node.name)).first()?.to_string();
 | 
				
			||||||
            let img = image::load(
 | 
					            let img = {
 | 
				
			||||||
                std::io::BufReader::new(File::open(file_name.clone()).unwrap()),
 | 
					                let _span = metrics::span("generate_preview_load", &prev_span);
 | 
				
			||||||
                image::ImageFormat::from_mime_type(mime)?
 | 
					                image::load_from_memory_with_format(
 | 
				
			||||||
            )
 | 
					                    file_buf.as_slice(),
 | 
				
			||||||
            .ok()?;
 | 
					                    image::ImageFormat::from_mime_type(mime)?
 | 
				
			||||||
            let img = img.resize(300, 300, image::imageops::FilterType::Triangle);
 | 
					                )
 | 
				
			||||||
            img.save(std::path::Path::new(&(file_name + "_preview.jpg"))).expect("Failed to save preview image");
 | 
					                .ok()?
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            let img = {
 | 
				
			||||||
 | 
					                let _span = metrics::span("generate_preview_convert", &prev_span);
 | 
				
			||||||
 | 
					                let width = NonZeroU32::try_from(img.width()).unwrap();
 | 
				
			||||||
 | 
					                let height = NonZeroU32::try_from(img.height()).unwrap();
 | 
				
			||||||
 | 
					                match img {
 | 
				
			||||||
 | 
					                    DynamicImage::ImageLuma8(v) => fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageLumaA8(v) => fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x2),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageRgb8(v) => fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x3),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageRgba8(v) => fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x4),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageLuma16(_) => fir::Image::from_vec_u8(width, height, img.to_luma8().into_raw(), fir::PixelType::U8),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageLumaA16(_) => fir::Image::from_vec_u8(width, height, img.to_luma_alpha8().into_raw(), fir::PixelType::U8x2),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageRgb16(_) => fir::Image::from_vec_u8(width, height, img.to_rgb8().into_raw(), fir::PixelType::U8x3),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageRgba16(_) => fir::Image::from_vec_u8(width, height, img.to_rgba8().into_raw(), fir::PixelType::U8x4),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageRgb32F(_) => fir::Image::from_vec_u8(width, height, img.to_rgb8().into_raw(), fir::PixelType::U8x3),
 | 
				
			||||||
 | 
					                    DynamicImage::ImageRgba32F(_) => fir::Image::from_vec_u8(width, height, img.to_rgba8().into_raw(), fir::PixelType::U8x4),
 | 
				
			||||||
 | 
					                    _ => fir::Image::from_vec_u8(width, height, img.to_rgba8().into_raw(), fir::PixelType::U8x4)
 | 
				
			||||||
 | 
					                }.expect("Failed to convert preview image")
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            let img = {
 | 
				
			||||||
 | 
					                let _span = metrics::span("generate_preview_resize", &prev_span);
 | 
				
			||||||
 | 
					                let new_dim = resize_dimensions(img.width().get(), img.height().get());
 | 
				
			||||||
 | 
					                let mut dst = fir::Image::new(
 | 
				
			||||||
 | 
					                    NonZeroU32::try_from(new_dim.0).unwrap(),
 | 
				
			||||||
 | 
					                    NonZeroU32::try_from(new_dim.1).unwrap(),
 | 
				
			||||||
 | 
					                    img.pixel_type()
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                fir::Resizer::new(fir::ResizeAlg::SuperSampling(
 | 
				
			||||||
 | 
					                    fir::FilterType::Hamming,
 | 
				
			||||||
 | 
					                    2
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					                .resize(&img.view(), &mut dst.view_mut())
 | 
				
			||||||
 | 
					                .expect("Failed to resize preview image");
 | 
				
			||||||
 | 
					                dst
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            let _span = metrics::span("generate_preview_save", &prev_span);
 | 
				
			||||||
 | 
					            let mut file = std::io::BufWriter::new(
 | 
				
			||||||
 | 
					                File::create(std::path::Path::new(&(file_name + "_preview.jpg")))
 | 
				
			||||||
 | 
					                    .expect("Failed to open preview image file")
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            image::codecs::jpeg::JpegEncoder::new(&mut file)
 | 
				
			||||||
 | 
					                .encode(
 | 
				
			||||||
 | 
					                    img.buffer(),
 | 
				
			||||||
 | 
					                    img.width().get(),
 | 
				
			||||||
 | 
					                    img.height().get(),
 | 
				
			||||||
 | 
					                    match img.pixel_type() {
 | 
				
			||||||
 | 
					                        PixelType::U8 => image::ColorType::L8,
 | 
				
			||||||
 | 
					                        PixelType::U8x2 => image::ColorType::La8,
 | 
				
			||||||
 | 
					                        PixelType::U8x3 => image::ColorType::Rgb8,
 | 
				
			||||||
 | 
					                        PixelType::U8x4 => image::ColorType::Rgba8,
 | 
				
			||||||
 | 
					                        _ => unreachable!()
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .expect("Failed to save preview image");
 | 
				
			||||||
            Some(())
 | 
					            Some(())
 | 
				
			||||||
        })()
 | 
					        })()
 | 
				
			||||||
        .is_some();
 | 
					        .is_some();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,28 +84,31 @@ export async function upload_file(
 | 
				
			|||||||
	token: string,
 | 
						token: string,
 | 
				
			||||||
	file: UploadFile,
 | 
						file: UploadFile,
 | 
				
			||||||
	onProgress: (progressEvent: ProgressEvent) => void
 | 
						onProgress: (progressEvent: ProgressEvent) => void
 | 
				
			||||||
): Promise<Responses.Success | Responses.Error> {
 | 
					): Promise<[Responses.Success | Responses.Error, boolean]> {
 | 
				
			||||||
	const node = await create_file(token, file.parent, file.file.name);
 | 
						const node = await create_file(token, file.parent, file.file.name);
 | 
				
			||||||
	if (isErrorResponse(node)) return node;
 | 
						if (isErrorResponse(node)) return [node, false];
 | 
				
			||||||
	if ('exists' in node && !node.isFile)
 | 
						if ('exists' in node && !node.isFile)
 | 
				
			||||||
		return { statusCode: 400, message: 'File exists as folder' };
 | 
							return [{ statusCode: 400, message: 'File exists as folder' }, false];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return axios
 | 
						return [
 | 
				
			||||||
		.post(`/api/fs/upload/${node.id}`, file.file, {
 | 
							await axios
 | 
				
			||||||
			headers: {
 | 
								.post(`/api/fs/upload/${node.id}`, file.file, {
 | 
				
			||||||
				Authorization: 'Bearer ' + token,
 | 
									headers: {
 | 
				
			||||||
				'Content-type': 'multipart/form-data'
 | 
										Authorization: 'Bearer ' + token,
 | 
				
			||||||
			},
 | 
										'Content-type': 'multipart/form-data'
 | 
				
			||||||
			onUploadProgress: onProgress
 | 
									},
 | 
				
			||||||
		})
 | 
									onUploadProgress: onProgress
 | 
				
			||||||
		.then((res) => {
 | 
								})
 | 
				
			||||||
			console.log(res);
 | 
								.then((res) => {
 | 
				
			||||||
			return res.data;
 | 
									console.log(res);
 | 
				
			||||||
		})
 | 
									return res.data;
 | 
				
			||||||
		.catch((err) => {
 | 
								})
 | 
				
			||||||
			console.log(err);
 | 
								.catch((err) => {
 | 
				
			||||||
			return err.response.data;
 | 
									console.log(err);
 | 
				
			||||||
		});
 | 
									return err.response.data;
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							'exists' in node
 | 
				
			||||||
 | 
						];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function download_file(token: string, id: number) {
 | 
					export function download_file(token: string, id: number) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,7 +48,7 @@ async function startDelete() {
 | 
				
			|||||||
			await resp.body.pipeTo(logWriter);
 | 
								await resp.body.pipeTo(logWriter);
 | 
				
			||||||
		} catch (err) {
 | 
							} catch (err) {
 | 
				
			||||||
			log.value += `Error: ${err}\n`;
 | 
								log.value += `Error: ${err}\n`;
 | 
				
			||||||
			logInst.value?.scrollTo({ position: 'top' });
 | 
								logInst.value?.scrollTo({ position: 'bottom', slient: true });
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -115,6 +115,8 @@ watch(
 | 
				
			|||||||
					v-else-if="fileType === fileTypes.IMAGE && src !== ''"
 | 
										v-else-if="fileType === fileTypes.IMAGE && src !== ''"
 | 
				
			||||||
					:src="src"
 | 
										:src="src"
 | 
				
			||||||
					:alt="node.name"
 | 
										:alt="node.name"
 | 
				
			||||||
 | 
										:img-props="{ style: 'max-width: 80vw; max-height: 70vh;' }"
 | 
				
			||||||
 | 
										object-fit="contain"
 | 
				
			||||||
				/>
 | 
									/>
 | 
				
			||||||
				<iframe
 | 
									<iframe
 | 
				
			||||||
					v-else-if="fileType === fileTypes.IFRAME && src !== ''"
 | 
										v-else-if="fileType === fileTypes.IFRAME && src !== ''"
 | 
				
			||||||
@@ -137,4 +139,4 @@ watch(
 | 
				
			|||||||
	</n-grid>
 | 
						</n-grid>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped></style>
 | 
					<style scoped lang="scss"></style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import filesize from 'filesize';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
	file: UploadFile;
 | 
						file: UploadFile;
 | 
				
			||||||
 | 
						abort: boolean;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const progress = ref(0);
 | 
					const progress = ref(0);
 | 
				
			||||||
@@ -15,13 +16,26 @@ const percentage = ref(0);
 | 
				
			|||||||
const err = ref('');
 | 
					const err = ref('');
 | 
				
			||||||
const status = ref<Status>('info');
 | 
					const status = ref<Status>('info');
 | 
				
			||||||
const shown = ref(true);
 | 
					const shown = ref(true);
 | 
				
			||||||
 | 
					const existed = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function startUpload(token: string, done: () => void) {
 | 
					async function startUpload(token: string, done: () => void) {
 | 
				
			||||||
	const resp = await FS.upload_file(token, props.file, (e) => {
 | 
						let sendDone = false;
 | 
				
			||||||
 | 
						if (props.abort) {
 | 
				
			||||||
 | 
							done();
 | 
				
			||||||
 | 
							shown.value = false;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						const resp_tuple = await FS.upload_file(token, props.file, (e) => {
 | 
				
			||||||
		progress.value = e.loaded;
 | 
							progress.value = e.loaded;
 | 
				
			||||||
		percentage.value = (e.loaded / e.total) * 100;
 | 
							percentage.value = (e.loaded / e.total) * 100;
 | 
				
			||||||
		if (e.loaded == e.total) done();
 | 
							if (e.loaded == e.total) {
 | 
				
			||||||
 | 
								sendDone = true;
 | 
				
			||||||
 | 
								done();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
						const resp = resp_tuple[0];
 | 
				
			||||||
 | 
						existed.value = resp_tuple[1];
 | 
				
			||||||
 | 
						if (!sendDone) done();
 | 
				
			||||||
	percentage.value = 100;
 | 
						percentage.value = 100;
 | 
				
			||||||
	if (isErrorResponse(resp)) {
 | 
						if (isErrorResponse(resp)) {
 | 
				
			||||||
		err.value = resp.message ?? 'Error';
 | 
							err.value = resp.message ?? 'Error';
 | 
				
			||||||
@@ -60,6 +74,12 @@ defineExpose({
 | 
				
			|||||||
			<div v-else-if="err !== ''">
 | 
								<div v-else-if="err !== ''">
 | 
				
			||||||
				{{ file.fullName }} - Error: {{ err }}
 | 
									{{ file.fullName }} - Error: {{ err }}
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
 | 
								<div v-else-if="existed">
 | 
				
			||||||
 | 
									{{ file.fullName }} - Old file overridden
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div v-else-if="status !== 'success'">
 | 
				
			||||||
 | 
									{{ file.fullName }} - Processing...
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
			<div v-else>{{ file.fullName }} - Completed</div>
 | 
								<div v-else>{{ file.fullName }} - Completed</div>
 | 
				
			||||||
			<n-progress
 | 
								<n-progress
 | 
				
			||||||
				type="line"
 | 
									type="line"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,20 +3,29 @@ import type { TokenInjectType, UploadFile } from '@/api';
 | 
				
			|||||||
import { ref, inject } from 'vue';
 | 
					import { ref, inject } from 'vue';
 | 
				
			||||||
import { update_token } from '@/api';
 | 
					import { update_token } from '@/api';
 | 
				
			||||||
import UploadEntry from '@/components/UploadDialog/UploadEntry.vue';
 | 
					import UploadEntry from '@/components/UploadDialog/UploadEntry.vue';
 | 
				
			||||||
import { NCard } from 'naive-ui';
 | 
					import { NCard, NButton } from 'naive-ui';
 | 
				
			||||||
 | 
					import semaphore from 'semaphore';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
					const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const entries = ref<typeof UploadEntry[]>([]);
 | 
					const entries = ref<typeof UploadEntry[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const abortUpload = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function startUpload() {
 | 
					async function startUpload() {
 | 
				
			||||||
	const token = await update_token(jwt);
 | 
						const token = await update_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						if (!token) return;
 | 
				
			||||||
	const ents: typeof UploadEntry[] = entries.value;
 | 
						const ents: typeof UploadEntry[] = entries.value;
 | 
				
			||||||
	const allProms: Promise<void>[] = [];
 | 
						const allProms: Promise<void>[] = [];
 | 
				
			||||||
 | 
						const uploadSem = semaphore(5);
 | 
				
			||||||
	for (const entry of ents) {
 | 
						for (const entry of ents) {
 | 
				
			||||||
		await new Promise<void>((resolve) =>
 | 
							allProms.push(
 | 
				
			||||||
			allProms.push(entry.startUpload(token, resolve))
 | 
								new Promise<void>((resolve) => {
 | 
				
			||||||
 | 
									uploadSem.take(async () => {
 | 
				
			||||||
 | 
										await entry.startUpload(token, () => uploadSem.leave());
 | 
				
			||||||
 | 
										resolve();
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	await Promise.all(allProms);
 | 
						await Promise.all(allProms);
 | 
				
			||||||
@@ -32,11 +41,13 @@ defineProps<{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
	<n-card title="Uploading files" style="margin: 20px">
 | 
						<n-card title="Uploading files" style="margin: 20px">
 | 
				
			||||||
 | 
							<n-button type="error" @click="abortUpload = true">Abort</n-button>
 | 
				
			||||||
		<UploadEntry
 | 
							<UploadEntry
 | 
				
			||||||
			v-for="f in files"
 | 
								v-for="f in files"
 | 
				
			||||||
			:key="f.file.name"
 | 
								:key="f.file.name"
 | 
				
			||||||
			ref="entries"
 | 
								ref="entries"
 | 
				
			||||||
			:file="f"
 | 
								:file="f"
 | 
				
			||||||
 | 
								:abort="abortUpload"
 | 
				
			||||||
		/>
 | 
							/>
 | 
				
			||||||
	</n-card>
 | 
						</n-card>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user