Skip to content

Commit

Permalink
Move sharing indicators into own component and add extension po… (#2928)
Browse files Browse the repository at this point in the history
Move sharing indicators into own component and add extension point
  • Loading branch information
Lukas Hirt authored Jan 30, 2020
2 parents 55a9902 + a57ae75 commit 8fa39d2
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 81 deletions.
75 changes: 74 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,76 @@
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]
----
import ExampleIndicators from './components/ExampleIndicators.vue'

const appInfo = {
name: 'ExampleIndicators',
id: 'ExampleIndicators',
icon: 'info',
isFileEditor: false,
extensions: [],
filesListIndicators: [{
name: 'CustomIndicators',
component: ExampleIndicators
}]
}
----

##### ExampleIndicators 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: 'ExampleIndicators',
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 8fa39d2

Please sign in to comment.