diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2021-01-19 16:38:51 +0100 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2021-01-28 12:00:20 +0100 |
commit | 4f90766ba314171bbfc78d1e988307c50633e7f3 (patch) | |
tree | f5f910ff0f3dd2ff8fa5a05c8fd1f905ffc21ba5 /apps/files/src | |
parent | 7e6d69d166cbc92fb457fc72efc9abe850a0bbe4 (diff) | |
download | nextcloud-server-4f90766ba314171bbfc78d1e988307c50633e7f3.tar.gz nextcloud-server-4f90766ba314171bbfc78d1e988307c50633e7f3.zip |
Skip template picker if none available
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files/src')
-rw-r--r-- | apps/files/src/components/TemplatePreview.vue | 45 | ||||
-rw-r--r-- | apps/files/src/services/Templates.js | 29 | ||||
-rw-r--r-- | apps/files/src/templates.js | 91 | ||||
-rw-r--r-- | apps/files/src/utils/davUtils.js | 18 | ||||
-rw-r--r-- | apps/files/src/views/TemplatePicker.vue | 72 |
5 files changed, 192 insertions, 63 deletions
diff --git a/apps/files/src/components/TemplatePreview.vue b/apps/files/src/components/TemplatePreview.vue index 538e1bcff7b..89162ba4efd 100644 --- a/apps/files/src/components/TemplatePreview.vue +++ b/apps/files/src/components/TemplatePreview.vue @@ -30,9 +30,9 @@ @change="onCheck"> <label :for="id" class="template-picker__label"> - <div class="template-picker__preview"> + <div class="template-picker__preview" + :class="failedPreview ? 'template-picker__preview--failed' : ''"> <img class="template-picker__image" - :class="failedPreview ? 'template-picker__image--failed' : ''" :src="realPreviewUrl" alt="" draggable="false" @@ -40,7 +40,7 @@ </div> <span class="template-picker__title"> - {{ basename }} + {{ nameWithoutExt }} </span> </label> </li> @@ -100,6 +100,14 @@ export default { }, computed: { + /** + * Strip away extension from name + * @returns {string} + */ + nameWithoutExt() { + return this.basename.indexOf('.') > -1 ? this.basename.split('.').slice(0, -1).join('.') : this.basename + }, + id() { return `template-picker-${this.fileid}` }, @@ -107,7 +115,7 @@ export default { realPreviewUrl() { // If original preview failed, fallback to mime icon if (this.failedPreview && this.mimeIcon) { - return generateUrl(this.mimeIcon) + return this.mimeIcon } if (this.previewUrl) { @@ -149,7 +157,6 @@ export default { align-items: center; flex: 1 1; flex-direction: column; - margin: var(--margin); &, * { cursor: pointer; @@ -162,32 +169,42 @@ export default { } &__preview { - display: flex; + display: block; overflow: hidden; // Stretch so all entries are the same width flex: 1 1; width: var(--width); - min-height: var(--width); + min-height: var(--height); max-height: var(--height); - padding: var(--margin); + padding: 0; border: var(--border) solid var(--color-border); border-radius: var(--border-radius-large); input:checked + label > & { border-color: var(--color-primary); } + + &--failed { + // Make sure to properly center fallback icon + display: flex; + } } &__image { max-width: 100%; background-color: var(--color-main-background); - &--failed { - width: calc(var(--margin) * 8); - // Center mime icon - margin: auto; - background-color: transparent !important; - } + object-fit: cover; + } + + // Failed preview, fallback to mime icon + &__preview--failed &__image { + width: calc(var(--margin) * 8); + // Center mime icon + margin: auto; + background-color: transparent !important; + + object-fit: initial; } &__title { diff --git a/apps/files/src/services/Templates.js b/apps/files/src/services/Templates.js new file mode 100644 index 00000000000..c1a8992d661 --- /dev/null +++ b/apps/files/src/services/Templates.js @@ -0,0 +1,29 @@ +/** + * @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import { generateOcsUrl } from '@nextcloud/router' +import axios from '@nextcloud/axios' + +export const getTemplates = async function() { + const response = await axios.get(generateOcsUrl('apps/files/api/v1', 2) + 'templates') + return response.data.ocs.data +} diff --git a/apps/files/src/templates.js b/apps/files/src/templates.js index 3cffc30085c..c493e254caf 100644 --- a/apps/files/src/templates.js +++ b/apps/files/src/templates.js @@ -23,9 +23,14 @@ import { getLoggerBuilder } from '@nextcloud/logger' import { loadState } from '@nextcloud/initial-state' import { translate as t, translatePlural as n } from '@nextcloud/l10n' +import { generateOcsUrl } from '@nextcloud/router' +import { getCurrentDirectory } from './utils/davUtils' +import axios from '@nextcloud/axios' import Vue from 'vue' import TemplatePickerView from './views/TemplatePicker' +import { getCurrentUser } from '@nextcloud/auth' +import { showError } from '@nextcloud/dialogs' // Set up logger const logger = getLoggerBuilder() @@ -47,8 +52,10 @@ TemplatePickerRoot.id = 'template-picker' document.body.appendChild(TemplatePickerRoot) // Retrieve and init templates -const templates = loadState('files', 'templates', []) +let templates = loadState('files', 'templates', []) +let templatesPath = loadState('files', 'templates_path', false) logger.debug('Templates providers', templates) +logger.debug('Templates folder', { templatesPath }) // Init vue app const View = Vue.extend(TemplatePickerView) @@ -60,33 +67,77 @@ const TemplatePicker = new View({ }) TemplatePicker.$mount('#template-picker') -// Init template engine after load +// Init template engine after load to make sure it's the last injected entry window.addEventListener('DOMContentLoaded', function() { - // Init template files menu - templates.forEach((provider, index) => { - - const newTemplatePlugin = { + if (!templatesPath) { + logger.debug('Templates folder not initialized') + const initTemplatesPlugin = { 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 - } - // 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', + id: 'template-init', + displayName: t('files', 'Set up templates folder'), + templateName: t('files', 'Templates'), + iconClass: 'icon-template-add', fileType: 'file', actionHandler(name) { - TemplatePicker.open(name, provider) + initTemplatesFolder(name) }, }) }, } - OC.Plugins.register('OCA.Files.NewFileMenu', newTemplatePlugin) - }) + OC.Plugins.register('OCA.Files.NewFileMenu', initTemplatesPlugin) + } +}) + +// 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 + } + + // 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', + actionHandler(name) { + TemplatePicker.open(name, provider) + }, + }) + }, + } + OC.Plugins.register('OCA.Files.NewFileMenu', newTemplatePlugin) }) + +/** + * Init the template directory + * + * @param {string} name the templates folder name + */ +const initTemplatesFolder = async function(name) { + const templatePath = (getCurrentDirectory() + `/${name}`).replace('//', '/') + try { + logger.debug('Initializing the templates directory', { templatePath }) + const response = await axios.post(generateOcsUrl('apps/files/api/v1/templates', 2) + 'path', { + templatePath, + copySystemTemplates: true, + }) + + // Go to template directory + OCA.Files.App.currentFileList.changeDirectory(templatePath, true, true) + + templates = response.data.ocs.data.templates + templatesPath = response.data.ocs.data.template_path + } catch (error) { + logger.error('Unable to initialize the templates directory') + showError(t('files', 'Unable to initialize the templates directory')) + } +} diff --git a/apps/files/src/utils/davUtils.js b/apps/files/src/utils/davUtils.js index f64801f08dd..cd5732a4772 100644 --- a/apps/files/src/utils/davUtils.js +++ b/apps/files/src/utils/davUtils.js @@ -23,7 +23,7 @@ import { generateRemoteUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' -const getRootPath = function() { +export const getRootPath = function() { if (getCurrentUser()) { return generateRemoteUrl(`dav/files/${getCurrentUser().uid}`) } else { @@ -31,12 +31,22 @@ const getRootPath = function() { } } -const isPublic = function() { +export const isPublic = function() { return !getCurrentUser() } -const getToken = function() { +export const getToken = function() { return document.getElementById('sharingToken') && document.getElementById('sharingToken').value } -export { getRootPath, getToken, isPublic } +/** + * Return the current directory, fallback to root + * @returns {string} + */ +export const getCurrentDirectory = function() { + const currentDirInfo = OCA?.Files?.App?.currentFileList?.dirInfo + || { path: '/', name: '' } + + // Make sure we don't have double slashes + return `${currentDirInfo.path}/${currentDirInfo.name}`.replace(/\/\//gi, '/') +} diff --git a/apps/files/src/views/TemplatePicker.vue b/apps/files/src/views/TemplatePicker.vue index 84c7c38aba4..95ba3d3a4dc 100644 --- a/apps/files/src/views/TemplatePicker.vue +++ b/apps/files/src/views/TemplatePicker.vue @@ -29,7 +29,7 @@ <form class="templates-picker__form" :style="style" @submit.prevent.stop="onSubmit"> - <h3>{{ t('files', 'Pick a template') }}</h3> + <h2>{{ t('files', 'Pick a template for {name}', { name: nameWithoutExt }) }}</h2> <!-- Templates list --> <ul class="templates-picker__list"> @@ -55,11 +55,11 @@ <input type="submit" class="primary" :value="t('files', 'Create')" - :aria-label="t('files', 'Create a new file with the ')"> + :aria-label="t('files', 'Create a new file with the selected template')"> </div> </form> - <EmptyContent class="templates-picker__loading" v-if="loading" icon="icon-loading"> + <EmptyContent v-if="loading" class="templates-picker__loading" icon="icon-loading"> {{ t('files', 'Creating file') }} </EmptyContent> </Modal> @@ -68,11 +68,12 @@ <script> import { generateOcsUrl } from '@nextcloud/router' import { showError } from '@nextcloud/dialogs' - import axios from '@nextcloud/axios' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import Modal from '@nextcloud/vue/dist/Components/Modal' +import { getCurrentDirectory } from '../utils/davUtils' +import { getTemplates } from '../services/Templates' import TemplatePreview from '../components/TemplatePreview' const border = 2 @@ -107,6 +108,14 @@ export default { }, computed: { + /** + * Strip away extension from name + * @returns {string} + */ + nameWithoutExt() { + return this.name.indexOf('.') > -1 ? this.name.split('.').slice(0, -1).join('.') : this.name + }, + emptyTemplate() { return { basename: t('files', 'Blank'), @@ -131,7 +140,7 @@ export default { '--width': width + 'px', '--border': border + 'px', '--fullwidth': width + 2 * margin + 2 * border + 'px', - '--height': this.ratio ? width * this.ratio + 'px' : null, + '--height': this.provider.ratio ? Math.round(width / this.provider.ratio) + 'px' : null, } }, }, @@ -142,11 +151,27 @@ export default { * @param {string} name the file name to create * @param {object} provider the template provider picked */ - open(name, provider) { + async open(name, provider) { + this.checked = this.emptyTemplate.fileid this.name = name - this.opened = true this.provider = provider + + const templates = await getTemplates() + const fetchedProvider = templates.find((fetchedProvider) => fetchedProvider.app === provider.app && fetchedProvider.label === provider.label) + if (fetchedProvider === null) { + throw new Error('Failed to match provider in results') + } + this.provider = fetchedProvider + + // If there is no templates available, just create an empty file + if (fetchedProvider.templates.length === 0) { + this.onSubmit() + return + } + + // Else, open the picker + this.opened = true }, /** @@ -170,7 +195,7 @@ export default { async onSubmit() { this.loading = true - const currentDirectory = this.getCurrentDirectory() + const currentDirectory = getCurrentDirectory() const fileList = OCA?.Files?.App?.currentFileList try { @@ -197,24 +222,13 @@ export default { this.close() } catch (error) { - this.logger.error('Error while creating the new file from template', error) + this.logger.error('Error while creating the new file from template') + console.error(error) showError(this.t('files', 'Unable to create new file from template')) } finally { this.loading = false } }, - - /** - * Return the current directory, fallback to root - * @returns {string} - */ - getCurrentDirectory() { - const currentDirInfo = OCA?.Files?.App?.currentFileList?.dirInfo - || { path: '/', name: '' } - - // Make sure we don't have double slashes - return `${currentDirInfo.path}/${currentDirInfo.name}`.replace(/\/\//gi, '/') - }, }, } </script> @@ -225,6 +239,12 @@ export default { padding: calc(var(--margin) * 2); // Will be handled by the buttons padding-bottom: 0; + + h2 { + text-align: center; + font-weight: bold; + margin: var(--margin) 0 calc(var(--margin) * 2); + } } &__list { @@ -233,18 +253,20 @@ export default { grid-auto-columns: 1fr; // We want maximum 5 columns. Putting 6 as we don't count the grid gap. So it will always be lower than 6 max-width: calc(var(--fullwidth) * 6); - grid-template-columns: repeat(auto-fit, minmax(var(--fullwidth), 1fr)); + grid-template-columns: repeat(auto-fit, var(--fullwidth)); // Make sure all rows are the same height grid-auto-rows: 1fr; + // Center the columns set + justify-content: center; } + &__buttons { display: flex; justify-content: space-between; padding: calc(var(--margin) * 2) var(--margin); position: sticky; - // Make sure the templates list doesn't weirdly peak under when scrolled. Happens on some rare occasions - bottom: -1px; - background-color: var(--color-main-background); + bottom: 0; + background-image: linear-gradient(0, var(--gradient-main-background)); } // Make sure we're relative for the loading emptycontent on top |