Files
provider_editor_office/src/components/OfficeViewer.vue
T
2026-09-14 18:08:20 -04:00

77 lines
3.0 KiB
Vue

<script setup lang="ts">
import { useIntegrationStore } from '@KTXC'
import { nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { launch, validateSession, type LaunchSession } from '../api'
import { officeResource, type OfficeEntity } from '../resource'
const props = defineProps<{ entity: OfficeEntity; mime: string }>()
const integrations = useIntegrationStore()
const session = ref<LaunchSession | null>(null)
const error = ref('')
const busy = ref(false)
const form = ref<HTMLFormElement | null>(null)
const submitted = ref(false)
const target = `office-${crypto.randomUUID()}`
let generation = 0
let timeout: ReturnType<typeof setTimeout> | undefined
async function open() {
const current = ++generation
clearTimeout(timeout)
session.value = null
submitted.value = false
error.value = ''
busy.value = true
try {
if (!integrations.getItems('documents_editor_service').some(item => item.id === 'service_wopi.wopi')) {
error.value = 'The WOPI service is not available.'
return
}
const result = await launch(officeResource(props.entity))
if (current !== generation) return
validateSession(result)
session.value = result
await nextTick()
if (current !== generation) return
form.value?.submit()
// Remove the token-bearing form from the DOM after submission.
submitted.value = true
timeout = setTimeout(() => {
error.value = 'This viewing session has expired. Reopen the document to continue.'
session.value = null
}, Math.min(result.accessTokenTtl - Date.now(), 2147483647))
} catch {
if (current === generation) error.value = 'Could not open the document. It may be unavailable, unsupported, or too large.'
} finally {
if (current === generation) busy.value = false
}
}
watch(() => [props.entity.provider, props.entity.service, props.entity.collection, props.entity.identifier], open, { immediate: true })
onBeforeUnmount(() => { generation++; clearTimeout(timeout); session.value = null })
</script>
<template>
<section class="office-viewer">
<p class="office-status">Read-only — reopen the document to see changes made elsewhere.</p>
<p v-if="busy" role="status">Opening document…</p>
<div v-if="error" role="alert">
<p>{{ error }}</p>
<button type="button" @click="open">Reopen document</button>
</div>
<template v-if="session">
<iframe :name="target" title="Office document viewer" referrerpolicy="no-referrer" allow="fullscreen" />
<form v-if="!submitted" ref="form" method="post" :action="session.actionUrl" :target="target" hidden>
<input type="hidden" name="access_token" :value="session.accessToken">
<input type="hidden" name="access_token_ttl" :value="session.accessTokenTtl">
</form>
</template>
</section>
</template>
<style scoped>
.office-viewer { display: flex; flex-direction: column; height: 100%; min-height: 0; padding: 8px; }
.office-status { margin: 0 0 8px; font-size: 0.875rem; }
iframe { flex: 1; width: 100%; min-height: 400px; border: 0; }
</style>