Skip to content

Commit

Permalink
Move sharing indicators into own component and add extension point
Browse files Browse the repository at this point in the history
Moved to smaller components

Load custom indicators as part of extension

Added click handler

Use parentPath and add docs

Added missing sharesTree getter

Extended docs

Added props
  • Loading branch information
LukasHirt committed Jan 30, 2020
1 parent 6dd8f12 commit ceabe83
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 81 deletions.
73 changes: 72 additions & 1 deletion apps/files/docs/extensionpoints.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Extensionpoints
## Extension points

#### file-details-panel

Expand All @@ -17,3 +17,74 @@
OC.$extend.request('files', 'file-details-panel', payload).then( response => {});
----

### Files list status indicators

An extension can extend status indicators in files list with custom component, properties and methods.
Extending this list can be done by creating a new object inside of appInfo called `filesListIndicators`. This object can contain keys `name` and `component`.

#### Props

Please see known issues before using props in custom indicators.

- `item`: resource to which are indicators assigned
- `parentPath`: parent folder path of the item
#### Example:

##### App entry point
[source,js]
----
const appInfo = {
name: 'ExampleIndicators',
id: 'ExampleIndicators',
icon: 'info',
isFileEditor: false,
extensions: [],
filesListIndicators: [{
name: 'CustomIndicators',
component: Indicators
}]
}
----

##### Indicators component
[source,vue]
----
<template>
<oc-button
v-if="isFolder"
class="file-row-share-indicator uk-text-middle"
aria-label="Custom indicator"
@click="triggerAlert"
variation="raw"
>
<oc-icon
name="folder"
class="uk-text-middle"
size="small"
variation="active"
/>
</oc-button>
</template>

<script>
export default {
name: 'Indicators',
computed: {
isFolder () {
return this.$parent.item.type === 'folder'
}
},
methods: {
triggerAlert () {
alert('Hello world')
}
}
}
</script>
----

#### Known issues:

Passing resource as a prop into the dynamic component causes Vuex mutation error. To avoid this, access resource direcly via parent component - `this.$parent.item`
84 changes: 5 additions & 79 deletions apps/files/src/components/AllFilesList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,7 @@
class="uk-margin-small-left"
/>
</div>
<div>
<oc-button v-if="$_isUserShare(item)" class="file-row-share-indicator uk-text-middle" :aria-label="$_shareUserIconLabel(item)" @click="$_openSideBar(item, 'files-sharing')" variation="raw">
<oc-icon name="group" class="uk-text-middle" size="small" :variation="$_shareUserIconVariation(item)"/>
</oc-button>
<oc-button v-if="$_isLinkShare(item)" class="file-row-share-indicator uk-text-middle" :aria-label="$_shareLinkIconLabel(item)" @click="$_openSideBar(item, 'file-link')" variation="raw">
<oc-icon name="link" class="uk-text-middle" size="small" :variation="$_shareLinkIconVariation(item)"/>
</oc-button>
</div>
<StatusIndicators :item="item" :parentPath="currentFolder.path" @click="$_openSideBar" />
<div class="uk-text-meta uk-text-nowrap uk-width-small" :class="{ 'uk-visible@s' : !_sidebarOpen, 'uk-hidden' : _sidebarOpen }">
{{ item.size | fileSize }}
</div>
Expand Down Expand Up @@ -75,18 +68,16 @@
<script>
import FileList from './FileList.vue'
import { mapGetters, mapActions, mapState } from 'vuex'
import { shareTypes } from '../helpers/shareTypes'
import { getParentPaths } from '../helpers/path'
import Mixins from '../mixins'
import FileActions from '../fileactions'
import intersection from 'lodash/intersection'
const userShareTypes = [shareTypes.user, shareTypes.group, shareTypes.guest, shareTypes.remote]
const StatusIndicators = () => import('./FilesLists/StatusIndicators/StatusIndicators.vue')
export default {
components: {
FileList
FileList,
StatusIndicators
},
mixins: [
Mixins,
Expand All @@ -110,46 +101,6 @@ export default {
this.$emit('sideBarOpen', item, sideBarName)
},
$_isDirectUserShare (item) {
return (intersection(userShareTypes, item.shareTypes).length > 0)
},
$_isIndirectUserShare (item) {
return (item.isReceivedShare() || intersection(userShareTypes, this.$_shareTypesIndirect).length > 0)
},
$_isDirectLinkShare (item) {
return (item.shareTypes.indexOf(shareTypes.link) >= 0)
},
$_isIndirectLinkShare (item) {
return (this.$_shareTypesIndirect.indexOf(shareTypes.link) >= 0)
},
$_isUserShare (item) {
return this.$_isDirectUserShare(item) || this.$_isIndirectUserShare(item)
},
$_isLinkShare (item) {
return this.$_isDirectLinkShare(item) || this.$_isIndirectLinkShare(item)
},
$_shareUserIconVariation (item) {
return this.$_isDirectUserShare(item) ? 'active' : 'passive'
},
$_shareLinkIconVariation (item) {
return this.$_isDirectLinkShare(item) ? 'active' : 'passive'
},
$_shareUserIconLabel (item) {
return this.$_isDirectUserShare(item) ? this.$gettext('Directly shared with collaborators') : this.$gettext('Shared with collaborators through one of the parent folders')
},
$_shareLinkIconLabel (item) {
return this.$_isDirectLinkShare(item) ? this.$gettext('Directly shared with links') : this.$gettext('Shared with links through one of the parent folders')
},
$_ocFilesFolder_getFolder () {
this.setFilterTerm('')
let absolutePath
Expand Down Expand Up @@ -216,38 +167,13 @@ export default {
},
computed: {
...mapState(['route']),
...mapGetters('Files', ['loadingFolder', 'activeFiles', 'quota', 'filesTotalSize', 'activeFilesCount', 'currentFolder', 'sharesTree']),
...mapGetters('Files', ['loadingFolder', 'activeFiles', 'quota', 'filesTotalSize', 'activeFilesCount', 'currentFolder']),
...mapGetters(['configuration']),
item () {
return this.$route.params.item
},
$_shareTypesIndirect () {
const parentPaths = getParentPaths(this.currentFolder.path, true)
if (parentPaths.length === 0) {
return []
}
// remove root entry
parentPaths.pop()
const shareTypes = {}
parentPaths.forEach((parentPath) => {
// TODO: optimize for performance by skipping once we got all known types
const shares = this.sharesTree[parentPath]
if (shares) {
shares.forEach((share) => {
// note: no distinction between incoming and outgoing shares as we display the same
// indirect indicator for them
shareTypes[share.info.share_type] = true
})
}
})
return Object.keys(shareTypes).map(shareType => parseInt(shareType, 10))
},
quotaVisible () {
return (
!this.publicPage() &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<template>
<div>
<oc-button
v-for="(indicator, index) in indicators"
:key="index"
class="file-row-share-indicator uk-text-middle"
:class="{ 'uk-margin-xsmall-left' : index > 0 }"
:aria-label="indicator.label"
@click="indicator.handler(item, indicator.id)"
variation="raw"
>
<oc-icon
:name="indicator.icon"
class="uk-text-middle"
size="small"
:variation="indicator.status"
/>
</oc-button>
</div>
</template>

<script>
import intersection from 'lodash/intersection'
import { mapGetters } from 'vuex'
import { shareTypes } from '../../../helpers/shareTypes'
import { getParentPaths } from '../../../helpers/path'
const userShareTypes = [shareTypes.user, shareTypes.group, shareTypes.guest, shareTypes.remote]
export default {
name: 'StatusIndicators',
props: {
item: {
type: Object,
required: true
},
parentPath: {
type: String,
required: true
}
},
computed: {
...mapGetters('Files', ['sharesTree']),
indicators () {
const indicators = []
if (this.isUserShare(this.item)) {
indicators.push({
id: 'files-sharing',
label: this.shareUserIconLabel(this.item),
icon: 'group',
status: this.shareUserIconVariation(this.item),
handler: this.indicatorHandler
})
}
if (this.isLinkShare(this.item)) {
indicators.push({
id: 'file-link',
label: this.shareLinkIconLabel(this.item),
icon: 'link',
status: this.shareLinkIconVariation(this.item),
handler: this.indicatorHandler
})
}
return indicators
},
shareTypesIndirect () {
const parentPaths = getParentPaths(this.parentPath, true)
if (parentPaths.length === 0) {
return []
}
// remove root entry
parentPaths.pop()
const shareTypes = {}
parentPaths.forEach((parentPath) => {
// TODO: optimize for performance by skipping once we got all known types
const shares = this.sharesTree[parentPath]
if (shares) {
shares.forEach((share) => {
// note: no distinction between incoming and outgoing shares as we display the same
// indirect indicator for them
shareTypes[share.info.share_type] = true
})
}
})
return Object.keys(shareTypes).map(shareType => parseInt(shareType, 10))
}
},
methods: {
isDirectUserShare (item) {
return (intersection(userShareTypes, item.shareTypes).length > 0)
},
isIndirectUserShare (item) {
return (item.isReceivedShare() || intersection(userShareTypes, this.shareTypesIndirect).length > 0)
},
isDirectLinkShare (item) {
return (item.shareTypes.indexOf(shareTypes.link) >= 0)
},
isIndirectLinkShare () {
return (this.shareTypesIndirect.indexOf(shareTypes.link) >= 0)
},
isUserShare (item) {
return this.isDirectUserShare(item) || this.isIndirectUserShare(item)
},
isLinkShare (item) {
return this.isDirectLinkShare(item) || this.isIndirectLinkShare(item)
},
shareUserIconVariation (item) {
return this.isDirectUserShare(item) ? 'active' : 'passive'
},
shareLinkIconVariation (item) {
return this.isDirectLinkShare(item) ? 'active' : 'passive'
},
shareUserIconLabel (item) {
return this.isDirectUserShare(item) ? this.$gettext('Directly shared with collaborators') : this.$gettext('Shared with collaborators through one of the parent folders')
},
shareLinkIconLabel (item) {
return this.isDirectLinkShare(item) ? this.$gettext('Directly shared with links') : this.$gettext('Shared with links through one of the parent folders')
},
indicatorHandler (item, sideBarName) {
this.$emit('click', item, sideBarName)
}
}
}
</script>
Loading

0 comments on commit ceabe83

Please sign in to comment.