Skip to content

Commit

Permalink
feat(files): migrate template picker
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Oct 17, 2023
1 parent ad7d20e commit 4b55594
Show file tree
Hide file tree
Showing 104 changed files with 1,342 additions and 295 deletions.
4 changes: 2 additions & 2 deletions apps/files/src/actions/openInFilesAction.spec.ts
Expand Up @@ -76,7 +76,7 @@ describe('Open in files action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(goToRouteMock).toBeCalledTimes(1)
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { fileid: 1, dir: '/Foo' })
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { dir: '/Foo' })
})

test('Open in files with folder', async () => {
Expand All @@ -96,6 +96,6 @@ describe('Open in files action execute tests', () => {
// Silent action
expect(exec).toBe(null)
expect(goToRouteMock).toBeCalledTimes(1)
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { fileid: 1, dir: '/Foo/Bar' })
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { dir: '/Foo/Bar' })
})
})
2 changes: 1 addition & 1 deletion apps/files/src/actions/openInFilesAction.ts
Expand Up @@ -42,7 +42,7 @@ export const action = new FileAction({
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: node.fileid },
{ dir, fileid: node.fileid },
{ dir },
)
return null
},
Expand Down
2 changes: 1 addition & 1 deletion apps/files/src/components/FileEntry.vue
Expand Up @@ -620,7 +620,7 @@ export default Vue.extend({
canDrag() {
const canDrag = (node: Node): boolean => {
return (node.permissions & Permission.UPDATE) !== 0
return (node?.permissions & Permission.UPDATE) !== 0
}
// If we're dragging a selection, we need to check all files
Expand Down
2 changes: 1 addition & 1 deletion apps/files/src/components/FilesListTableHeaderButton.vue
Expand Up @@ -22,7 +22,7 @@
<template>
<NcButton :aria-label="sortAriaLabel(name)"
:class="{'files-list__column-sort-button--active': sortingMode === mode}"
:alignment="mode !== 'size' ? 'start-reverse' : 'center'"
:alignment="mode !== 'size' ? 'start-reverse' : undefined"
class="files-list__column-sort-button"
type="tertiary"
@click.stop.prevent="toggleSortBy(mode)">
Expand Down
50 changes: 31 additions & 19 deletions apps/files/src/components/FilesListVirtual.vue
Expand Up @@ -145,7 +145,7 @@ export default Vue.extend({
},
fileId() {
return parseInt(this.$route.params.fileid || this.$route.query.fileid) || null
return parseInt(this.$route.params.fileid) || null
},
summaryFile() {
Expand Down Expand Up @@ -187,35 +187,47 @@ export default Vue.extend({
},
},
watch: {
fileId(fileId) {
this.scrollToFile(fileId, false)
},
},
mounted() {
// Add events on parent to cover both the table and DragAndDrop notice
const mainContent = window.document.querySelector('main.app-content') as HTMLElement
mainContent.addEventListener('dragover', this.onDragOver)
mainContent.addEventListener('dragleave', this.onDragLeave)
// Scroll to the file if it's in the url
if (this.fileId) {
const index = this.nodes.findIndex(node => node.fileid === this.fileId)
if (index === -1 && this.fileId !== this.currentFolder.fileid) {
showError(this.t('files', 'File not found'))
}
this.scrollToIndex = Math.max(0, index)
}
this.scrollToFile(this.fileId)
this.openSidebarForFile(this.fileId)
},
methods: {
// Open the file sidebar if we have the room for it
// but don't open the sidebar for the current folder
if (document.documentElement.clientWidth > 1024 && this.currentFolder.fileid !== this.fileId) {
// Open the sidebar for the given URL fileid
// iif we just loaded the app.
const node = this.nodes.find(n => n.fileid === this.fileId) as NcNode
if (node && sidebarAction?.enabled?.([node], this.currentView)) {
logger.debug('Opening sidebar on file ' + node.path, { node })
sidebarAction.exec(node, this.currentView, this.currentFolder.path)
openSidebarForFile(fileId) {
if (document.documentElement.clientWidth > 1024 && this.currentFolder.fileid !== fileId) {
// Open the sidebar for the given URL fileid
// iif we just loaded the app.
const node = this.nodes.find(n => n.fileid === fileId) as NcNode
if (node && sidebarAction?.enabled?.([node], this.currentView)) {
logger.debug('Opening sidebar on file ' + node.path, { node })
sidebarAction.exec(node, this.currentView, this.currentFolder.path)
}
}
}
},
},
scrollToFile(fileId: number, warn = true) {
if (fileId) {
const index = this.nodes.findIndex(node => node.fileid === fileId)
if (warn && index === -1 && fileId !== this.currentFolder.fileid) {
showError(this.t('files', 'File not found'))
}
this.scrollToIndex = Math.max(0, index)
}
},
methods: {
getFileId(node) {
return node.fileid
},
Expand Down
116 changes: 59 additions & 57 deletions apps/files/src/templates.js → apps/files/src/init-templates.ts
Expand Up @@ -20,17 +20,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import type { Entry } from '@nextcloud/files'

import { Folder, Node, Permission, addNewFileMenuEntry, removeNewFileMenuEntry } from '@nextcloud/files'
import { generateOcsUrl } from '@nextcloud/router'
import { getLoggerBuilder } from '@nextcloud/logger'
import { join } from 'path'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentDirectory } from './utils/davUtils.js'
import axios from '@nextcloud/axios'
import Vue from 'vue'

import PlusSvg from '@mdi/svg/svg/plus.svg?raw'

import TemplatePickerView from './views/TemplatePicker.vue'
import { showError } from '@nextcloud/dialogs'
import { getUniqueName } from './newMenu/newFolder'
import { getCurrentUser } from '@nextcloud/auth'

// Set up logger
const logger = getLoggerBuilder()
Expand Down Expand Up @@ -66,67 +72,59 @@ const TemplatePicker = new View({
},
})
TemplatePicker.$mount('#template-picker')
if (!templatesPath) {
logger.debug('Templates folder not initialized')
addNewFileMenuEntry({
id: 'template-picker',
displayName: t('files', 'Create new templates folder'),
iconSvgInline: PlusSvg,
order: 10,
enabled(context: Folder): boolean {
// Allow creation on your own folders only
if (context.owner !== getCurrentUser()?.uid) {
return false
}
return (context.permissions & Permission.CREATE) !== 0
},
handler(context: Folder, content: Node[]) {
// Check for conflicts
const contentNames = content.map((node: Node) => node.basename)
const name = getUniqueName(t('files', 'Templates'), contentNames)

// Init template engine after load to make sure it's the last injected entry
window.addEventListener('DOMContentLoaded', function() {
if (!templatesPath) {
logger.debug('Templates folder not initialized')
const initTemplatesPlugin = {
attach(menu) {
// register the new menu entry
menu.addMenuEntry({
id: 'template-init',
displayName: t('files', 'Set up templates folder'),
templateName: t('files', 'Templates'),
iconClass: 'icon-template-add',
fileType: 'file',
actionLabel: t('files', 'Create new templates folder'),
actionHandler(name) {
initTemplatesFolder(name)
menu.removeMenuEntry('template-init')
},
})
},
}
OC.Plugins.register('OCA.Files.NewFileMenu', initTemplatesPlugin)
}
})
// Create the template folder
initTemplatesFolder(context, name)

// Remove the menu entry
removeNewFileMenuEntry('template-picker')
},
} as Entry)
}

// Init template files menu
templates.forEach((provider, index) => {
const newTemplatePlugin = {
attach(menu) {
const fileList = menu.fileList

// only attach to main file list, public view is not supported yet
if (fileList.id !== 'files' && fileList.id !== 'files.public') {
return
}
addNewFileMenuEntry({
id: `template-new-${provider.app}-${index}`,
displayName: provider.label,
// TODO: migrate to inline svg
iconClass: provider.iconClass || 'icon-file',
enabled(context: Folder): boolean {
return (context.permissions & Permission.CREATE) !== 0
},
order: 11,
handler(context: Folder, content: Node[]) {
// Check for conflicts
const contentNames = content.map((node: Node) => node.basename)
const name = getUniqueName(provider.label + provider.extension, contentNames)

// register the new menu entry
menu.addMenuEntry({
id: `template-new-${provider.app}-${index}`,
displayName: provider.label,
templateName: provider.label + provider.extension,
iconClass: provider.iconClass || 'icon-file',
fileType: 'file',
actionLabel: provider.actionLabel,
actionHandler(name) {
TemplatePicker.open(name, provider)
},
})
// Create the file
TemplatePicker.open(name, provider)
},
}
OC.Plugins.register('OCA.Files.NewFileMenu', newTemplatePlugin)
} as Entry)
})

/**
* Init the template directory
*
* @param {string} name the templates folder name
*/
const initTemplatesFolder = async function(name) {
const templatePath = (getCurrentDirectory() + `/${name}`).replace('//', '/')
// Init template folder
const initTemplatesFolder = async function(directory: Folder, name: string) {
const templatePath = join(directory.path, name)
try {
logger.debug('Initializing the templates directory', { templatePath })
const response = await axios.post(generateOcsUrl('apps/files/api/v1/templates/path'), {
Expand All @@ -135,7 +133,11 @@ const initTemplatesFolder = async function(name) {
})

// Go to template directory
OCA.Files.App.currentFileList.changeDirectory(templatePath, true, true)
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: undefined },
{ dir: templatePath },
)

templates = response.data.ocs.data.templates
templatesPath = response.data.ocs.data.template_path
Expand Down
5 changes: 4 additions & 1 deletion apps/files/src/init.ts
Expand Up @@ -19,6 +19,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { addNewFileMenuEntry, registerFileAction } from '@nextcloud/files'

import { action as deleteAction } from './actions/deleteAction'
import { action as downloadAction } from './actions/downloadAction'
import { action as editLocallyAction } from './actions/editLocallyAction'
Expand All @@ -35,7 +37,8 @@ import registerFavoritesView from './views/favorites'
import registerRecentView from './views/recent'
import registerFilesView from './views/files'
import registerPreviewServiceWorker from './services/ServiceWorker.js'
import { addNewFileMenuEntry, registerFileAction } from '@nextcloud/files'

import './init-templates'

// Register file actions
registerFileAction(deleteAction)
Expand Down
3 changes: 0 additions & 3 deletions apps/files/src/main.ts
@@ -1,6 +1,3 @@
import './templates.js'
import './legacy/filelistSearch.js'

import Vue from 'vue'
import { createPinia, PiniaVuePlugin } from 'pinia'
import { getNavigation } from '@nextcloud/files'
Expand Down
11 changes: 3 additions & 8 deletions apps/files/src/newMenu/newFolder.ts
Expand Up @@ -21,15 +21,14 @@
*/
import type { Entry, Node } from '@nextcloud/files'

import { addNewFileMenuEntry, Permission, Folder } from '@nextcloud/files'
import { basename, extname } from 'path'
import { emit } from '@nextcloud/event-bus'
import { getCurrentUser } from '@nextcloud/auth'
import { Permission, Folder } from '@nextcloud/files'
import { showSuccess } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import FolderPlusSvg from '@mdi/svg/svg/folder-plus.svg?raw'
import Vue from 'vue'

type createFolderResponse = {
fileid: number
Expand Down Expand Up @@ -65,8 +64,9 @@ export const getUniqueName = (name: string, names: string[]): string => {
export const entry = {
id: 'newFolder',
displayName: t('files', 'New folder'),
if: (context: Folder) => (context.permissions & Permission.CREATE) !== 0,
enabled: (context: Folder) => (context.permissions & Permission.CREATE) !== 0,
iconSvgInline: FolderPlusSvg,
order: 0,
async handler(context: Folder, content: Node[]) {
const contentNames = content.map((node: Node) => node.basename)
const name = getUniqueName(t('files', 'New folder'), contentNames)
Expand All @@ -82,11 +82,6 @@ export const entry = {
root: context?.root || '/files/' + getCurrentUser()?.uid,
})

if (!context._children) {
Vue.set(context, '_children', [])
}
context._children.push(folder.fileid)

showSuccess(t('files', 'Created new folder "{name}"', { name: basename(source) }))
emit('files:node:created', folder)
emit('files:node:rename', folder)
Expand Down

0 comments on commit 4b55594

Please sign in to comment.