diff options
-rw-r--r-- | lib/Controller/WopiController.php | 2 | ||||
-rw-r--r-- | src/services/collabora.js | 80 | ||||
-rw-r--r-- | src/view/Office.vue | 97 |
3 files changed, 165 insertions, 14 deletions
diff --git a/lib/Controller/WopiController.php b/lib/Controller/WopiController.php index d01db98f..07bd8a38 100644 --- a/lib/Controller/WopiController.php +++ b/lib/Controller/WopiController.php @@ -195,7 +195,7 @@ class WopiController extends Controller { 'UserCanRename' => !$isPublic && !$isVersion, 'EnableInsertRemoteImage' => true, 'EnableShare' => $file->isShareable() && !$isVersion, - 'HideUserList' => 'desktop', + 'HideUserList' => '', 'DisablePrint' => $wopi->getHideDownload(), 'DisableExport' => $wopi->getHideDownload(), 'DisableCopy' => $wopi->getHideDownload(), diff --git a/src/services/collabora.js b/src/services/collabora.js new file mode 100644 index 00000000..8be1612c --- /dev/null +++ b/src/services/collabora.js @@ -0,0 +1,80 @@ +/* + * @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net> + * + * @author Julius Härtl <jus@bitgrid.net> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import { getCapabilities } from '@nextcloud/capabilities' +import axios from '@nextcloud/axios' + +export const isCollaboraConfigured = () => { + const collaboraCapabilities = getCapabilities()?.richdocuments?.collabora + return isBuiltinCodeServerUsed() || collaboraCapabilities.length !== 0 +} + +export const isBuiltinCodeServerUsed = () => { + const richdocumentsCapabilities = getCapabilities()?.richdocuments + return richdocumentsCapabilities?.config?.wopi_url?.indexOf('proxy.php') !== -1 +} + +let proxyStatusCheckRetry = 0 +export const checkProxyStatus = async(_resolve, _reject) => { + + const wopiUrl = getCapabilities()?.richdocuments?.config?.wopi_url + if (!wopiUrl) { + throw Error(t('richdocuments', 'Collabora is not configured')) + } + + if (wopiUrl.indexOf('proxy.php') === -1) { + return true + } + + const url = wopiUrl.substr(0, wopiUrl.indexOf('proxy.php') + 'proxy.php'.length) + const proxyStatusUrl = url + '?status' + + const checkProxyStatusCallback = async(resolve, reject) => { + const result = await axios.get(proxyStatusUrl) + if (!result || !result.status) { + reject('Failed to contact status endpoint') + } + + if (result.status === 'OK') { + return resolve(true) + } + if (result.status === 'error') { + return reject(t('richdocuments', 'Built-in CODE server failed to start')) + } + + if (proxyStatusCheckRetry < 3 && (result.status === 'starting' || result.status === 'stopped' || result.status === 'restarting')) { + setTimeout(() => { + proxyStatusCheckRetry++ + checkProxyStatus(resolve, reject) + }) + } else { + reject('Maximum retries reached') + } + + } + + if (_reject && _resolve) { + return checkProxyStatusCallback(_reject, _resolve) + } else { + return new Promise(checkProxyStatusCallback) + } +} diff --git a/src/view/Office.vue b/src/view/Office.vue index 9a847935..78b567af 100644 --- a/src/view/Office.vue +++ b/src/view/Office.vue @@ -22,9 +22,19 @@ <template> <transition name="fade" appear> - <div v-show="!loading" id="richdocuments-wrapper"> - <div class="header"> - <!-- This is obviously not the way to go since it would require absolute positioning and therefore not be compatible with viewer actions/sidebar --> + <div id="richdocuments-wrapper"> + <div v-if="showLoadingIndicator" id="cool-loading-overlay" :class="{ debug: debug }"> + <EmptyContent v-if="!error" icon="icon-loading"> + Loading document + </EmptyContent> + <EmptyContent v-else icon="icon-error"> + {{ t('richdocuments', 'Document loading failed') }} + <template #desc> + {{ errorMessage }} + </template> + </EmptyContent> + </div> + <div v-show="!useNativeHeader && showIframe" class="header"> <div class="avatars"> <Avatar v-for="view in avatarViews" :key="view.ViewId" @@ -38,7 +48,10 @@ <ActionButton icon="icon-menu-sidebar" @click="share" /> </Actions> </div> - <iframe id="collaboraframe" ref="documentFrame" :src="src" /> + <iframe v-show="showIframe" + id="collaboraframe" + ref="documentFrame" + :src="src" /> </div> </transition> </template> @@ -47,23 +60,38 @@ import Avatar from '@nextcloud/vue/dist/Components/Avatar' import Actions from '@nextcloud/vue/dist/Components/Actions' import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import { basename, dirname } from 'path' import { getDocumentUrlForFile, getDocumentUrlForPublicFile } from '../helpers/url' import PostMessageService from '../services/postMessage.tsx' import FilesAppIntegration from './FilesAppIntegration' +import { checkProxyStatus } from '../services/collabora' const FRAME_DOCUMENT = 'FRAME_DOCUMENT' const PostMessages = new PostMessageService({ FRAME_DOCUMENT: () => document.getElementById('collaboraframe').contentWindow, }) +const LOADING_STATE = { + LOADING: 0, + FRAME_READY: 1, + DOCUMENT_READY: 2, + + FAILED: -1, +} + +const LOADING_ERROR = { + PROXY_FAILED: 1, +} + export default { name: 'Office', components: { Avatar, Actions, ActionButton, + EmptyContent, }, props: { filename: { @@ -83,11 +111,15 @@ export default { data() { return { src: null, - loading: true, + loading: LOADING_STATE.LOADING, + error: null, views: [], } }, computed: { + useNativeHeader() { + return true + }, avatarViews() { return this.views }, @@ -98,9 +130,34 @@ export default { 'border-style': 'solid', }) }, + showIframe() { + return this.loading >= LOADING_STATE.FRAME_READY + }, + showLoadingIndicator() { + return this.loading !== LOADING_STATE.DOCUMENT_READY + }, + errorMessage() { + switch (this.error) { + case LOADING_ERROR.PROXY_FAILED: + return t('richdocuments', 'Starting the built-in CODE server failed') + default: + return t('richdocuments', 'Unknown error') + } + }, + debug() { + return !!window.TESTING + }, }, - mounted() { - const fileList = OCA?.Files?.App?.getCurrentFileList() + async mounted() { + try { + await checkProxyStatus() + } catch (e) { + this.error = LOADING_ERROR.PROXY_FAILED + this.loading = LOADING_STATE.FAILED + return + } + + const fileList = OCA?.Files?.App?.getCurrentFileList?.() FilesAppIntegration.init({ fileName: basename(this.filename), fileId: this.fileid, @@ -120,15 +177,14 @@ export default { case 'App_LoadingStatus': if (args.Status === 'Frame_Ready') { // defer showing the frame until collabora has finished also loading the document - this.loading = false + this.loading = LOADING_STATE.FRAME_READY this.$emit('update:loaded', true) FilesAppIntegration.initAfterReady() } if (args.Status === 'Document_Loaded') { - this.loading = false - this.$emit('update:loaded', true) + this.loading = LOADING_STATE.DOCUMENT_READY } else if (args.Status === 'Failed') { - this.loading = false + this.loading = LOADING_STATE.FAILED this.$emit('update:loaded', true) } break @@ -175,7 +231,7 @@ export default { if (isPublic) { this.src = getDocumentUrlForPublicFile(this.filename, this.fileid) } - this.loading = true + this.loading = LOADING_STATE.LOADING }, async share() { if (OCA.Files.Sidebar) { @@ -186,16 +242,31 @@ export default { } </script> <style lang="scss"> + #cool-loading-overlay { + border-top: 3px solid var(--color-primary-element); + position: absolute; + height: 100%; + width: 100%; + z-index: 1; + top: 0; + left: 0; + background-color: #fff; + &.debug { + opacity: .5; + } + } + .header { position: absolute; right: 44px; top: 3px; z-index: 99999; display: flex; + background-color: #fff; .avatars { display: flex; - padding: 6px; + padding: 4px; .avatardiv { margin-left: -15px; |