feat(files): add sharing icon in header

Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ 2023-09-01 12:02:17 +02:00
Родитель e2303d0cc1
Коммит e040d939e5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 60C25B8C072916CF
23 изменённых файлов: 146 добавлений и 48 удалений

Просмотреть файл

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

6
dist/9872-9872.js → dist/7761-7761.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

1
dist/7761-7761.js.map поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1
dist/9872-9872.js.map поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

4
dist/core-common.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

2
dist/core-common.js.map поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

4
dist/files-main.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

2
dist/files-main.js.map поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

4
dist/files-personal-settings.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

2
dist/files-personal-settings.js.map поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

4
dist/files_sharing-files_sharing_tab.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

2
dist/files_sharing-files_sharing_tab.js.map поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны