Browse Source

Merge pull request #40192 from nextcloud/feat/sharing-icon-bread

tags/v28.0.0beta1
John Molakvoæ 8 months ago
parent
commit
fe692f2c7f
No account linked to committer's email address

+ 3
- 3
apps/files/src/actions/sidebarAction.ts View File

@@ -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,
)


+ 12
- 10
apps/files/src/components/FileEntry.vue View File

@@ -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)
}
},


+ 9
- 6
apps/files/src/components/FilesListVirtual.vue View File

@@ -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)
}
}
},

+ 1
- 1
apps/files/src/newMenu/newFolder.ts View File

@@ -69,7 +69,7 @@ const entry = {
iconSvgInline: FolderPlusSvg,
async handler(context: Folder, content: Node[]) {
const contentNames = content.map((node: Node) => node.basename)
const name = getUniqueName(t('files', 'New Folder'), contentNames)
const name = getUniqueName(t('files', 'New folder'), contentNames)
const { fileid, source } = await createNewFolder(context.source, name)

// Create the folder in the store

+ 80
- 4
apps/files/src/views/FilesList.vue View File

@@ -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;
}

+ 11
- 2
apps/sharebymail/lib/Capabilities.php View File

@@ -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' =>
[

+ 8
- 1
apps/sharebymail/tests/CapabilitiesTest.php View File

@@ -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' =>

+ 1
- 0
cypress/e2e/files_versions/version_creation.cy.ts View File

@@ -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(() => {

dist/7761-7761.js
File diff suppressed because it is too large
View File


dist/9872-9872.js.LICENSE.txt → dist/7761-7761.js.LICENSE.txt View File


+ 1
- 0
dist/7761-7761.js.map
File diff suppressed because it is too large
View File


+ 0
- 1
dist/9872-9872.js.map
File diff suppressed because it is too large
View File


+ 2
- 2
dist/core-common.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/core-common.js.map
File diff suppressed because it is too large
View File


+ 2
- 2
dist/federatedfilesharing-vue-settings-admin.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/federatedfilesharing-vue-settings-admin.js.map
File diff suppressed because it is too large
View File


+ 2
- 2
dist/files-main.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/files-main.js.map
File diff suppressed because it is too large
View File


+ 2
- 2
dist/files-personal-settings.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/files-personal-settings.js.map
File diff suppressed because it is too large
View File


+ 2
- 2
dist/files_sharing-files_sharing_tab.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/files_sharing-files_sharing_tab.js.map
File diff suppressed because it is too large
View File


+ 2
- 2
dist/updatenotification-updatenotification.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/updatenotification-updatenotification.js.map
File diff suppressed because it is too large
View File


Loading…
Cancel
Save