Compare commits
	
		
			3 Commits
		
	
	
		
			4e68ab4ef0
			...
			2d728e4b07
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2d728e4b07 | |||
| 7ff9605460 | |||
| d3bf9739b5 | 
							
								
								
									
										198
									
								
								DysonNetwork.Drive/Client/src/components/FilePoolSelect.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								DysonNetwork.Drive/Client/src/components/FilePoolSelect.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | ||||
| <template> | ||||
|   <n-select | ||||
|     :value="modelValue" | ||||
|     @update:value="onUpdate" | ||||
|     :options="pools ?? []" | ||||
|     :render-label="renderPoolSelectLabel" | ||||
|     :render-tag="renderSingleSelectTag" | ||||
|     value-field="id" | ||||
|     label-field="name" | ||||
|     :placeholder="props.placeholder || 'Select a file pool to upload'" | ||||
|     clearable | ||||
|     size="large" | ||||
|   /> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { | ||||
|   NSelect, | ||||
|   NTag, | ||||
|   NDivider, | ||||
|   NTooltip, | ||||
|   type SelectOption, | ||||
|   type SelectRenderTag, | ||||
| } from 'naive-ui' | ||||
| import { h, onMounted, ref, watch } from 'vue' | ||||
| import type { SnFilePool } from '@/types/pool' | ||||
| import { formatBytes } from '@/views/format' | ||||
|  | ||||
| const props = defineProps<{ | ||||
|   modelValue: string | null | ||||
|   placeholder: string | undefined | ||||
| }>() | ||||
|  | ||||
| const emit = defineEmits(['update:modelValue', 'update:pool']) | ||||
|  | ||||
| type SnFilePoolOption = SnFilePool & any | ||||
|  | ||||
| const pools = ref<SnFilePoolOption[] | undefined>() | ||||
| async function fetchPools() { | ||||
|   const resp = await fetch('/api/pools') | ||||
|   pools.value = await resp.json() | ||||
| } | ||||
| onMounted(() => fetchPools()) | ||||
|  | ||||
| function onUpdate(value: string | null) { | ||||
|   emit('update:modelValue', value) | ||||
|   if (value === null) { | ||||
|     emit('update:pool', null) | ||||
|     return | ||||
|   } | ||||
|   if (pools.value) { | ||||
|     const pool = pools.value.find((p) => p.id === value) ?? null | ||||
|     emit('update:pool', pool) | ||||
|   } | ||||
| } | ||||
|  | ||||
| watch(pools, (newPools) => { | ||||
|   if (props.modelValue && newPools) { | ||||
|     const pool = newPools.find((p) => p.id === props.modelValue) ?? null | ||||
|     emit('update:pool', pool) | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const renderSingleSelectTag: SelectRenderTag = ({ option }) => { | ||||
|   return h( | ||||
|     'div', | ||||
|     { | ||||
|       style: { | ||||
|         display: 'flex', | ||||
|         alignItems: 'center', | ||||
|       }, | ||||
|     }, | ||||
|     [option.name as string], | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const perkPrivilegeList = ['Stellar', 'Nova', 'Supernova'] | ||||
|  | ||||
| function renderPoolSelectLabel(option: SelectOption & SnFilePool) { | ||||
|   const policy: any = option.policy_config | ||||
|   return h( | ||||
|     'div', | ||||
|     { | ||||
|       style: { | ||||
|         padding: '8px 2px', | ||||
|       }, | ||||
|     }, | ||||
|     [ | ||||
|       h('div', null, [option.name as string]), | ||||
|       option.description && | ||||
|         h( | ||||
|           'div', | ||||
|           { | ||||
|             style: { | ||||
|               fontSize: '0.875rem', | ||||
|               opacity: '0.75', | ||||
|             }, | ||||
|           }, | ||||
|           option.description, | ||||
|         ), | ||||
|       h( | ||||
|         'div', | ||||
|         { | ||||
|           style: { | ||||
|             display: 'flex', | ||||
|             marginBottom: '4px', | ||||
|             fontSize: '0.75rem', | ||||
|             opacity: '0.75', | ||||
|           }, | ||||
|         }, | ||||
|         [ | ||||
|           policy.max_file_size && h('span', `Max ${formatBytes(policy.max_file_size)}`), | ||||
|           policy.accept_types && | ||||
|             h( | ||||
|               NTooltip, | ||||
|               {}, | ||||
|               { | ||||
|                 trigger: () => h('span', `Accept limited types`), | ||||
|                 default: () => h('span', policy.accept_types.join(', ')), | ||||
|               }, | ||||
|             ), | ||||
|           policy.require_privilege && | ||||
|             h('span', `Require ${perkPrivilegeList[policy.require_privilege - 1]} Program`), | ||||
|           h('span', `Cost x${option.billing_config.cost_multiplier.toFixed(1)} NSD`), | ||||
|         ] | ||||
|           .filter((el) => el) | ||||
|           .flatMap((el, idx, arr) => | ||||
|             idx < arr.length - 1 ? [el, h(NDivider, { vertical: true })] : [el], | ||||
|           ), | ||||
|       ), | ||||
|       h( | ||||
|         'div', | ||||
|         { | ||||
|           style: { | ||||
|             display: 'flex', | ||||
|             gap: '0.25rem', | ||||
|             marginTop: '2px', | ||||
|             marginLeft: '-2px', | ||||
|             marginRight: '-2px', | ||||
|           }, | ||||
|         }, | ||||
|         [ | ||||
|           policy.public_usable && | ||||
|             h( | ||||
|               NTag, | ||||
|               { | ||||
|                 type: 'info', | ||||
|                 size: 'small', | ||||
|                 round: true, | ||||
|               }, | ||||
|               { default: () => 'Public Shared' }, | ||||
|             ), | ||||
|           policy.public_indexable && | ||||
|             h( | ||||
|               NTag, | ||||
|               { | ||||
|                 type: 'success', | ||||
|                 size: 'small', | ||||
|                 round: true, | ||||
|               }, | ||||
|               { default: () => 'Public Indexable' }, | ||||
|             ), | ||||
|           policy.allow_encryption && | ||||
|             h( | ||||
|               NTag, | ||||
|               { | ||||
|                 type: 'warning', | ||||
|                 size: 'small', | ||||
|                 round: true, | ||||
|               }, | ||||
|               { default: () => 'Allow Encryption' }, | ||||
|             ), | ||||
|           policy.allow_anonymous && | ||||
|             h( | ||||
|               NTag, | ||||
|               { | ||||
|                 type: 'info', | ||||
|                 size: 'small', | ||||
|                 round: true, | ||||
|               }, | ||||
|               { default: () => 'Allow Anonymous' }, | ||||
|             ), | ||||
|           policy.enable_recycle && | ||||
|             h( | ||||
|               NTag, | ||||
|               { | ||||
|                 type: 'info', | ||||
|                 size: 'small', | ||||
|                 round: true, | ||||
|               }, | ||||
|               { default: () => 'Recycle Enabled' }, | ||||
|             ), | ||||
|         ], | ||||
|       ), | ||||
|     ], | ||||
|   ) | ||||
| } | ||||
| </script> | ||||
| @@ -1,6 +1,22 @@ | ||||
| <template> | ||||
|   <section class="h-full px-5 py-4"> | ||||
|     <n-data-table :columns="tableColumns" :data="files" :pagination="tablePagination" /> | ||||
|     <div class="flex flex-col gap-3 mb-3"> | ||||
|       <file-pool-select | ||||
|         v-model="filePool" | ||||
|         placeholder="Filter by file pool" | ||||
|         class="max-w-[480px]" | ||||
|         @update:pool="fetchFiles" | ||||
|       /> | ||||
|     </div> | ||||
|     <n-data-table | ||||
|       remote | ||||
|       :row-key="(row) => row.id" | ||||
|       :columns="tableColumns" | ||||
|       :data="files" | ||||
|       :loading="loading" | ||||
|       :pagination="tablePagination" | ||||
|       @page-change="handlePageChange" | ||||
|     /> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| @@ -27,10 +43,12 @@ import { | ||||
| import { h, onMounted, ref } from 'vue' | ||||
| import { useRouter } from 'vue-router' | ||||
| import { formatBytes } from '../format' | ||||
| import FilePoolSelect from '@/components/FilePoolSelect.vue' | ||||
|  | ||||
| const router = useRouter() | ||||
|  | ||||
| const files = ref<any[]>([]) | ||||
| const filePool = ref<string | null>(null) | ||||
|  | ||||
| const tableColumns: DataTableColumns<any> = [ | ||||
|   { | ||||
| @@ -95,7 +113,6 @@ const tableColumns: DataTableColumns<any> = [ | ||||
|         h( | ||||
|           NButton, | ||||
|           { | ||||
|             quaternary: true, | ||||
|             circle: true, | ||||
|             text: true, | ||||
|             onClick: () => { | ||||
| @@ -109,7 +126,6 @@ const tableColumns: DataTableColumns<any> = [ | ||||
|         h( | ||||
|           NButton, | ||||
|           { | ||||
|             quaternary: true, | ||||
|             circle: true, | ||||
|             text: true, | ||||
|             type: 'error', | ||||
| @@ -129,11 +145,19 @@ const tableColumns: DataTableColumns<any> = [ | ||||
| const tablePagination = ref<PaginationProps>({ | ||||
|   page: 1, | ||||
|   itemCount: 0, | ||||
|   pageSize: 10, | ||||
|   showSizePicker: true, | ||||
|   pageSizes: [10, 20, 30, 40, 50], | ||||
| }) | ||||
|  | ||||
| async function fetchFiles() { | ||||
|   if (loading.value) return | ||||
|   try { | ||||
|     const response = await fetch('/api/files/me') | ||||
|     loading.value = true | ||||
|     const pag = tablePagination.value | ||||
|     const response = await fetch( | ||||
|       `/api/files/me?take=${pag.pageSize}&offset=${(pag.page! - 1) * pag.pageSize!}${filePool.value ? '&pool=' + filePool.value : ''}`, | ||||
|     ) | ||||
|     if (!response.ok) { | ||||
|       throw new Error('Network response was not ok') | ||||
|     } | ||||
| @@ -142,10 +166,19 @@ async function fetchFiles() { | ||||
|     tablePagination.value.itemCount = parseInt(response.headers.get('x-total') ?? '0') | ||||
|   } catch (error) { | ||||
|     console.error('Failed to fetch files:', error) | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
| onMounted(() => fetchFiles()) | ||||
|  | ||||
| function handlePageChange(page: number) { | ||||
|   tablePagination.value.page = page | ||||
|   fetchFiles() | ||||
| } | ||||
|  | ||||
| const loading = ref(false) | ||||
|  | ||||
| const dialog = useDialog() | ||||
| const messageDialog = useMessage() | ||||
| const loadingBar = useLoadingBar() | ||||
|   | ||||
| @@ -2,8 +2,8 @@ | ||||
|   <section class="min-h-full relative flex items-center justify-center"> | ||||
|     <n-spin v-if="!fileInfo && !error" /> | ||||
|     <n-result status="404" title="No file was found" :description="error" v-else-if="error" /> | ||||
|     <n-card class="max-w-4xl my-4" v-else> | ||||
|       <n-grid :cols="2" x-gap="16"> | ||||
|     <n-card class="max-w-4xl my-4 mx-8" v-else> | ||||
|       <n-grid cols="1 m:2" x-gap="16" y-gap="16" responsive="screen"> | ||||
|         <n-gi> | ||||
|           <div v-if="fileInfo.is_encrypted"> | ||||
|             <n-alert type="info" size="small" title="Encrypted file"> | ||||
| @@ -14,6 +14,15 @@ | ||||
|           <div v-else> | ||||
|             <n-image v-if="fileType === 'image'" :src="fileSource" class="w-full" /> | ||||
|             <video v-else-if="fileType === 'video'" :src="fileSource" controls class="w-full" /> | ||||
|             <audio v-else-if="fileType === 'audio'" :src="fileSource" controls class="w-full" /> | ||||
|             <n-result | ||||
|               status="418" | ||||
|               title="Preview Unavailable" | ||||
|               description="How can you preview this file?" | ||||
|               size="small" | ||||
|               class="py-6" | ||||
|               v-else | ||||
|             /> | ||||
|           </div> | ||||
|         </n-gi> | ||||
|  | ||||
|   | ||||
| @@ -23,17 +23,7 @@ | ||||
|       </template> | ||||
|  | ||||
|       <div class="mb-3"> | ||||
|         <n-select | ||||
|           v-model:value="filePool" | ||||
|           :options="pools ?? []" | ||||
|           :render-label="renderPoolSelectLabel" | ||||
|           :render-tag="renderSingleSelectTag" | ||||
|           value-field="id" | ||||
|           label-field="name" | ||||
|           placeholder="Select a file pool to upload" | ||||
|           clearable | ||||
|           size="large" | ||||
|         /> | ||||
|         <file-pool-select v-model="filePool" @update:pool="currentFilePool = $event" /> | ||||
|       </div> | ||||
|  | ||||
|       <n-collapse-transition :show="modeAdvanced"> | ||||
| @@ -134,6 +124,8 @@ import { useUserStore } from '@/stores/user' | ||||
| import type { SnFilePool } from '@/types/pool' | ||||
| import { formatBytes } from './format' | ||||
|  | ||||
| import FilePoolSelect from '@/components/FilePoolSelect.vue' | ||||
|  | ||||
| import * as tus from 'tus-js-client' | ||||
|  | ||||
| const userStore = useUserStore() | ||||
|   | ||||
| @@ -146,16 +146,17 @@ public class FileController( | ||||
|             .Where(e => e.AccountId == accountId) | ||||
|             .Include(e => e.Pool) | ||||
|             .OrderByDescending(e => e.CreatedAt) | ||||
|             .Skip(offset) | ||||
|             .Take(take); | ||||
|             .AsQueryable(); | ||||
|  | ||||
|         if (pool.HasValue) query = query.Where(e => e.PoolId == pool); | ||||
|          | ||||
|         var files = await query.ToListAsync(); | ||||
|  | ||||
|         var total = await query.CountAsync(); | ||||
|         Response.Headers.Append("X-Total", total.ToString()); | ||||
|  | ||||
|         var files = await query | ||||
|             .Skip(offset) | ||||
|             .Take(take) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         return Ok(files); | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user