feat(files): add sharing icon in header

Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
pull/40192/head
John Molakvoæ 2 years ago
parent e2303d0cc1
commit e040d939e5
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
  1. 6
      apps/files/src/actions/sidebarAction.ts
  2. 22
      apps/files/src/components/FileEntry.vue
  3. 15
      apps/files/src/components/FilesListVirtual.vue
  4. 84
      apps/files/src/views/FilesList.vue
  5. 13
      apps/sharebymail/lib/Capabilities.php
  6. 9
      apps/sharebymail/tests/CapabilitiesTest.php
  7. 1
      cypress/e2e/files_versions/version_creation.cy.ts
  8. 6
      dist/7761-7761.js
  9. 0
      dist/7761-7761.js.LICENSE.txt
  10. 1
      dist/7761-7761.js.map
  11. 1
      dist/9872-9872.js.map
  12. 4
      dist/core-common.js
  13. 2
      dist/core-common.js.map
  14. 4
      dist/federatedfilesharing-vue-settings-admin.js
  15. 2
      dist/federatedfilesharing-vue-settings-admin.js.map
  16. 4
      dist/files-main.js
  17. 2
      dist/files-main.js.map
  18. 4
      dist/files-personal-settings.js
  19. 2
      dist/files-personal-settings.js.map
  20. 4
      dist/files_sharing-files_sharing_tab.js
  21. 2
      dist/files_sharing-files_sharing_tab.js.map
  22. 4
      dist/updatenotification-updatenotification.js
  23. 2
      dist/updatenotification-updatenotification.js.map

@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { Permission, type Node, View, registerFileAction, FileAction } from '@nextcloud/files'
import { Permission, type Node, View, registerFileAction, FileAction, FileType } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import InformationSvg from '@mdi/svg/svg/information-variant.svg?raw'
@ -51,7 +51,7 @@ export const action = new FileAction({
return (nodes[0].root?.startsWith('/files/') && nodes[0].permissions !== Permission.NONE) ?? false
},
async exec(node: Node, view: View) {
async exec(node: Node, view: View, dir: string) {
try {
// TODO: migrate Sidebar to use a Node instead
await window.OCA.Files.Sidebar.open(node.path)
@ -60,7 +60,7 @@ export const action = new FileAction({
window.OCP.Files.Router.goToRoute(
null,
{ view: view.id, fileid: node.fileid },
{ dir: node.dirname },
{ dir },
true,
)

@ -166,12 +166,15 @@
</template>
<script lang='ts'>
import type { PropType } from 'vue'
import type { Node } from '@nextcloud/files'
import { CancelablePromise } from 'cancelable-promise'
import { debounce } from 'debounce'
import { emit } from '@nextcloud/event-bus'
import { extname } from 'path'
import { generateUrl } from '@nextcloud/router'
import { getFileActions, DefaultType, FileType, formatFileSize, Permission, NodeStatus } from '@nextcloud/files'
import { getFileActions, DefaultType, FileType, formatFileSize, Permission, Folder, File } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate } from '@nextcloud/l10n'
import { vOnClickOutside } from '@vueuse/components'
@ -186,7 +189,7 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import Vue from 'vue'
import { ACTION_DETAILS } from '../actions/sidebarAction.ts'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { hashCode } from '../utils/hashUtils.ts'
import { isCachedPreview } from '../services/PreviewService.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
@ -235,7 +238,7 @@ export default Vue.extend({
default: false,
},
source: {
type: Object,
type: [Folder, File, Node] as PropType<Node>,
required: true,
},
index: {
@ -243,7 +246,7 @@ export default Vue.extend({
required: true,
},
nodes: {
type: Array,
type: Array as PropType<Node[]>,
required: true,
},
filesListWidth: {
@ -295,7 +298,7 @@ export default Vue.extend({
currentDir() {
// Remove any trailing slash but leave root slash
return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
return (this.$route?.query?.dir?.toString() || '/').replace(/^(.+)\/$/, '$1')
},
currentFileId() {
return this.$route.params.fileid || this.$route.query.fileid || null
@ -660,11 +663,10 @@ export default Vue.extend({
},
openDetailsIfAvailable(event) {
const detailsAction = this.enabledActions.find(action => action.id === ACTION_DETAILS)
if (detailsAction) {
event.preventDefault()
event.stopPropagation()
detailsAction.exec(this.source, this.currentView)
event.preventDefault()
event.stopPropagation()
if (sidebarAction?.enabled?.([this.source], this.currentView)) {
sidebarAction.exec(this.source, this.currentView, this.currentDir)
}
},

@ -67,11 +67,13 @@
</template>
<script lang="ts">
import type { PropType } from 'vue'
import type { Node } from '@nextcloud/files'
import { translate, translatePlural } from '@nextcloud/l10n'
import { getFileListHeaders, type Node } from '@nextcloud/files'
import { getFileListHeaders, Folder, View } from '@nextcloud/files'
import { showError } from '@nextcloud/dialogs'
import Vue from 'vue'
import VirtualList from './VirtualList.vue'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
import FileEntry from './FileEntry.vue'
@ -80,6 +82,7 @@ import FilesListTableFooter from './FilesListTableFooter.vue'
import FilesListTableHeader from './FilesListTableHeader.vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
import logger from '../logger.js'
import VirtualList from './VirtualList.vue'
export default Vue.extend({
name: 'FilesListVirtual',
@ -97,15 +100,15 @@ export default Vue.extend({
props: {
currentView: {
type: Object,
type: View,
required: true,
},
currentFolder: {
type: Object,
type: Folder,
required: true,
},
nodes: {
type: Array,
type: Array as PropType<Node[]>,
required: true,
},
},
@ -179,7 +182,7 @@ export default Vue.extend({
const node = this.nodes.find(n => n.fileid === this.fileId) as Node
if (node && sidebarAction?.enabled?.([node], this.currentView)) {
logger.debug('Opening sidebar on file ' + node.path, { node })
sidebarAction.exec(node, this.currentView, this.currentFolder)
sidebarAction.exec(node, this.currentView, this.currentFolder.path)
}
}
},

@ -25,8 +25,20 @@
<!-- Current folder breadcrumbs -->
<BreadCrumbs :path="dir" @reload="fetchContent">
<template #actions>
<NcButton v-if="canShare"
:aria-label="shareButtonLabel"
:class="{ 'files-list__header-share-button--shared': shareButtonType }"
:title="shareButtonLabel"
class="files-list__header-share-button"
type="tertiary"
@click="openSharingSidebar">
<template #icon>
<LinkIcon v-if="shareButtonType === Type.SHARE_TYPE_LINK" />
<ShareVariantIcon v-else :size="20" />
</template>
</NcButton>
<!-- Uploader -->
<UploadPicker v-if="currentFolder"
<UploadPicker v-if="currentFolder && canUpload"
:content="dirContents"
:destination="currentFolder"
:multiple="true"
@ -77,18 +89,24 @@ import type { Upload } from '@nextcloud/upload'
import type { UserConfig } from '../types.ts'
import type { View, ContentsWithRoot } from '@nextcloud/files'
import { Folder, Node } from '@nextcloud/files'
import { Folder, Node, Permission } from '@nextcloud/files'
import { getCapabilities } from '@nextcloud/capabilities'
import { join, dirname } from 'path'
import { orderBy } from 'natural-orderby'
import { translate } from '@nextcloud/l10n'
import { UploadPicker } from '@nextcloud/upload'
import { Type } from '@nextcloud/sharing'
import Vue from 'vue'
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import Vue from 'vue'
import LinkIcon from 'vue-material-design-icons/Link.vue'
import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { useFilesStore } from '../store/files.ts'
import { usePathsStore } from '../store/paths.ts'
import { useSelectionStore } from '../store/selection.ts'
@ -100,17 +118,21 @@ import FilesListVirtual from '../components/FilesListVirtual.vue'
import filesSortingMixin from '../mixins/filesSorting.ts'
import logger from '../logger.js'
const isSharingEnabled = getCapabilities()?.files_sharing !== undefined
export default Vue.extend({
name: 'FilesList',
components: {
BreadCrumbs,
FilesListVirtual,
LinkIcon,
NcAppContent,
NcButton,
NcEmptyContent,
NcIconSvgWrapper,
NcLoadingIcon,
ShareVariantIcon,
UploadPicker,
},
@ -139,6 +161,7 @@ export default Vue.extend({
return {
loading: true,
promise: null,
Type,
}
},
@ -157,7 +180,7 @@ export default Vue.extend({
*/
dir(): string {
// Remove any trailing slash but leave root slash
return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
return (this.$route?.query?.dir?.toString() || '/').replace(/^(.+)\/$/, '$1')
},
/**
@ -242,6 +265,43 @@ export default Vue.extend({
const dir = this.dir.split('/').slice(0, -1).join('/') || '/'
return { ...this.$route, query: { dir } }
},
shareAttributes(): number[]|undefined {
if (!this.currentFolder?.attributes?.['share-types']) {
return undefined
}
return Object.values(this.currentFolder?.attributes?.['share-types'] || {}).flat() as number[]
},
shareButtonLabel() {
if (!this.shareAttributes) {
return this.t('files', 'Share')
}
if (this.shareButtonType === Type.SHARE_TYPE_LINK) {
return this.t('files', 'Shared by link')
}
return this.t('files', 'Shared')
},
shareButtonType(): Type|null {
if (!this.shareAttributes) {
return null
}
// If all types are links, show the link icon
if (this.shareAttributes.some(type => type === Type.SHARE_TYPE_LINK)) {
return Type.SHARE_TYPE_LINK
}
return Type.SHARE_TYPE_USER
},
canUpload() {
return this.currentFolder && (this.currentFolder.permissions & Permission.CREATE) !== 0
},
canShare() {
return isSharingEnabled
&& this.currentFolder && (this.currentFolder.permissions & Permission.SHARE) !== 0
},
},
watch: {
@ -348,6 +408,13 @@ export default Vue.extend({
}
},
openSharingSidebar() {
if (window?.OCA?.Files?.Sidebar?.setActiveTab) {
window.OCA.Files.Sidebar.setActiveTab('sharing')
}
sidebarAction.exec(this.currentFolder, this.currentView, this.currentFolder.path)
},
t: translate,
},
})
@ -378,12 +445,21 @@ $navigationToggleSize: 50px;
// Only the breadcrumbs shrinks
flex: 0 0;
}
&-share-button {
opacity: .3;
&--shared {
opacity: 1;
}
}
}
&__refresh-icon {
flex: 0 0 44px;
width: 44px;
height: 44px;
}
&__loading-icon {
margin: auto;
}

@ -28,6 +28,7 @@ declare(strict_types=1);
namespace OCA\ShareByMail;
use OCA\ShareByMail\Settings\SettingsManager;
use OCP\App\IAppManager;
use OCP\Capabilities\ICapability;
use OCP\Share\IManager;
@ -39,10 +40,15 @@ class Capabilities implements ICapability {
/** @var SettingsManager */
private $settingsManager;
/** @var IAppManager */
private $appManager;
public function __construct(IManager $manager,
SettingsManager $settingsManager) {
SettingsManager $settingsManager,
IAppManager $appManager) {
$this->manager = $manager;
$this->settingsManager = $settingsManager;
$this->appManager = $appManager;
}
/**
@ -64,9 +70,12 @@ class Capabilities implements ICapability {
* },
* }
* }
* }
* }|array<empty>
*/
public function getCapabilities(): array {
if (!$this->appManager->isEnabledForUser('files_sharing')) {
return [];
}
return [
'files_sharing' =>
[

@ -27,6 +27,7 @@ namespace OCA\ShareByMail\Tests;
use OCA\ShareByMail\Capabilities;
use OCA\ShareByMail\Settings\SettingsManager;
use OCP\App\IAppManager;
use OCP\Share\IManager;
use Test\TestCase;
@ -40,13 +41,17 @@ class CapabilitiesTest extends TestCase {
/** @var IManager | \PHPUnit\Framework\MockObject\MockObject */
private $settingsManager;
/** @var IAppManager | \PHPUnit\Framework\MockObject\MockObject */
private $appManager;
protected function setUp(): void {
parent::setUp();
$this->manager = $this::createMock(IManager::class);
$this->settingsManager = $this::createMock(SettingsManager::class);
$this->capabilities = new Capabilities($this->manager, $this->settingsManager);
$this->appManager = $this::createMock(IAppManager::class);
$this->capabilities = new Capabilities($this->manager, $this->settingsManager, $this->appManager);
}
public function testGetCapabilities() {
@ -58,6 +63,8 @@ class CapabilitiesTest extends TestCase {
->willReturn(false);
$this->settingsManager->method('sendPasswordByMail')
->willReturn(true);
$this->appManager->method('isEnabledForUser')
->willReturn(true);
$capabilities = [
'files_sharing' =>

@ -38,6 +38,7 @@ describe('Versions creation', () => {
})
it('Opens the versions panel and sees the versions', () => {
cy.visit('/apps/files')
openVersionsPanel(randomFileName)
cy.get('#tab-version_vue').within(() => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save