chore: Move from old dialog to a modern vue one

Signed-off-by: Julius Knorr <jus@bitgrid.net>
This commit is contained in:
Julius Knorr 2025-05-23 11:58:11 +02:00
parent d82b195294
commit b0aa47846a
5 changed files with 189 additions and 120 deletions

View file

@ -134,5 +134,3 @@
margin-left: 12px;
}
}
@import 'templatePicker';

View file

@ -1,45 +0,0 @@
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@use 'sass:math';
#template-picker {
.template-container:not(.hidden) {
display: flex;
flex-wrap: wrap;
a {
$size: 170px;
$sizeY: math.div($size, 210) * 297;
$space: 10px;
border-radius: var(--border-radius);
border: 1px solid var(--color-border);
margin: $space;
position: relative;
&:hover,
&:focus {
border-color: var(--color-primary-element);
}
> span {
display: flex;
flex-direction: column;
width: $size;
margin: $space;
cursor: pointer;
> img,
> span {
width: $size;
height: $sizeY;
background-color: var(--color-background-dark);
background-size: 24px;
}
> h2 {
margin-top: $space;
font-size: inherit;
}
}
}
}
}

View file

@ -0,0 +1,176 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<NcModal :title="t('richdocuments', 'Select template')" @close="onCancel">
<div class="template-picker">
<NcTextField v-model="filename"
class="filename-input"
type="text"
:label="t('richdocuments', 'File name')"
:error="!!filenameError"
:helper-text="filenameError ?? ''"
@keyup.enter="onCreate" />
<div class="template-container">
<div v-for="template in templates"
:key="template.id"
class="template"
:class="{ selected: selectedTemplateId === template.id }"
@click="selectTemplate(template.id)">
<img v-if="template.preview" :src="templatePreviewUrl(template.id)" :alt="template.name">
<h2>{{ stripFileExtension(template.name) }}</h2>
</div>
</div>
<div class="buttons">
<NcButton type="tertiary" @click="onCancel">
{{ t('core', 'Cancel') }}
</NcButton>
<NcButton type="primary" :disabled="!!filenameError" @click="onCreate">
{{ t('richdocuments', 'Create') }}
</NcButton>
</div>
</div>
</NcModal>
</template>
<script>
import { generateUrl } from '@nextcloud/router'
import { translate as t } from '@nextcloud/l10n'
import { NcModal, NcButton, NcTextField } from '@nextcloud/vue'
import { isFilenameValid, getUniqueName } from '@nextcloud/files'
export default {
name: 'TemplatePicker',
components: {
NcModal,
NcButton,
NcTextField,
},
props: {
suggestedFilename: {
type: String,
default: '',
},
templates: {
type: Array,
required: true,
},
initialTemplateId: {
type: [String, Number],
default: null,
},
content: {
type: Array,
default: () => [],
},
},
data() {
return {
selectedTemplateId: this.initialTemplateId || (this.templates[0]?.id || null),
filename: '',
}
},
computed: {
filenameError() {
if (!isFilenameValid(this.filename)) {
return t('core', 'Invalid file name')
}
if (this.content.some((n) => n.basename === this.filenameWithExtension || n.basename === this.filename)) {
return t('core', 'File name already exists')
}
return null
},
filenameWithExtension() {
if (!this.filename.includes('.')) {
const extension = this.suggestedFilename.split('.').pop()
return this.filename + '.' + extension
}
return this.filename
},
},
mounted() {
this.filename = getUniqueName(this.suggestedFilename, this.content.map((n) => n.basename))
},
methods: {
t,
stripFileExtension(filename) {
return filename.replace(/\.[^/.]+$/, '')
},
templatePreviewUrl(templateId) {
return generateUrl('apps/richdocuments/template/preview/' + templateId)
},
selectTemplate(templateId) {
this.selectedTemplateId = templateId
},
onCancel() {
this.$emit('close')
},
onCreate() {
if (this.filenameError) {
return
}
this.$emit('close', this.selectedTemplateId, this.filenameWithExtension)
},
},
}
</script>
<style lang="scss" scoped>
.template-picker {
padding: calc(var(--default-grid-baseline) * 3);
.filename-input {
margin-bottom: calc(var(--default-grid-baseline) * 3);
}
.template-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: calc(var(--default-grid-baseline) * 3);
margin-bottom: calc(var(--default-grid-baseline) * 3);
}
.template {
cursor: pointer;
padding: 10px;
border: var(--border-width-input-focused) solid transparent;
border-radius: var(--border-radius-large);
transition: all 0.2s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
&:hover {
background-color: var(--color-background-hover);
}
&.selected {
border-color: var(--color-primary);
}
img {
width: 100%;
height: auto;
border-radius: var(--border-radius-small);
}
h2 {
margin: calc(var(--default-grid-baseline) * 2) 0 0;
text-align: center;
margin-top: auto;
font-size: var(--font-size-small);
}
}
.buttons {
display: flex;
justify-content: flex-end;
gap: calc(var(--default-grid-baseline) * 2);
}
}
</style>

View file

@ -7,10 +7,11 @@ import axios from '@nextcloud/axios'
import { emit } from '@nextcloud/event-bus'
import Types from '../helpers/types.js'
import { createEmptyFile } from '../services/api.js'
import { generateUrl, generateFilePath, generateOcsUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'
import { generateOcsUrl } from '@nextcloud/router'
import { showError, spawnDialog } from '@nextcloud/dialogs'
import { getCapabilities } from '../services/capabilities.ts'
import { File, addNewFileMenuEntry, isFilenameValid, getUniqueName } from '@nextcloud/files'
import TemplatePicker from '../components/Modal/TemplatePicker.vue'
const createFileTypeEntry = (type, displayName, iconClass, index) => ({
id: 'add-' + type.extension,
@ -87,65 +88,18 @@ const openTemplatePicker = async (context, type, mimetype, filename, content = [
return
}
await showTemplatePickerDialog(context, type, mimetype, filename, templates, content)
await spawnDialog(TemplatePicker, {
templates,
suggestedFilename: filename,
content,
initialTemplateId: templates[0]?.id,
}, (templateId, filename) => {
if (templateId) {
createDocument(context, mimetype, filename, templateId, content)
}
})
} catch (error) {
console.error(error)
showError(t('core', 'Could not load templates'))
}
}
const showTemplatePickerDialog = async (context, type, mimetype, filename, templates, content) => {
const { data: tmpl } = await axios.get(generateFilePath('richdocuments', 'templates', 'templatePicker.html'))
const $tmpl = $(tmpl).eq(2)
const $dlg = $tmpl.octemplate({
dialog_name: 'template-picker',
dialog_title: t('richdocuments', 'Select template'),
})
templates.forEach((template) => {
appendTemplateFromData($dlg[0], template)
})
$('body').append($dlg)
const buttonlist = [
{
text: t('core', 'Cancel'),
classes: 'cancel',
click() {
$(this).ocdialog('close')
},
},
{
text: t('richdocuments', 'Create'),
classes: 'primary',
click() {
const templateId = this.dataset.templateId
createDocument(context, mimetype, filename, templateId, content)
$(this).ocdialog('close')
},
}
]
$('#template-picker').ocdialog({
closeOnEscape: true,
modal: true,
buttons: buttonlist,
})
}
const appendTemplateFromData = (dlg, data) => {
const template = dlg.querySelector('.template-model').cloneNode(true)
template.className = ''
template.querySelector('img').src = generateUrl('apps/richdocuments/template/preview/' + data.id)
template.querySelector('h2').textContent = data.name
template.onclick = function(e) {
e.preventDefault()
dlg.dataset.templateId = data.id
}
if (!dlg.dataset.templateId) {
dlg.dataset.templateId = data.id
}
dlg.querySelector('.template-container').appendChild(template)
}

View file

@ -1,14 +0,0 @@
<!--
- SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<div id="{dialog_name}" title="{dialog_title}">
<div class="template-container">
<a class="hidden template-model" href="#">
<span>
<img src="" alt="" />
<h2></h2>
</span>
</a>
</div>
</div>