248 lines
7.0 KiB
Vue
248 lines
7.0 KiB
Vue
<!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
|
|
<!-- eslint-disable vuejs-accessibility/click-events-have-key-events -->
|
|
<template>
|
|
<LayoutDialog v-model:open="open" title="Model upload history" :buttons="buttons">
|
|
<LayoutTable
|
|
:columns="[
|
|
{ id: 'job', header: 'Job #', classes: 'col-span-1' },
|
|
{ id: 'file', header: 'File', classes: 'col-span-4' },
|
|
{ id: 'status', header: 'Status', classes: 'col-span-2' },
|
|
{ id: 'size', header: 'Size', classes: 'col-span-2' },
|
|
{ id: 'date', header: 'Date', classes: 'col-span-2' },
|
|
{
|
|
id: 'actions',
|
|
header: '',
|
|
classes: 'col-span-1 flex items-center justify-end'
|
|
}
|
|
]"
|
|
:items="items"
|
|
:loading="isVeryFirstLoading"
|
|
empty-message="This model has no uploads"
|
|
style="max-height: 300px"
|
|
>
|
|
<template #job="{ item }">
|
|
<span class="text-foreground-2">{{ item.id }}</span>
|
|
</template>
|
|
<template #file="{ item }">
|
|
<div
|
|
v-tippy="{
|
|
content: item.fileName.length > 35 ? item.fileName : undefined,
|
|
placement: 'top-start',
|
|
delay: 300
|
|
}"
|
|
class="truncate text-foreground"
|
|
>
|
|
{{ item.fileName }}
|
|
</div>
|
|
</template>
|
|
<template #size="{ item }">
|
|
<span class="text-foreground-2">{{ prettyFileSize(item.fileSize) }}</span>
|
|
</template>
|
|
<template #status="{ item }">
|
|
<div
|
|
v-keyboard-clickable
|
|
:class="[
|
|
'flex items-center gap-2',
|
|
getStatusOptions(item).isErrorStatus ? 'group hover:cursor-pointer' : ''
|
|
]"
|
|
@click="onErrorBadgeClick(item)"
|
|
>
|
|
<CommonBadge
|
|
v-tippy="getStatusOptions(item).tooltip"
|
|
:color-classes="getStatusOptions(item).colorClasses"
|
|
>
|
|
{{ getStatusOptions(item).label }}
|
|
</CommonBadge>
|
|
<CommonCopyButton
|
|
v-if="getStatusOptions(item).isErrorStatus"
|
|
class="group-hover:text-foreground"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<template #date="{ item }">
|
|
<span
|
|
v-tippy="formattedFullDate(item.convertedLastUpdate || item.uploadDate)"
|
|
class="text-foreground-2"
|
|
>
|
|
{{ formattedRelativeDate(item.convertedLastUpdate || item.uploadDate) }}
|
|
</span>
|
|
</template>
|
|
<template #actions="{ item }">
|
|
<FormButton
|
|
:icon-left="ArrowDownTrayIcon"
|
|
hide-text
|
|
size="sm"
|
|
color="outline"
|
|
@click="onDownload(item)"
|
|
/>
|
|
</template>
|
|
<template #loader>
|
|
<InfiniteLoading
|
|
v-if="items?.length"
|
|
:settings="{ identifier }"
|
|
hide-when-complete
|
|
@infinite="onInfiniteLoad"
|
|
/>
|
|
</template>
|
|
</LayoutTable>
|
|
</LayoutDialog>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { ArrowDownTrayIcon } from '@heroicons/vue/24/outline'
|
|
import {
|
|
FileUploadConvertedStatus,
|
|
fileUploadConvertedStatusLabels
|
|
} from '@speckle/shared/blobs'
|
|
import type { LayoutDialogButton } from '@speckle/ui-components'
|
|
import { usePaginatedQuery } from '~/lib/common/composables/graphql'
|
|
import { graphql } from '~/lib/common/generated/gql'
|
|
import type { ProjectPageModelsUploadsDialog_FileUploadFragment } from '~/lib/common/generated/gql/graphql'
|
|
import { useFailedFileImportJobUtils } from '~/lib/core/composables/fileImport'
|
|
import { useFileDownload } from '~/lib/core/composables/fileUpload'
|
|
import { prettyFileSize } from '~~/lib/core/helpers/file'
|
|
|
|
graphql(`
|
|
fragment ProjectPageModelsUploadsDialog_FileUpload on FileUpload {
|
|
id
|
|
convertedStatus
|
|
convertedMessage
|
|
fileName
|
|
fileSize
|
|
convertedLastUpdate
|
|
uploadDate
|
|
uploadComplete
|
|
branchName
|
|
...UseFailedFileImportJobUtils_FileUpload
|
|
}
|
|
`)
|
|
|
|
const getModelUploadsQuery = graphql(`
|
|
query GetModelUploads(
|
|
$projectId: String!
|
|
$modelId: String!
|
|
$input: GetModelUploadsInput!
|
|
) {
|
|
project(id: $projectId) {
|
|
id
|
|
model(id: $modelId) {
|
|
id
|
|
uploads(input: $input) {
|
|
totalCount
|
|
cursor
|
|
items {
|
|
id
|
|
...ProjectPageModelsUploadsDialog_FileUpload
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
|
|
const props = defineProps<{
|
|
projectId: string
|
|
modelId: string
|
|
}>()
|
|
|
|
const open = defineModel<boolean>('open', { required: true })
|
|
const { copy } = useClipboard()
|
|
|
|
const { getErrorMessage, convertUploadToFailedJob } = useFailedFileImportJobUtils()
|
|
const {
|
|
identifier,
|
|
onInfiniteLoad,
|
|
query: { result },
|
|
isVeryFirstLoading
|
|
} = usePaginatedQuery({
|
|
query: getModelUploadsQuery,
|
|
baseVariables: computed(() => ({
|
|
projectId: props.projectId,
|
|
modelId: props.modelId,
|
|
input: {
|
|
cursor: null as string | null
|
|
}
|
|
})),
|
|
options: {
|
|
enabled: open,
|
|
// reload query when dialog opens
|
|
fetchPolicy: 'cache-and-network'
|
|
},
|
|
resolveKey: (vars) => [vars.projectId, vars.modelId],
|
|
resolveCurrentResult: (res) => res?.project.model.uploads,
|
|
resolveNextPageVariables: (baseVars, cursor) => ({
|
|
...baseVars,
|
|
input: {
|
|
...baseVars.input,
|
|
cursor
|
|
}
|
|
}),
|
|
resolveCursorFromVariables: (vars) => vars.input.cursor
|
|
})
|
|
|
|
const { download } = useFileDownload()
|
|
|
|
const items = computed(() => result.value?.project.model.uploads.items)
|
|
|
|
const buttons = computed((): LayoutDialogButton[] => [
|
|
{
|
|
text: 'Close',
|
|
onClick: () => {
|
|
open.value = false
|
|
}
|
|
}
|
|
])
|
|
|
|
const getStatusOptions = (item: ProjectPageModelsUploadsDialog_FileUploadFragment) => {
|
|
let colorClasses: string | undefined = undefined
|
|
switch (item.convertedStatus) {
|
|
case FileUploadConvertedStatus.Error:
|
|
colorClasses = 'bg-danger text-foundation'
|
|
break
|
|
case FileUploadConvertedStatus.Converting:
|
|
colorClasses = 'bg-primary text-foundation'
|
|
break
|
|
case FileUploadConvertedStatus.Completed:
|
|
colorClasses = 'bg-success text-foundation'
|
|
break
|
|
case FileUploadConvertedStatus.Queued:
|
|
colorClasses = 'bg-info text-foundation'
|
|
break
|
|
}
|
|
|
|
return {
|
|
label:
|
|
fileUploadConvertedStatusLabels[
|
|
item.convertedStatus as FileUploadConvertedStatus
|
|
],
|
|
tooltip:
|
|
item.convertedStatus === FileUploadConvertedStatus.Error
|
|
? {
|
|
content:
|
|
getErrorMessage(convertUploadToFailedJob(item)) +
|
|
` Error: ${item.convertedMessage}`
|
|
}
|
|
: undefined,
|
|
colorClasses,
|
|
isErrorStatus: item.convertedStatus === FileUploadConvertedStatus.Error
|
|
}
|
|
}
|
|
|
|
const onDownload = async (item: ProjectPageModelsUploadsDialog_FileUploadFragment) => {
|
|
await download({
|
|
blobId: item.id,
|
|
fileName: item.fileName,
|
|
projectId: props.projectId
|
|
})
|
|
}
|
|
|
|
const onErrorBadgeClick = async (
|
|
item: ProjectPageModelsUploadsDialog_FileUploadFragment
|
|
) => {
|
|
if (getStatusOptions(item).isErrorStatus) {
|
|
await copy(getStatusOptions(item).tooltip?.content || '', {
|
|
successMessage: 'Error message copied'
|
|
})
|
|
}
|
|
}
|
|
</script>
|