Skip to content

Commit

Permalink
feat: add location picker into embed mode
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasHirt committed Oct 25, 2023
1 parent 8d7e7c7 commit 8a645c5
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 12 deletions.
36 changes: 36 additions & 0 deletions docs/embed-mode/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,39 @@ The app is emitting various events depending on the goal of the user. All events
| **owncloud-embed:select** | Resource[] | Gets emitted when user selects resources via the "Attach as copy" action |
| **owncloud-embed:share** | string[] | Gets emitted when user selects resources and shares them via the "Share links" action |
| **owncloud-embed:cancel** | void | Gets emitted when user attempts to close the embedded instance via "Cancel" action |

### Example

```html
<iframe src="https://my-owncloud-web-instance?mode=embed"></iframe>

<script>
function selectEventHandler(event) {
const resources = event.detail
doSomethingWithSelectedResources(resources)
}
window.addEventListener('owncloud-embed:select', selectEventHandler)
</script>
```

## Location picker

By default, the Embed mode allows users to select resources. In certain cases (e.g. uploading a file), this needs to be changed to allow selecting a location. This can be achieved by running the embed mode with additional parameter `embed-target=location`. With this parameter, resource selection is disabled and the selected resources array always includes the current folder as the only item.

### Example

```html
<iframe src="https://my-owncloud-web-instance?mode=embed&embed-target=location"></iframe>

<script>
function selectEventHandler(event) {
const currentFolder = event.detail[0]
uploadIntoCurrentFolder(currentFolder)
}
window.addEventListener('owncloud-embed:select', selectEventHandler)
</script>
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
$gettext('Cancel')
}}</oc-button>
<oc-button
v-if="!isLocationPicker"
key="btn-share"
data-testid="button-share"
variation="inverse"
appearance="filled"
Expand All @@ -17,7 +19,7 @@
appearance="filled"
:disabled="areSelectActionsDisabled"
@click="emitSelect"
>{{ $gettext('Attach as copy') }}</oc-button
>{{ selectLabel }}</oc-button
>
</section>
</template>
Expand All @@ -29,6 +31,7 @@ import {
showQuickLinkPasswordModal,
useAbility,
useClientService,
useEmbedMode,
usePasswordPolicyService,
useStore
} from '@ownclouders/web-pkg'
Expand All @@ -42,15 +45,22 @@ export default {
const clientService = useClientService()
const passwordPolicyService = usePasswordPolicyService()
const language = useGettext()
const { isLocationPicker } = useEmbedMode()
const selectedFiles = computed<Resource[]>(() => {
if (isLocationPicker.value) return [store.getters['Files/currentFolder']]
return store.getters['Files/selectedFiles']
})
const areSelectActionsDisabled = computed<boolean>(() => selectedFiles.value.length < 1)
const canCreatePublicLinks = computed<boolean>(() => ability.can('create-all', 'PublicLink'))
const selectLabel = computed<string>(() =>
isLocationPicker.value ? language.$gettext('Choose') : language.$gettext('Attach as copy')
)
const emitSelect = (): void => {
const event: CustomEvent<Resource[]> = new CustomEvent('owncloud-embed:select', {
detail: selectedFiles.value
Expand Down Expand Up @@ -132,6 +142,8 @@ export default {
return {
areSelectActionsDisabled,
canCreatePublicLinks,
isLocationPicker,
selectLabel,
sharePublicLinks,
emitCancel,
emitSelect
Expand Down
7 changes: 5 additions & 2 deletions packages/web-app-files/src/views/spaces/GenericSpace.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
>
<template #actions="{ limitedScreenSpace }">
<create-and-upload
v-if="!isLocationPicker"
:space="space"
:item="item"
:item-id="itemId"
Expand Down Expand Up @@ -152,7 +153,7 @@ import {
SpaceResource
} from '@ownclouders/web-client/src/helpers'
import { useFileActions } from '@ownclouders/web-pkg'
import { useEmbedMode, useFileActions } from '@ownclouders/web-pkg'
import { AppBar } from '@ownclouders/web-pkg'
import { ContextActions } from '@ownclouders/web-pkg'
Expand Down Expand Up @@ -247,6 +248,7 @@ export default defineComponent({
const clientService = useClientService()
const hasShareJail = useCapabilityShareJailEnabled()
const { breadcrumbsFromPath, concatBreadcrumbs } = useBreadcrumbsFromPath()
const { isLocationPicker } = useEmbedMode()
let loadResourcesEventToken
Expand Down Expand Up @@ -520,7 +522,8 @@ export default defineComponent({
),
whitespaceContextMenu,
clientService,
hasShareJail
hasShareJail,
isLocationPicker
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ describe('EmbedActions', () => {
payload: { detail: [{ id: 1 }] }
})
})

it('should enable select action when embedTarget is set to location', () => {
const { wrapper } = getWrapper({ configuration: { options: { embedTarget: 'location' } } })

expect(wrapper.find(selectors.btnSelect).attributes()).not.toHaveProperty('disabled')
})

it('should emit select event with currentFolder as selected resource when select action is triggered', async () => {
window.parent.dispatchEvent = jest.fn()
global.CustomEvent = jest.fn().mockImplementation(mockCustomEvent)

const { wrapper } = getWrapper({
currentFolder: { id: 1 },
configuration: { options: { embedTarget: 'location' } }
})

await wrapper.find(selectors.btnSelect).trigger('click')

expect(window.parent.dispatchEvent).toHaveBeenCalledWith({
name: 'owncloud-embed:select',
payload: { detail: [{ id: 1 }] }
})
})
})

describe('cancel action', () => {
Expand Down Expand Up @@ -127,26 +150,43 @@ describe('EmbedActions', () => {
payload: { detail: ['password-link-1'] }
})
})

it('should hide share action when embedTarget is set to location', () => {
const { wrapper } = getWrapper({ configuration: { options: { embedTarget: 'location' } } })

expect(wrapper.find(selectors.btnShare).exists()).toBe(false)
})
})
})

function getWrapper(
{ selectedFiles = [], abilities = [], capabilities = jest.fn().mockReturnValue({}) } = {
{
selectedFiles = [],
abilities = [],
capabilities = jest.fn().mockReturnValue({}),
configuration = { options: {} },
currentFolder = {}
} = {
selectedFiles: [],
abilities: [],
capabilities: jest.fn().mockReturnValue({})
}
) {
const storeOptions = {
...defaultStoreMockOptions,
getters: { ...defaultStoreMockOptions.getters, capabilities },
getters: {
...defaultStoreMockOptions.getters,
capabilities,
configuration: jest.fn().mockReturnValue(configuration || { options: {} })
},
modules: {
...defaultStoreMockOptions.modules,
Files: {
...defaultStoreMockOptions.modules.Files,
getters: {
...defaultStoreMockOptions.modules.Files.getters,
selectedFiles: jest.fn().mockReturnValue(selectedFiles)
selectedFiles: jest.fn().mockReturnValue(selectedFiles),
currentFolder: jest.fn().mockReturnValue(currentFolder)
}
}
}
Expand Down
11 changes: 7 additions & 4 deletions packages/web-pkg/src/components/FilesList/ResourceTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
@sort="sort"
@update:model-value="$emit('update:modelValue', $event)"
>
<template #selectHeader>
<template v-if="!isLocationPicker" #selectHeader>
<div class="resource-table-select-all">
<oc-checkbox
id="resource-table-select-all"
Expand All @@ -39,7 +39,7 @@
/>
</div>
</template>
<template #select="{ item }">
<template v-if="!isLocationPicker" #select="{ item }">
<oc-checkbox
:id="`resource-table-select-${resourceDomSelector(item)}`"
:label="getResourceCheckboxLabel(item)"
Expand Down Expand Up @@ -227,7 +227,8 @@ import {
ViewModeConstants,
useConfigurationManager,
useGetMatchingSpace,
useFolderLink
useFolderLink,
useEmbedMode
} from '../../composables'
import { EVENT_TROW_MOUNTED, EVENT_FILE_DROPPED, ImageDimension } from '../../constants'
import { eventBus } from '../../services'
Expand Down Expand Up @@ -441,6 +442,7 @@ export default defineComponent({
const configurationManager = useConfigurationManager()
const { getMatchingSpace } = useGetMatchingSpace()
const { $gettext } = useGettext()
const { isLocationPicker } = useEmbedMode()
const { width } = useWindowSize()
const hasTags = computed(
Expand Down Expand Up @@ -487,7 +489,8 @@ export default defineComponent({
...useFolderLink({
space: ref(props.space),
targetRouteCallback: computed(() => props.targetRouteCallback)
})
}),
isLocationPicker
}
},
data() {
Expand Down
6 changes: 5 additions & 1 deletion packages/web-pkg/src/composables/embedMode/useEmbedMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@ export const useEmbedMode = () => {

const isEnabled = computed<boolean>(() => store.getters.configuration.options.mode === 'embed')

return { isEnabled }
const isLocationPicker = computed<boolean>(() => {
return store.getters.configuration.options.embedTarget === 'location'
})

return { isEnabled, isLocationPicker }
}
1 change: 1 addition & 0 deletions packages/web-pkg/src/configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface OptionsConfiguration {
tokenStorageLocal?: boolean
disabledExtensions?: string[]
mode?: string
embedTarget?: string
}

export interface OAuth2Configuration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,26 @@ describe('ResourceTable', () => {
expect((wrapper.emitted('update:selectedIds')[0][0] as any).length).toBe(0)
})
})

describe('embed mode location target', () => {
it('should not hide checkboxes when embed mode does not have location as target', () => {
const { wrapper } = getMountedWrapper({
configuration: { options: { embedTarget: undefined } }
})

expect(wrapper.find('.resource-table-select-all').exists()).toBe(true)
expect(wrapper.find('.resource-table-select-all .oc-checkbox').exists()).toBe(true)
})

it('should hide checkboxes when embed mode has location as target', () => {
const { wrapper } = getMountedWrapper({
configuration: { options: { embedTarget: 'location' } }
})

expect(wrapper.find('.resource-table-select-all').exists()).toBe(false)
expect(wrapper.find('.resource-table-select-all .oc-checkbox').exists()).toBe(false)
})
})
})

describe('resource activation', () => {
Expand Down Expand Up @@ -429,7 +449,8 @@ describe('ResourceTable', () => {
function getMountedWrapper({
props = {},
isUserContextReady = true,
addProcessingResources = false
addProcessingResources = false,
configuration = { options: {} }
} = {}) {
const storeOptions = defaultStoreMockOptions
storeOptions.modules.runtime.modules.auth.getters.isUserContextReady.mockReturnValue(
Expand All @@ -440,6 +461,17 @@ function getMountedWrapper({
tags: true
}
}))
storeOptions.getters.configuration.mockImplementation(() => ({
currentTheme: { general: { slogan: '' } },
...configuration,
options: {
editor: {
autosaveEnabled: false,
autosaveInterval: 120
},
...configuration?.options
}
}))

const store = createStore(storeOptions)

Expand Down
7 changes: 7 additions & 0 deletions packages/web-runtime/src/container/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export const announceConfiguration = async (path: string): Promise<RuntimeConfig
rawConfig.options = { ...rawConfig.options, mode: getQueryParam('mode') ?? 'web' }
}

// Can enable location picker in embed mode
const embedTarget = getQueryParam('embed-target')

if (embedTarget) {
rawConfig.options.embedTarget = embedTarget
}

configurationManager.initialize(rawConfig)
// TODO: we might want to get rid of exposing the raw config. needs more refactoring though.
return rawConfig
Expand Down

0 comments on commit 8a645c5

Please sign in to comment.