generated from Nodarx/template
77 lines
3.0 KiB
Vue
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>
|