Added attachment preview
Build and Deploy Web App / build-and-deploy (push) Successful in 18s Details

This commit is contained in:
andrewlalis 2025-09-27 09:16:05 -04:00
parent 92a3dc4c62
commit dc0c48b2df
3 changed files with 63 additions and 6 deletions

View File

@ -18,10 +18,14 @@ export class AttachmentApiClient extends ApiClient {
this.profileName = getSelectedProfile(route) this.profileName = getSelectedProfile(route)
} }
downloadAttachment(attachmentId: number, token: string) { getAttachmentUrl(attachmentId: number, token: string) {
const url = return (
import.meta.env.VITE_API_BASE_URL + import.meta.env.VITE_API_BASE_URL +
`/profiles/${this.profileName}/attachments/${attachmentId}/download?t=${token}` `/profiles/${this.profileName}/attachments/${attachmentId}/download?t=${token}`
window.open(url, '_blank') )
}
downloadAttachment(attachmentId: number, token: string) {
window.open(this.getAttachmentUrl(attachmentId, token), '_blank')
} }
} }

View File

@ -3,21 +3,40 @@ import { AttachmentApiClient } from '@/api/attachment'
import { useAuthStore } from '@/stores/auth-store' import { useAuthStore } from '@/stores/auth-store'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import AppButton from './AppButton.vue' import AppButton from './AppButton.vue'
import { computed, useTemplateRef, type Ref } from 'vue'
import ModalWrapper from './ModalWrapper.vue'
interface AttachmentInfo { interface AttachmentInfo {
filename: string filename: string
contentType: string contentType: string
size: number size: number
id?: number id?: number
file?: File
} }
const route = useRoute() const route = useRoute()
const api = new AttachmentApiClient(route)
const authStore = useAuthStore() const authStore = useAuthStore()
const props = defineProps<{ attachment: AttachmentInfo; disabled?: boolean }>() const props = defineProps<{ attachment: AttachmentInfo; disabled?: boolean }>()
defineEmits<{ deleted: void }>() defineEmits<{ deleted: void }>()
const srcUrl: Ref<string | null> = computed(() => {
if (props.attachment.file !== undefined) {
return URL.createObjectURL(props.attachment.file)
}
if (props.attachment.id === undefined || authStore.state === null) {
return null
}
return api.getAttachmentUrl(props.attachment.id, authStore.state.token)
})
const canPreviewAttachment = computed(() => {
if (!srcUrl.value) return false
const c = props.attachment.contentType
return c.startsWith('image') || c === 'application/pdf'
})
const attachmentViewerModal = useTemplateRef('attachmentViewer')
function downloadFile() { function downloadFile() {
const api = new AttachmentApiClient(route)
if (!authStore.state) return if (!authStore.state) return
api.downloadAttachment(props.attachment?.id ?? 0, authStore.state.token) api.downloadAttachment(props.attachment?.id ?? 0, authStore.state.token)
} }
@ -31,19 +50,39 @@ function downloadFile() {
</div> </div>
</div> </div>
<div> <div>
<AppButton
icon="eye"
v-if="canPreviewAttachment"
@click="attachmentViewerModal?.show()"
></AppButton>
<AppButton <AppButton
icon="download" icon="download"
type="button"
@click="downloadFile()" @click="downloadFile()"
v-if="attachment.id !== undefined" v-if="attachment.id !== undefined"
/> />
<AppButton <AppButton
v-if="!disabled" v-if="!disabled"
type="button"
icon="trash" icon="trash"
@click="$emit('deleted')" @click="$emit('deleted')"
/> />
</div> </div>
<ModalWrapper
ref="attachmentViewer"
v-if="canPreviewAttachment"
>
<div>
<h3 style="margin-top: 0; margin-bottom: 0.25rem">{{ attachment.filename }}</h3>
<embed
:src="srcUrl ?? ''"
:type="attachment.contentType"
:class="{
'attachment-embed-img': attachment.contentType.startsWith('image'),
'attachment-embed-pdf': attachment.contentType === 'application/pdf',
}"
/>
</div>
</ModalWrapper>
</div> </div>
</template> </template>
<style lang="css"> <style lang="css">
@ -55,4 +94,13 @@ function downloadFile() {
border-radius: 0.5rem; border-radius: 0.5rem;
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.attachment-embed-img {
width: 100%;
}
.attachment-embed-pdf {
min-width: 500px;
min-height: 500px;
}
</style> </style>

View File

@ -52,11 +52,16 @@ defineExpose({ show, close })
color: inherit; color: inherit;
border-radius: 1rem; border-radius: 1rem;
border-width: 0; border-width: 0;
/* border: 3px solid var(--theme-secondary); */
min-width: 300px; min-width: 300px;
max-width: 500px; max-width: 500px;
padding: 1rem; padding: 1rem;
} }
.app-modal-dialog::backdrop {
background-color: rgb(0 0 0 / 60%);
}
.app-modal-dialog-header { .app-modal-dialog-header {
margin-top: 0; margin-top: 0;
} }