Shamelessly copy the good work of Skjnldsv from the files app

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2022-08-31 07:54:31 +02:00
Родитель 7de934124f
Коммит ade8623048
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 74434EFE0D2E2205
3 изменённых файлов: 342 добавлений и 18 удалений

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

@ -162,13 +162,15 @@
<!-- Text file creation dialog -->
<NcModal v-if="showTextFileDialog !== false"
size="small"
size="normal"
class="templates-picker"
@close="dismissTextFileCreation">
<div class="new-text-file">
<h2>
{{ t('spreed', 'Create and share a new file') }}
</h2>
<form class="new-text-file__form"
<form class="new-text-file__form templates-picker__form"
:style="style"
@submit.prevent="handleCreateTextFile">
<NcTextField id="new-file-form-name"
ref="textFileTitleInput"
@ -177,17 +179,33 @@
:label="t('spreed', 'Name of the new file')"
:placeholder="textFileTitle"
:value.sync="textFileTitle" />
<template v-if="fileTemplate.templates.length">
<ul class="templates-picker__list">
<TemplatePreview v-bind="emptyTemplate"
:checked="checked === emptyTemplate.fileid"
@check="onCheck" />
<TemplatePreview v-for="template in fileTemplate.templates"
:key="template.fileid"
v-bind="template"
:checked="checked === template.fileid"
:ratio="fileTemplate.ratio"
@check="onCheck" />
</ul>
</template>
<div class="new-text-file__buttons">
<NcButton type="tertiary"
@click="dismissTextFileCreation">
{{ t('spreed', 'Close') }}
</NcButton>
<NcButton type="primary"
@click="handleCreateTextFile">
{{ t('spreed', 'Create file') }}
</NcButton>
</div>
</form>
<div class="new-text-file__buttons">
<NcButton type="tertiary"
@click="dismissTextFileCreation">
{{ t('spreed', 'Close') }}
</NcButton>
<NcButton type="primary"
@click="handleCreateTextFile">
{{ t('spreed', 'Create file') }}
</NcButton>
</div>
</div>
</NcModal>
</div>
@ -216,6 +234,7 @@ import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import Folder from 'vue-material-design-icons/Folder.vue'
import Upload from 'vue-material-design-icons/Upload.vue'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import TemplatePreview from './TemplatePreview.vue'
const picker = getFilePickerBuilder(t('spreed', 'File to share'))
.setMultiSelect(false)
@ -224,6 +243,10 @@ const picker = getFilePickerBuilder(t('spreed', 'File to share'))
.allowDirectories()
.build()
const border = 2
const margin = 8
const width = margin * 20
export default {
name: 'NewMessageForm',
@ -245,6 +268,7 @@ export default {
NcModal,
Folder,
Upload,
TemplatePreview,
NcTextField,
},
@ -266,6 +290,9 @@ export default {
showTextFileDialog: false,
textFileTitle: t('spreed', 'New file'),
newFileError: '',
// Check empty template by default
checked: -1,
}
},
@ -372,6 +399,34 @@ export default {
fileTemplateOptions() {
return this.$store.getters.getFileTemplates()
},
fileTemplate() {
return this.fileTemplateOptions[this.showTextFileDialog]
},
emptyTemplate() {
return {
basename: t('files', 'Blank'),
fileid: -1,
filename: t('files', 'Blank'),
hasPreview: false,
mime: this.fileTemplate?.mimetypes[0] || this.fileTemplate?.mimetypes,
}
},
selectedTemplate() {
return this.fileTemplate.templates.find(template => template.fileid === this.checked)
},
style() {
return {
'--margin': margin + 'px',
'--width': width + 'px',
'--border': border + 'px',
'--fullwidth': width + 2 * margin + 2 * border + 'px',
'--height': this.fileTemplate.ratio ? Math.round(width / this.fileTemplate.ratio) + 'px' : null,
}
},
},
watch: {
@ -656,19 +711,31 @@ export default {
this.showSimplePollsEditor = value
},
/**
* Manages the radio template picker change
*
* @param {number} fileid the selected template file id
*/
onCheck(fileid) {
this.checked = fileid
},
// Create text file and share it to a conversation
async handleCreateTextFile() {
this.newFileError = ''
let filePath = this.$store.getters.getAttachmentFolder() + '/' + this.textFileTitle.replace('/', '')
const fileTemplate = this.fileTemplateOptions[this.showTextFileDialog]
if (!filePath.endsWith(fileTemplate.extension)) {
filePath += fileTemplate.extension
if (!filePath.endsWith(this.fileTemplate.extension)) {
filePath += this.fileTemplate.extension
}
let fileData
try {
const response = await createTextFile(filePath)
const response = await createTextFile(
filePath,
this.selectedTemplate?.filename,
this.selectedTemplate?.templateType,
)
fileData = response.data.ocs.data
} catch (error) {
console.error('Error while creating file', error)
@ -813,6 +880,19 @@ export default {
&__form {
width: 100%;
.templates-picker__list {
display: grid;
grid-gap: calc(var(--margin) * 2);
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, var(--fullwidth));
// Make sure all rows are the same height
grid-auto-rows: 1fr;
// Center the columns set
justify-content: center;
}
}
}
</style>

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

@ -0,0 +1,240 @@
<!--
- @copyright Copyright (c) 2020 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/>.
-
-->
<!-- FIXME COPY FROM apps/files/src/components/TemplatePreview.vue should be deduplicated -->
<template>
<li class="template-picker__item">
<input :id="id"
:checked="checked"
type="radio"
class="radio"
name="template-picker"
@change="onCheck">
<label :for="id" class="template-picker__label">
<div class="template-picker__preview"
:class="failedPreview ? 'template-picker__preview--failed' : ''">
<img class="template-picker__image"
:src="realPreviewUrl"
alt=""
draggable="false"
@error="onFailure">
</div>
<span class="template-picker__title">
{{ nameWithoutExt }}
</span>
</label>
</li>
</template>
<script>
import { generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
const encodeFilePath = function(path) {
const pathSections = (path.startsWith('/') ? path : `/${path}`).split('/')
let relativePath = ''
pathSections.forEach((section) => {
if (section !== '') {
relativePath += '/' + encodeURIComponent(section)
}
})
return relativePath
}
const isPublic = function() {
return !getCurrentUser()
}
const getToken = function() {
return document.getElementById('sharingToken') && document.getElementById('sharingToken').value
}
// preview width generation
const previewWidth = 256
export default {
name: 'TemplatePreview',
inheritAttrs: false,
props: {
basename: {
type: String,
required: true,
},
checked: {
type: Boolean,
default: false,
},
fileid: {
type: [String, Number],
required: true,
},
filename: {
type: String,
required: true,
},
previewUrl: {
type: String,
default: null,
},
hasPreview: {
type: Boolean,
default: true,
},
mime: {
type: String,
required: true,
},
ratio: {
type: Number,
default: null,
},
},
data() {
return {
failedPreview: false,
}
},
computed: {
/**
* Strip away extension from name
*
* @return {string}
*/
nameWithoutExt() {
return this.basename.indexOf('.') > -1 ? this.basename.split('.').slice(0, -1).join('.') : this.basename
},
id() {
return `template-picker-${this.fileid}`
},
realPreviewUrl() {
// If original preview failed, fallback to mime icon
if (this.failedPreview && this.mimeIcon) {
return this.mimeIcon
}
if (this.previewUrl) {
return this.previewUrl
}
// TODO: find a nicer standard way of doing this?
if (isPublic()) {
return generateUrl(`/apps/files_sharing/publicpreview/${getToken()}?fileId=${this.fileid}&file=${encodeFilePath(this.filename)}&x=${previewWidth}&y=${previewWidth}&a=1`)
}
return generateUrl(`/core/preview?fileId=${this.fileid}&x=${previewWidth}&y=${previewWidth}&a=1`)
},
mimeIcon() {
return OC.MimeType.getIconUrl(this.mime)
},
},
methods: {
onCheck() {
this.$emit('check', this.fileid)
},
onFailure() {
this.failedPreview = true
},
},
}
</script>
<style lang="scss" scoped>
.template-picker {
&__item {
display: flex;
}
&__label {
display: flex;
// Align in the middle of the grid
align-items: center;
flex: 1 1;
flex-direction: column;
&, * {
cursor: pointer;
user-select: none;
}
&::before {
display: none !important;
}
}
&__preview {
display: block;
overflow: hidden;
// Stretch so all entries are the same width
flex: 1 1;
width: var(--width);
min-height: var(--height);
max-height: var(--height);
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);
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 {
overflow: hidden;
// also count preview border
max-width: calc(var(--width) + 2*2px);
padding: var(--margin);
white-space: nowrap;
text-overflow: ellipsis;
}
}
</style>

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

@ -62,12 +62,16 @@ const getFileTemplates = async () => {
/**
* Share a text file to a conversation
*
* @param { string } filePath the file path
* @param {string} filePath The new file destination path
* @param {string} templatePath The template source path
* @param {string} templateType The template type e.g 'user'
* @return { object } the file object
*/
const createTextFile = async function(filePath) {
const createTextFile = async function(filePath, templatePath, templateType) {
return await axios.post(generateOcsUrl('apps/files/api/v1/templates/create'), {
filePath,
templatePath,
templateType,
})
}