uploading files and a lot more
This commit is contained in:
33
ui/components/Breadcrumbs.vue
Normal file
33
ui/components/Breadcrumbs.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup lang="js">
|
||||
const props = defineProps({
|
||||
path: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const crumbs = computed(() => {
|
||||
const paths = props.path.split("/").filter(x => !!x);
|
||||
return paths.map((crumb, index) => {
|
||||
return {
|
||||
name: crumb,
|
||||
link: "/" + paths.slice(0, index + 1).join("/")
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-row">
|
||||
<span v-for="(crumb, index) in crumbs" class="flex items-center">
|
||||
<svg v-if="index != 0" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
||||
class="text-subtle mx-1">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="m9 6l6 6l-6 6" />
|
||||
</svg>
|
||||
<a class="hover:text-text" :class="index === crumbs.length - 1 ? 'text-foam' : 'text-subtle'"
|
||||
:href="crumb.link">{{
|
||||
crumb.name }}</a>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
116
ui/components/FileNav.vue
Normal file
116
ui/components/FileNav.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script lang="ts" setup>
|
||||
import { useUser } from '~/composables/useUser'
|
||||
const { getUser } = useUser()
|
||||
|
||||
const props = defineProps({
|
||||
usageBytes: Number
|
||||
})
|
||||
|
||||
const user = await getUser()
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
let capacityBytes = ref(user.plan.max_storage);
|
||||
|
||||
const radius = 13;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
||||
const percentage = computed(() => {
|
||||
return (props.usageBytes / capacityBytes.value);
|
||||
});
|
||||
|
||||
console.log(percentage.value, props.usageBytes, capacityBytes.value)
|
||||
const offset = computed(() => {
|
||||
return circumference - percentage.value * circumference;
|
||||
});
|
||||
const usage = computed(() => {
|
||||
return formatBytes(props.usageBytes)
|
||||
});
|
||||
const capacity = computed(() => {
|
||||
return formatBytes(capacityBytes.value)
|
||||
});
|
||||
|
||||
if (props.usageBytes > capacityBytes.value) {
|
||||
console.log("SCAN SCAN SCAM SCAM")
|
||||
}
|
||||
|
||||
const isAllFilesActive = computed(() => route.path === '/home');
|
||||
|
||||
const isInFolder = computed(() => route.path.startsWith('/home/') && route.path !== '/home');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside class="h-screen flex flex-col w-56 pt-3 bg-surface border-r">
|
||||
<div class="pl-9 h-14 flex items-center">
|
||||
<h2>Home</h2>
|
||||
</div>
|
||||
<div class="p-4 flex-grow">
|
||||
<ul class="flex flex-col gap-y-2">
|
||||
<li>
|
||||
<NuxtLink to="/home"
|
||||
class="flex py-1.5 px-4 rounded-lg transition-bg duration-300 hover:bg-muted/10"
|
||||
:class="{ 'bg-muted/10': isAllFilesActive }">
|
||||
<svg class="m-0.5 mr-2" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 256 256">
|
||||
<g fill="currentColor">
|
||||
<path d="M208 72v112a8 8 0 0 1-8 8h-24v-88l-40-40H80V40a8 8 0 0 1 8-8h80Z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="m213.66 66.34l-40-40A8 8 0 0 0 168 24H88a16 16 0 0 0-16 16v16H56a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h112a16 16 0 0 0 16-16v-16h16a16 16 0 0 0 16-16V72a8 8 0 0 0-2.34-5.66ZM168 216H56V72h76.69L168 107.31V216Zm32-32h-16v-80a8 8 0 0 0-2.34-5.66l-40-40A8 8 0 0 0 136 56H88V40h76.69L200 75.31Zm-56-32a8 8 0 0 1-8 8H88a8 8 0 0 1 0-16h48a8 8 0 0 1 8 8Zm0 32a8 8 0 0 1-8 8H88a8 8 0 0 1 0-16h48a8 8 0 0 1 8 8Z" />
|
||||
</g>
|
||||
</svg>
|
||||
All files
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="flex flex-col">
|
||||
<NuxtLink to="/home/name"
|
||||
class="flex py-1.5 px-4 rounded-lg transition-bg duration-300 hover:bg-muted/10"
|
||||
:class="{ 'bg-muted/10': isInFolder }">
|
||||
<svg v-if="isInFolder" class="m-0.5 mr-2" xmlns="http://www.w3.org/2000/svg" width="20"
|
||||
height="20" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m5 19l2.757-7.351A1 1 0 0 1 8.693 11H21a1 1 0 0 1 .986 1.164l-.996 5.211A2 2 0 0 1 19.026 19za2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h4l3 3h7a2 2 0 0 1 2 2v2" />
|
||||
</svg>
|
||||
<svg v-else class="m-0.5 mr-2" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2" />
|
||||
</svg>
|
||||
Folders
|
||||
</NuxtLink>
|
||||
<!-- <ul class="flex flex-col gap-y-2 w-4/5 mt-2 ml-auto">
|
||||
<li>
|
||||
<a href="/folder/thing" class="flex py-1.5 px-4 rounded-lg transition-bg duration-300"
|
||||
:class="isActive('/folder/thing') ? 'bg-muted/10' : 'hover:bg-muted/10'">
|
||||
<svg class="m-0.5 mr-2" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 256 256">
|
||||
<g fill="currentColor">
|
||||
<path d="M208 72v112a8 8 0 0 1-8 8h-24v-88l-40-40H80V40a8 8 0 0 1 8-8h80Z"
|
||||
opacity=".2" />
|
||||
<path
|
||||
d="m213.66 66.34l-40-40A8 8 0 0 0 168 24H88a16 16 0 0 0-16 16v16H56a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h112a16 16 0 0 0 16-16v-16h16a16 16 0 0 0 16-16V72a8 8 0 0 0-2.34-5.66ZM168 216H56V72h76.69L168 107.31V216Zm32-32h-16v-80a8 8 0 0 0-2.34-5.66l-40-40A8 8 0 0 0 136 56H88V40h76.69L200 75.31Zm-56-32a8 8 0 0 1-8 8H88a8 8 0 0 1 0-16h48a8 8 0 0 1 8 8Zm0 32a8 8 0 0 1-8 8H88a8 8 0 0 1 0-16h48a8 8 0 0 1 8 8Z" />
|
||||
</g>
|
||||
</svg>
|
||||
All files
|
||||
</a>
|
||||
</li>
|
||||
</ul> -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="m-2 w-[calc(100%-16px)]">
|
||||
<div class="p-3 bg-overlay border rounded-lg flex items-end">
|
||||
<svg width="32" height="32" class="-rotate-90 mr-2" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Background Track -->
|
||||
<circle class="stroke-foam/20" cx="16" cy="16" :r="radius" fill="none" stroke-width="3" />
|
||||
<!-- Progress Track -->
|
||||
<circle class="stroke-foam" cx="16" cy="16" :r="radius" fill="none" stroke-width="3"
|
||||
:stroke-dasharray="circumference" :stroke-dashoffset="offset" stroke-linecap="round" />
|
||||
</svg>
|
||||
<p class="text-sm h-min"> {{ usage }} of {{ capacity }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
13
ui/components/Footer.vue
Normal file
13
ui/components/Footer.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
let year = new Date().getFullYear()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="flex flex-col gap-2 sm:flex-row py-6 w-full shrink-0 items-center px-4 md:px-6 border-t">
|
||||
<div class="flex flex-row gap-x-2">
|
||||
<p>Privacy policy</p>
|
||||
<p>Other policy</p>
|
||||
</div>
|
||||
<p class="text-xs text-subtle ml-auto">© {{ year }} juls0730. All rights reserved.</p>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -12,6 +12,6 @@ function updateValue(value) {
|
||||
|
||||
<template>
|
||||
<input
|
||||
class="py-2 px-4 resize-none bg-overlay rounded-md my-2 border border-muted/20 hover:border-muted/40 focus:border-muted/60 placeholder:italic placeholder:text-subtle transition-[border-color] max-w-64"
|
||||
class="py-2 px-4 resize-none bg-overlay rounded-md my-2 border hover:border-muted/40 focus:border-muted/60 placeholder:italic placeholder:text-subtle transition-[border-color] max-w-64"
|
||||
:placeholder="placeholder" :type="type" v-on:input="updateValue($event.target.value)" />
|
||||
</template>
|
||||
@@ -1,2 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
let colorMode = useColorMode();
|
||||
|
||||
const changeTheme = () => {
|
||||
if (colorMode.preference === "dark") {
|
||||
// from dark => light
|
||||
colorMode.preference = "light"
|
||||
} else if (colorMode.preference === "light") {
|
||||
// from light => system
|
||||
colorMode.preference = "system";
|
||||
} else {
|
||||
// from system => dark
|
||||
colorMode.preference = "dark";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="flex h-[var(--nav-height)] px-4 justify-center sticky top-0 z-50 border-b bg-base">
|
||||
<div class="flex w-full items-center justify-between space-x-2.5">
|
||||
<p
|
||||
class="-ml-2.5 flex shrink-0 items-center px-2.5 py-1.5 focus:outline-none focus:ring rounded-m font-semiboldd">
|
||||
filething
|
||||
</p>
|
||||
</div>
|
||||
<nav class="hidden md:flex" aria-label="Main">
|
||||
<ul class="flex items-center gap-3" role="list">
|
||||
<li>
|
||||
<a href="#"
|
||||
class="px-2.5 py-1.5 text-[15px] font-semibold transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset rounded-md">Link</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
class="px-2.5 py-1.5 text-[15px] font-semibold transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset rounded-md">Link</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
class="px-2.5 py-1.5 text-[15px] font-semibold transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset rounded-md">Link</a>
|
||||
</li>
|
||||
<li class="h-6 border-r"></li>
|
||||
<li>
|
||||
<button
|
||||
class="flex items-center px-3 h-8 text-[15px] font-semibold transition-bg duration-300 hover:bg-muted/10 focus:outline-none focus:ring focus:ring-inset rounded-md"
|
||||
@click="changeTheme">
|
||||
<span class="inline-block">
|
||||
<svg v-if="$colorMode.preference === 'dark'" xmlns="http://www.w3.org/2000/svg" width="22"
|
||||
height="22" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M12 3h.393a7.5 7.5 0 0 0 7.92 12.446A9 9 0 1 1 12 2.992z" />
|
||||
</svg>
|
||||
<svg v-else-if="$colorMode.preference === 'light'" xmlns="http://www.w3.org/2000/svg"
|
||||
width="22" height="22" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M14.828 14.828a4 4 0 1 0-5.656-5.656a4 4 0 0 0 5.656 5.656m-8.485 2.829l-1.414 1.414M6.343 6.343L4.929 4.929m12.728 1.414l1.414-1.414m-1.414 12.728l1.414 1.414M4 12H2m10-8V2m8 10h2m-10 8v2" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 256 256">
|
||||
<path fill="currentColor"
|
||||
d="M208 36H48a28 28 0 0 0-28 28v112a28 28 0 0 0 28 28h160a28 28 0 0 0 28-28V64a28 28 0 0 0-28-28Zm4 140a4 4 0 0 1-4 4H48a4 4 0 0 1-4-4V64a4 4 0 0 1 4-4h160a4 4 0 0 1 4 4Zm-40 52a12 12 0 0 1-12 12H96a12 12 0 0 1 0-24h64a12 12 0 0 1 12 12Z" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</template>
|
||||
239
ui/components/UploadPane.vue
Normal file
239
ui/components/UploadPane.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from '~/utils/formatBytes';
|
||||
import type { FileUpload } from '~/types/user';
|
||||
|
||||
const props = defineProps({
|
||||
uploadingFiles: {
|
||||
type: Array<FileUpload>,
|
||||
required: true
|
||||
},
|
||||
closed: Boolean,
|
||||
})
|
||||
defineEmits(['update:closed'])
|
||||
|
||||
const abortUpload = (id: string) => {
|
||||
let file = props.uploadingFiles.find(upload => upload.id === id);
|
||||
if (!file) {
|
||||
throw new Error("Upload cannot be aborted file is missing!")
|
||||
}
|
||||
|
||||
const controller = file.controller;
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
}
|
||||
};
|
||||
|
||||
const formatRemainingTime = (seconds: number): string => {
|
||||
if (seconds < 60) {
|
||||
return `${Math.floor(seconds)} second${Math.floor(seconds) === 1 ? '' : 's'} left`;
|
||||
}
|
||||
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
if (minutes < 60) {
|
||||
return `${minutes} minute${minutes === 1 ? '' : 's'} left`;
|
||||
}
|
||||
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) {
|
||||
return `${hours} hour${hours === 1 ? '' : 's'} left`;
|
||||
}
|
||||
|
||||
const days = Math.floor(hours / 24);
|
||||
return `${days} day${days === 1 ? '' : 's'} left`;
|
||||
};
|
||||
|
||||
const truncateFilenameToFitWidth = (filename: string, maxWidthPx: number, font = '18px ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji') => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
context.font = font;
|
||||
|
||||
const name = filename.substring(0, filename.lastIndexOf('.'));
|
||||
const extension = filename.substring(filename.lastIndexOf('.'));
|
||||
|
||||
function getTextWidth(text) {
|
||||
return context.measureText(text).width;
|
||||
}
|
||||
|
||||
if (getTextWidth(filename) <= maxWidthPx) {
|
||||
return filename;
|
||||
}
|
||||
|
||||
let truncatedName = name;
|
||||
let charsToRemove = 4;
|
||||
while (getTextWidth(truncatedName + extension) > maxWidthPx && truncatedName.length > charsToRemove) {
|
||||
const start = Math.ceil((truncatedName.length - charsToRemove) / 2);
|
||||
const end = Math.floor((truncatedName.length + charsToRemove) / 2);
|
||||
|
||||
truncatedName = truncatedName.substring(0, start) + '...' + truncatedName.substring(end);
|
||||
charsToRemove++;
|
||||
}
|
||||
|
||||
canvas.remove()
|
||||
|
||||
return truncatedName + extension;
|
||||
}
|
||||
|
||||
let collapsed = ref(false);
|
||||
let closeable = computed(() => props.uploadingFiles.filter(x => x.uploading === true).length === 0);
|
||||
let overallRemaining = computed(() => {
|
||||
if (closeable.value) {
|
||||
return
|
||||
}
|
||||
const uploadingFiles = props.uploadingFiles.filter(x => x.uploading === true);
|
||||
|
||||
return uploadingFiles.reduce((max, item) => item.remainingTime > max.remainingTime ? item : max).remainingTime
|
||||
});
|
||||
let overallPercentage = computed(() => {
|
||||
const uploadingFiles = props.uploadingFiles.filter(x => x.uploading === true);
|
||||
|
||||
const totalLoaded = uploadingFiles.reduce((acc, file) => acc + file.length.loaded, 0);
|
||||
const totalSize = uploadingFiles.reduce((acc, file) => acc + file.length.total, 0);
|
||||
|
||||
if (totalSize === 0) return 0; // Avoid division by zero
|
||||
|
||||
return (totalLoaded / totalSize) * 100; // Return percentage
|
||||
})
|
||||
|
||||
let uploadedSuccessfully = computed(() => props.uploadingFiles.filter(x => x.status.error === false));
|
||||
let uploadFailed = computed(() => props.uploadingFiles.filter(x => x.status.error === true));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="absolute bottom-0 right-0 m-3 rounded-2xl border flex flex-col sm:w-[440px] w-[calc(100%-24px)] shadow-md bg-surface"
|
||||
:class="{ 'h-[510px]': !collapsed, 'hidden': closed }">
|
||||
<div class="flex flex-row justify-between h-14 items-center mb-3 px-4" :class="{ 'hidden': collapsed }">
|
||||
<h3 class="text-xl font-semibold">Upload</h3>
|
||||
<div class="flex flex-row gap-x-2">
|
||||
<button v-on:click="collapsed = !collapsed"
|
||||
class="p-1 border h-fit rounded-md hover:bg-muted/10 active:bg-muted/20 transition-bg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="m6 9l6 6l6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
<button v-on:click="$emit('update:closed', true)" v-if="closeable"
|
||||
class="p-1 border h-fit rounded-md hover:bg-muted/10 active:bg-muted/20 transition-bg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M18 6L6 18M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow px-4 overflow-y-auto max-h-[358px]" :class="{ 'hidden': collapsed }">
|
||||
<div v-for="(upload, index) in uploadingFiles" :key="index" :id="`file-upload-${upload.id}`">
|
||||
<div class="flex flex-row gap-x-2 py-2 w-full">
|
||||
<div>
|
||||
<svg v-if="upload.uploading" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M7 18a4.6 4.4 0 0 1 0-9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-1" />
|
||||
<path d="m9 15l3-3l3 3m-3-3v9" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg v-else-if="upload.status.aborted" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 18.004H6.657C4.085 18 2 15.993 2 13.517s2.085-4.482 4.657-4.482c.393-1.762 1.794-3.2 3.675-3.773c1.88-.572 3.956-.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.37 0 2.556.8 3.117 1.964M22 22l-5-5m0 5l5-5" />
|
||||
</svg>
|
||||
<svg v-else-if="upload.status.error" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 18.004H6.657C4.085 18 2 15.993 2 13.517s2.085-4.482 4.657-4.482c.393-1.762 1.794-3.2 3.675-3.773c1.88-.572 3.956-.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.374 0 2.562.805 3.121 1.972M19 16v3m0 3v.01" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M11 18.004H6.657C4.085 18 2 15.993 2 13.517s2.085-4.482 4.657-4.482c.393-1.762 1.794-3.2 3.675-3.773c1.88-.572 3.956-.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.388 0 2.585.82 3.138 2.007M15 19l2 2l4-4" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="px-2 flex-grow">
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="font-medium overflow-hidden overflow-ellipsis whitespace-nowrap inline-block max-w-[220px]">{{
|
||||
truncateFilenameToFitWidth(upload.file.name, 220) }}</span>
|
||||
<div class="flex flex-row">
|
||||
<div
|
||||
class="font-medium uppercase rounded-full bg-overlay text-[10px] px-2 py-0.5 -ml-1 mr-2 w-fit max-w-20 overflow-hidden overflow-ellipsis max-h-[19px] whitespace-nowrap inline-block">
|
||||
{{ upload.file.name.split(".")[upload.file.name.split(".").length - 1] }}
|
||||
</div>
|
||||
<div class="flex text-[10px] items-end text-subtle">
|
||||
<span
|
||||
class="h-min overflow-hidden overflow-ellipsis max-w-56 whitespace-nowrap inline-block"
|
||||
v-if="upload.uploading">
|
||||
Uploading - {{ formatBytes(upload.length.loaded, 1) }} / {{
|
||||
formatBytes(upload.length.total, 1) }} - {{
|
||||
formatRemainingTime(upload.remainingTime) }}
|
||||
</span>
|
||||
<span v-else-if="upload.status.code >= 200 && upload.status.code < 300"
|
||||
class="h-min overflow-hidden overflow-ellipsis max-w-56 whitespace-nowrap inline-block">
|
||||
Uploaded to upload path
|
||||
</span>
|
||||
<span
|
||||
class="h-min overflow-hidden overflow-ellipsis max-w-56 whitespace-nowrap inline-block"
|
||||
v-else-if="upload.status.aborted">
|
||||
Canceled
|
||||
</span>
|
||||
<span
|
||||
class="h-min overflow-hidden overflow-ellipsis max-w-56 whitespace-nowrap inline-block"
|
||||
v-else-if="upload.status.error">
|
||||
{{ upload.status.message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center" v-if="upload.uploading">
|
||||
<button v-on:click="abortUpload(upload.id)"
|
||||
class="h-fit p-1 border rounded-md hover:bg-love/10 active:bg-love/20 hover:text-love transition-[background-color,color] text-sm py-1 px-2">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="upload.length.loaded !== undefined && upload.status.code === undefined"
|
||||
class="w-full rounded-full h-1 bg-foam/20 relative -mt-1">
|
||||
<div class="bg-foam rounded-full absolute left-0 top-0 bottom-0 transition-[width]"
|
||||
:style="'width: ' + Math.round((upload.length.loaded / upload.length.total) * 100) + '%;'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-3 rounded-md bg-overlay border bottom-2 flex flex-row">
|
||||
<div class="flex p-3 w-fit rounded-md">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 9l5-5l5 5m-5-5v12" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-row flex-grow items-center">
|
||||
<div v-if="!closeable" class="p-2 flex flex-col flex-grow relative">
|
||||
<span class="font-medium font-pine">Uploading Files</span>
|
||||
<span class="text-xs items-end text-subtle" v-if="overallRemaining">{{
|
||||
formatRemainingTime(overallRemaining) }}</span>
|
||||
<div class="bg-pine/25 absolute left-0 bottom-0 top-0"
|
||||
:style="'width: ' + overallPercentage + '%;'">
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="uploadFailed.length === 0" class="p-2 flex flex-col flex-grow">
|
||||
<span class="font-medium">Successfully Uploaded all files</span>
|
||||
</div>
|
||||
<div v-else-if="uploadedSuccessfully.length === 0" class="p-2 flex flex-col flex-grow">
|
||||
<span class="font-medium">Failed to Uploaded all files</span>
|
||||
</div>
|
||||
<div v-else class="p-2 flex flex-col flex-grow">
|
||||
<span class="font-medium">Successfully Uploaded some files</span>
|
||||
</div>
|
||||
<button v-if="collapsed" v-on:click="collapsed = !collapsed"
|
||||
class="p-1 border h-fit rounded-md hover:bg-muted/10 active:bg-muted/20 transition-bg mr-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="m6 15l6-6l6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user