Skip to content

Commit

Permalink
web-app-external: view mode (#9879)
Browse files Browse the repository at this point in the history
* web-app-external: open files in shares and public links in view mode by default

* Simplify logic by not handling everything in a watcher

* Show error popup when trying to switch to edit mode for read-only files

* Simplify logic some more

* Remove obsolete unref

* Remove console output

* Implement feature flag

* run linter

* test: fix unit tests

* add config to dev docs

* fixup! add config to dev docs

---------

Co-authored-by: Jannik Stehle <jannik.stehle@gmail.com>
  • Loading branch information
2 people authored and AlexAndBear committed Dec 13, 2023
1 parent 839b651 commit 5c94161
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Depending on the backend you are using, there are sample config files provided i
- `options.upload.xhr.timeout` Specifies the timeout for XHR uploads in milliseconds.
- `options.editor.autosaveEnabled` Specifies if the autosave for the editor apps is enabled.
- `options.editor.autosaveInterval` Specifies the time interval for the autosave of editor apps in seconds.
- `options.editor.openAsPreview` Specifies if non-personal files i.e. files in shares, spaces or public links are being opened in read only mode so the user needs to manually switch to edit mode. Can be set to `true`, `false` or an array of web app/editor names.
- `options.contextHelpersReadMore` Specifies whether the "Read more" link should be displayed or not.
- `options.openLinksWithDefaultApp` Specifies whether single file link shares should be opened with default app or not.
- `options.tokenStorageLocal` Specifies whether the access token will be stored in the local storage when set to `true` or in the session storage when set to `false`. If stored in the local storage, login state will be persisted across multiple browser tabs, means no additional logins are required. Defaults to `true`.
Expand Down
86 changes: 76 additions & 10 deletions packages/web-app-external/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,34 @@ import { PropType, computed, defineComponent, unref, nextTick, ref, watch, VNode
import { useTask } from 'vue-concurrency'
import { useGettext } from 'vue3-gettext'
import { Resource } from '@ownclouders/web-client/src'
import { Resource, SpaceResource } from '@ownclouders/web-client/src'
import { urlJoin } from '@ownclouders/web-client/src/utils'
import { queryItemAsString, useRequest, useRouteQuery, useStore } from '@ownclouders/web-pkg'
import { configurationManager } from '@ownclouders/web-pkg'
import {
isSameResource,
queryItemAsString,
useConfigurationManager,
useRequest,
useRouteQuery,
useStore
} from '@ownclouders/web-pkg'
import {
isProjectSpaceResource,
isPublicSpaceResource,
isShareSpaceResource
} from '@ownclouders/web-client/src/helpers'
export default defineComponent({
name: 'ExternalApp',
props: {
resource: { type: Object as PropType<Resource>, required: true }
space: { type: Object as PropType<SpaceResource>, required: true },
resource: { type: Object as PropType<Resource>, required: true },
isReadOnly: { type: Boolean, required: true }
},
emits: ['update:applicationName'],
setup(props, { emit }) {
const language = useGettext()
const store = useStore()
const configurationManager = useConfigurationManager()
const { $gettext } = language
const { makeRequest } = useRequest()
Expand Down Expand Up @@ -73,8 +87,15 @@ export default defineComponent({
})
}
const loadAppUrl = useTask(function* () {
const loadAppUrl = useTask(function* (signal, viewMode: string) {
try {
if (props.isReadOnly && viewMode === 'write') {
store.dispatch('showErrorMessage', {
title: $gettext('Cannot open file in edit mode as it is read-only')
})
return
}
const fileId = props.resource.fileId
const baseUrl = urlJoin(
configurationManager.serverUrl,
Expand All @@ -84,7 +105,8 @@ export default defineComponent({
const query = stringify({
file_id: fileId,
lang: language.current,
...(unref(applicationName) && { app_name: unref(applicationName) })
...(unref(applicationName) && { app_name: unref(applicationName) }),
...(viewMode && { view_mode: viewMode })
})
const url = `${baseUrl}?${query}`
Expand Down Expand Up @@ -132,12 +154,56 @@ export default defineComponent({
}
}).restartable()
const determineOpenAsPreview = (appName: string) => {
const openAsPreview = configurationManager.options.editor.openAsPreview
return (
openAsPreview === true || (Array.isArray(openAsPreview) && openAsPreview.includes(appName))
)
}
// switch to write mode when edit is clicked
const catchClickMicrosoftEdit = (event) => {
try {
if (JSON.parse(event.data)?.MessageId === 'UI_Edit') {
loadAppUrl.perform('write')
}
} catch (e) {}
}
watch(
props.resource,
() => {
loadAppUrl.perform()
applicationName,
(newAppName, oldAppName) => {
if (determineOpenAsPreview(newAppName) && newAppName !== oldAppName) {
window.addEventListener('message', catchClickMicrosoftEdit)
} else {
window.removeEventListener('message', catchClickMicrosoftEdit)
}
},
{
immediate: true
}
)
watch(
[props.resource],
([newResource], [oldResource]) => {
if (isSameResource(newResource, oldResource)) {
return
}
debugger
let viewMode = props.isReadOnly ? 'view' : 'write'
if (
determineOpenAsPreview(unref(applicationName)) &&
(isShareSpaceResource(props.space) ||
isPublicSpaceResource(props.space) ||
isProjectSpaceResource(props.space))
) {
viewMode = 'view'
}
loadAppUrl.perform(viewMode)
},
{ immediate: true }
{ immediate: true, deep: true }
)
return {
Expand Down
18 changes: 11 additions & 7 deletions packages/web-app-external/tests/unit/app.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { mock } from 'jest-mock-extended'
import { mock, mockDeep } from 'jest-mock-extended'
import {
createStore,
defaultPlugins,
defaultStoreMockOptions,
shallowMount
} from 'web-test-helpers'
import { useRequest, useRouteQuery } from '@ownclouders/web-pkg'
import { ConfigurationManager, useRequest, useRouteQuery } from '@ownclouders/web-pkg'
import { ref } from 'vue'

import { Resource } from '@ownclouders/web-client'
Expand All @@ -14,7 +14,15 @@ import App from '../../src/App.vue'
jest.mock('@ownclouders/web-pkg', () => ({
...jest.requireActual('@ownclouders/web-pkg'),
useRequest: jest.fn(),
useRouteQuery: jest.fn()
useRouteQuery: jest.fn(),
useConfigurationManager: () =>
mockDeep<ConfigurationManager>({
options: {
editor: {
openAsPreview: false
}
}
})
}))

const appUrl = 'https://example.test/d12ab86/loe009157-MzBw'
Expand All @@ -38,10 +46,6 @@ describe('The app provider extension', () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined)
})

afterEach(() => {
jest.clearAllMocks()
})

it('should fail for unauthenticated users', async () => {
const makeRequest = jest.fn().mockResolvedValue({
ok: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ export default defineComponent({
const slotAttrs = computed(() => ({
url: unref(url),
space: unref(unref(currentFileContext).space),
resource: unref(resource),
isDirty: unref(isDirty),
isReadOnly: unref(isReadOnly),
Expand Down
10 changes: 10 additions & 0 deletions packages/web-pkg/src/configuration/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ export class ConfigurationManager {
'openLinksWithDefaultApp',
get(options, 'openLinksWithDefaultApp', true)
)

// when this setting is enabled, non-personal files i.e. files in shares, spaces or public links
// are opened in read only mode and the user needs another click to switch to edit mode.
// it can be set to true/false or an array of web app/editor names.
set(
this.optionsConfiguration,
'editor.openAsPreview',
get(options, 'editor.openAsPreview', false)
)

set(this.optionsConfiguration, 'upload.companionUrl', get(options, 'upload.companionUrl', ''))
set(this.optionsConfiguration, 'tokenStorageLocal', get(options, 'tokenStorageLocal', true))
set(this.optionsConfiguration, 'loginUrl', get(options, 'loginUrl', ''))
Expand Down
3 changes: 3 additions & 0 deletions packages/web-pkg/src/configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export interface OptionsConfiguration {
mode?: string
isRunningOnEos?: boolean
embedTarget?: string
editor?: {
openAsPreview?: boolean | string[]
}
}

export interface OAuth2Configuration {
Expand Down

0 comments on commit 5c94161

Please sign in to comment.