Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add open action for editors #11085

Merged
merged 21 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/embed-mode/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,35 @@ By default, the Embed mode allows users to select resources. In certain cases (e
</script>
```

## File picker

The File Picker mode in ownCloud Web is designed for embedding an interface that allows users to pick a single file.
This mode can be configured to restrict the file types that users can select. To enable the File Picker mode, you need
to include the embed-target=file query parameter in the iframe URL. Furthermore, you can specify allowed file types
using the embed-file-types parameter. The file types can be specified using file extensions, MIME types, or a
combination of both. If the embed-file-types parameter is not provided, all file types will be selectable by default.

### Example

```html

<iframe src="https://my-owncloud-web-instance?embed=true&embed-target=file&embed-file-types=txt,image/png"></iframe>

<script>
function selectEventHandler(event) {
if (event.data?.name !== 'owncloud-embed:file-pick') {
return
}

const file = event.data.data

doSomethingWithPickedFile(file)
}

window.addEventListener('message', selectEventHandler)
</script>
```

## Delegate authentication

If you already have a valid `access_token` that can be used to call the API from within the Embed mode and do not want to force the user to authenticate again, you can delegate the authentication. Delegating authentication will disable internal login form in ownCloud Web and will instead use events to obtain the token and update it.
Expand Down
4 changes: 3 additions & 1 deletion packages/web-app-files/src/extensions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Extension,
useCapabilityStore,
useConfigStore,
useFileActionsCopyQuickLink,
useFileActionsShowShares,
useRouter,
Expand All @@ -14,6 +15,7 @@ import { quickActionsExtensionPoint } from './extensionPoints'

export const extensions = () => {
const capabilityStore = useCapabilityStore()
const configStore = useConfigStore()
const router = useRouter()
const { search: searchFunction } = useSearch()

Expand All @@ -30,7 +32,7 @@ export const extensions = () => {
id: 'com.github.owncloud.web.files.search',
extensionPointIds: ['app.search.provider'],
type: 'search',
searchProvider: new SDKSearch(capabilityStore, router, searchFunction)
searchProvider: new SDKSearch(capabilityStore, router, searchFunction, configStore)
},
{
id: 'com.github.owncloud.web.files.quick-action.collaborator',
Expand Down
10 changes: 8 additions & 2 deletions packages/web-app-files/src/search/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import List from './list'
import { Router } from 'vue-router'
import {
CapabilityStore,
ConfigStore,
SearchFunction,
SearchList,
SearchPreview,
Expand All @@ -20,10 +21,15 @@ export default class Provider implements SearchProvider {
public readonly listSearch: SearchList
private readonly capabilityStore: CapabilityStore

constructor(capabilityStore: CapabilityStore, router: Router, searchFunction: SearchFunction) {
constructor(
capabilityStore: CapabilityStore,
router: Router,
searchFunction: SearchFunction,
configStore: ConfigStore
) {
this.id = 'files.sdk'
this.displayName = $gettext('Files')
this.previewSearch = new Preview(router, searchFunction)
this.previewSearch = new Preview(router, searchFunction, configStore)
this.listSearch = new List(searchFunction)
this.capabilityStore = capabilityStore
}
Expand Down
11 changes: 8 additions & 3 deletions packages/web-app-files/src/search/sdk/preview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SearchFunction, SearchPreview, SearchResult } from '@ownclouders/web-pkg'
import { ConfigStore, SearchFunction, SearchPreview, SearchResult } from '@ownclouders/web-pkg'
import { Component, unref } from 'vue'
import { Router } from 'vue-router'
import { ResourcePreview } from '@ownclouders/web-pkg'
Expand All @@ -9,18 +9,23 @@ export default class Preview implements SearchPreview {
public readonly component: Component
private readonly router: Router
private readonly searchFunction: SearchFunction
private readonly configStore: ConfigStore

constructor(router: Router, searchFunction: SearchFunction) {
constructor(router: Router, searchFunction: SearchFunction, configStore: ConfigStore) {
this.component = ResourcePreview
this.router = router
this.searchFunction = searchFunction
this.configStore = configStore
}

public search(term: string): Promise<SearchResult> {
return this.searchFunction(term, previewSearchLimit)
}

public get available(): boolean {
return unref(this.router.currentRoute).name !== 'search-provider-list'
return (
unref(this.router.currentRoute).name !== 'search-provider-list' &&
AlexAndBear marked this conversation as resolved.
Show resolved Hide resolved
!this.configStore.options?.embed?.enabled
)
}
}
7 changes: 4 additions & 3 deletions packages/web-app-files/tests/unit/search/sdk.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { RouteLocation, Router } from 'vue-router'
import { mock } from 'vitest-mock-extended'
import { ref } from 'vue'
import { createTestingPinia } from 'web-test-helpers/src'
import { useCapabilityStore } from '@ownclouders/web-pkg'
import { ConfigStore, useCapabilityStore } from '@ownclouders/web-pkg'

const getStore = (reports: string[] = []) => {
createTestingPinia({
Expand All @@ -14,7 +14,7 @@ const getStore = (reports: string[] = []) => {

describe('SDKProvider', () => {
it('is only available if announced via capabilities', () => {
const search = new SDKSearch(getStore(), mock<Router>(), vi.fn())
const search = new SDKSearch(getStore(), mock<Router>(), vi.fn(), mock<ConfigStore>())
expect(search.available).toBe(false)
})

Expand All @@ -30,7 +30,8 @@ describe('SDKProvider', () => {
mock<Router>({
currentRoute: ref(mock<RouteLocation>({ name: v.route }))
}),
vi.fn()
vi.fn(),
mock<ConfigStore>()
)

expect(!!search.previewSearch.available).toBe(!!v.available)
Expand Down
22 changes: 15 additions & 7 deletions packages/web-app-search/src/portals/SearchBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<oc-icon name="search" fill-type="line"></oc-icon>
</oc-button>
<oc-drop
v-if="showDrop"
id="files-global-search-options"
ref="optionsDropRef"
mode="manual"
Expand Down Expand Up @@ -239,15 +240,15 @@ export default defineComponent({
}

if (unref(optionsDrop)) {
unref(optionsDrop).hide()
unref(optionsDrop)?.hide()
}

if (unref(activePreviewIndex) === null) {
router.push(getSearchResultLocation('files.sdk'))
}
if (unref(activePreviewIndex) !== null) {
unref(optionsDrop)
.$el.querySelectorAll('.preview')
?.$el.querySelectorAll('.preview')
[unref(activePreviewIndex)].firstChild.click()
}
}
Expand Down Expand Up @@ -283,17 +284,17 @@ export default defineComponent({
if (!unref(term)) {
return
}
unref(optionsDrop).show()
unref(optionsDrop)?.show()
await search()
}

const updateTerm = (input: string) => {
restoreSearchFromRoute.value = false
term.value = input
if (!unref(term)) {
return unref(optionsDrop).hide()
return unref(optionsDrop)?.hide()
}
return unref(optionsDrop).show()
return unref(optionsDrop)?.show()
}

const debouncedSearch = debounce(search, 500)
Expand All @@ -306,6 +307,12 @@ export default defineComponent({
debouncedSearch()
})

const showDrop = computed(() => {
return unref(availableProviders).some(
(provider) => provider?.previewSearch?.available === true
)
})

return {
userContextReady,
publicLinkContextReady,
Expand All @@ -328,7 +335,8 @@ export default defineComponent({
search,
showPreview,
updateTerm,
getSearchResultLocation
getSearchResultLocation,
showDrop
}
},

Expand Down Expand Up @@ -506,7 +514,7 @@ export default defineComponent({
this.showCancelButton = false
},
hideOptionsDrop() {
this.optionsDrop.hide()
this.optionsDrop?.hide()
}
}
})
Expand Down
8 changes: 7 additions & 1 deletion packages/web-pkg/src/components/AppTemplates/AppWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import {
import { DavPermission } from '@ownclouders/web-client/webdav'
import { HttpError } from '@ownclouders/web-client'
import { dirname } from 'path'
import { useFileActionsOpenWithApp } from '../../composables/actions/files/useFileActionsOpenWithApp'

export default defineComponent({
name: 'AppWrapper',
Expand Down Expand Up @@ -135,6 +136,9 @@ export default defineComponent({
const configStore = useConfigStore()
const resourcesStore = useResourcesStore()

const { actions: openWithAppActions } = useFileActionsOpenWithApp({
appId: props.applicationId
})
const { actions: createQuickLinkActions } = useFileActionsCopyQuickLink()
const { actions: downloadFileActions } = useFileActionsDownloadFile()
const { actions: showDetailsActions } = useFileActionsShowDetails()
Expand Down Expand Up @@ -459,7 +463,9 @@ export default defineComponent({
})

const menuItemsContext = computed(() => {
return [...unref(fileActionsSave)].filter((item) => item.isVisible(unref(actionOptions)))
return [...unref(openWithAppActions), ...unref(fileActionsSave)].filter((item) =>
item.isVisible(unref(actionOptions))
)
})
const menuItemsShare = computed(() => {
return [...unref(showSharesActions), ...unref(createQuickLinkActions)].filter((item) =>
Expand Down
2 changes: 1 addition & 1 deletion packages/web-pkg/src/components/AppTopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</oc-drop>
</template>
<span v-if="hasAutosave" class="oc-flex oc-flex-middle">
<oc-icon name="refresh" color="white" v-oc-tooltip="autoSaveTooltipText" />
<oc-icon v-oc-tooltip="autoSaveTooltipText" name="refresh" color="white" />
</span>
<template v-if="mainActions.length && resource">
<context-action-menu
Expand Down
26 changes: 17 additions & 9 deletions packages/web-pkg/src/components/FilesList/ResourceTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -508,8 +508,9 @@ export default defineComponent({
const {
isLocationPicker,
isFilePicker,
postMessage,
isEnabled: isEmbedModeEnabled,
extensions: embedModeExtensions
fileTypes: embedModeFileTypes
} = useEmbedMode()

const configStore = useConfigStore()
Expand Down Expand Up @@ -540,13 +541,12 @@ export default defineComponent({
const getTagToolTip = (text: string) => (text.length > 7 ? text : '')

const isResourceDisabled = (resource: Resource) => {
if (
unref(isEmbedModeEnabled) &&
unref(embedModeExtensions)?.length &&
!unref(embedModeExtensions).includes(resource.extension) &&
!resource.isFolder
) {
return true
if (unref(isEmbedModeEnabled) && unref(embedModeFileTypes)?.length) {
return (
!unref(embedModeFileTypes).includes(resource.extension) &&
!unref(embedModeFileTypes).includes(resource.mimeType) &&
!resource.isFolder
)
}
return resource.processing === true
}
Expand Down Expand Up @@ -587,6 +587,7 @@ export default defineComponent({
space: ref(props.space),
targetRouteCallback: computed(() => props.targetRouteCallback)
}),
postMessage,
isFilePicker,
isLocationPicker,
isEmbedModeEnabled,
Expand Down Expand Up @@ -988,6 +989,13 @@ export default defineComponent({
*/
const resource = data[0]

if (this.isEmbedModeEnabled && this.isFilePicker) {
return this.postMessage<Resource>(
'owncloud-embed:file-pick',
JSON.parse(JSON.stringify(resource))
)
}

if (this.isResourceDisabled(resource)) {
return
}
Expand Down Expand Up @@ -1054,7 +1062,7 @@ export default defineComponent({
return false
}

if (this.isEmbedModeEnabled && !resource.isFolder) {
if (this.isEmbedModeEnabled && !this.isFilePicker && !resource.isFolder) {
return false
}

Expand Down
22 changes: 11 additions & 11 deletions packages/web-pkg/src/components/FilesList/ResourceTile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
:data-item-id="resource.id"
:class="{
'oc-tile-card-selected': isResourceSelected,
'oc-tile-card-disabled': resource.processing,
'state-trashed': resourceDisabled
'oc-tile-card-disabled': isResourceDisabled && !isProjectSpaceResource(resource),
'state-trashed': isResourceDisabled && isProjectSpaceResource(resource)
}"
@contextmenu="$emit('contextmenu', $event)"
>
Expand All @@ -21,7 +21,7 @@
<slot name="selection" :item="resource" />
</div>
<oc-tag
v-if="resourceDisabled"
v-if="isResourceDisabled && isProjectSpaceResource(resource)"
class="resource-disabled-indicator oc-position-absolute"
type="span"
>
Expand Down Expand Up @@ -85,7 +85,7 @@ import { computed, defineComponent, PropType } from 'vue'
import ResourceIcon from './ResourceIcon.vue'
import ResourceListItem from './ResourceListItem.vue'
import ResourceLink from './ResourceLink.vue'
import { Resource } from '@ownclouders/web-client'
import { isProjectSpaceResource, Resource } from '@ownclouders/web-client'
import { useGettext } from 'vue3-gettext'
import { isSpaceResource } from '@ownclouders/web-client'
import { isResourceTxtFileAlmostEmpty } from '../../helpers'
Expand Down Expand Up @@ -115,6 +115,11 @@ export default defineComponent({
required: false,
default: true
},
isResourceDisabled: {
type: Boolean,
required: false,
default: false
},
isExtensionDisplayed: {
type: Boolean,
default: true
Expand Down Expand Up @@ -158,11 +163,6 @@ export default defineComponent({
}
return null
})

const resourceDisabled = computed(() => {
return isSpaceResource(props.resource) && props.resource.disabled === true
})

const resourceDescription = computed(() => {
if (isSpaceResource(props.resource)) {
return props.resource.description
Expand All @@ -178,11 +178,11 @@ export default defineComponent({
statusIconAttrs,
showStatusIcon,
tooltipLabelIcon,
resourceDisabled,
resourceDescription,
shouldDisplayThumbnails
}
}
},
methods: { isProjectSpaceResource }
AlexAndBear marked this conversation as resolved.
Show resolved Hide resolved
})
</script>

Expand Down
Loading