Skip to content

Commit b0aa478

Browse files
committed
chore: Move from old dialog to a modern vue one
Signed-off-by: Julius Knorr <jus@bitgrid.net>
1 parent d82b195 commit b0aa478

5 files changed

Lines changed: 189 additions & 120 deletions

File tree

css/files.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,5 +134,3 @@
134134
margin-left: 12px;
135135
}
136136
}
137-
138-
@import 'templatePicker';

css/templatePicker.scss

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
<template>
6+
<NcModal :title="t('richdocuments', 'Select template')" @close="onCancel">
7+
<div class="template-picker">
8+
<NcTextField v-model="filename"
9+
class="filename-input"
10+
type="text"
11+
:label="t('richdocuments', 'File name')"
12+
:error="!!filenameError"
13+
:helper-text="filenameError ?? ''"
14+
@keyup.enter="onCreate" />
15+
<div class="template-container">
16+
<div v-for="template in templates"
17+
:key="template.id"
18+
class="template"
19+
:class="{ selected: selectedTemplateId === template.id }"
20+
@click="selectTemplate(template.id)">
21+
<img v-if="template.preview" :src="templatePreviewUrl(template.id)" :alt="template.name">
22+
<h2>{{ stripFileExtension(template.name) }}</h2>
23+
</div>
24+
</div>
25+
<div class="buttons">
26+
<NcButton type="tertiary" @click="onCancel">
27+
{{ t('core', 'Cancel') }}
28+
</NcButton>
29+
<NcButton type="primary" :disabled="!!filenameError" @click="onCreate">
30+
{{ t('richdocuments', 'Create') }}
31+
</NcButton>
32+
</div>
33+
</div>
34+
</NcModal>
35+
</template>
36+
37+
<script>
38+
import { generateUrl } from '@nextcloud/router'
39+
import { translate as t } from '@nextcloud/l10n'
40+
import { NcModal, NcButton, NcTextField } from '@nextcloud/vue'
41+
import { isFilenameValid, getUniqueName } from '@nextcloud/files'
42+
43+
export default {
44+
name: 'TemplatePicker',
45+
components: {
46+
NcModal,
47+
NcButton,
48+
NcTextField,
49+
},
50+
props: {
51+
suggestedFilename: {
52+
type: String,
53+
default: '',
54+
},
55+
templates: {
56+
type: Array,
57+
required: true,
58+
},
59+
initialTemplateId: {
60+
type: [String, Number],
61+
default: null,
62+
},
63+
content: {
64+
type: Array,
65+
default: () => [],
66+
},
67+
},
68+
data() {
69+
return {
70+
selectedTemplateId: this.initialTemplateId || (this.templates[0]?.id || null),
71+
filename: '',
72+
}
73+
},
74+
computed: {
75+
filenameError() {
76+
if (!isFilenameValid(this.filename)) {
77+
return t('core', 'Invalid file name')
78+
}
79+
80+
if (this.content.some((n) => n.basename === this.filenameWithExtension || n.basename === this.filename)) {
81+
return t('core', 'File name already exists')
82+
}
83+
84+
return null
85+
},
86+
filenameWithExtension() {
87+
if (!this.filename.includes('.')) {
88+
const extension = this.suggestedFilename.split('.').pop()
89+
return this.filename + '.' + extension
90+
}
91+
return this.filename
92+
},
93+
},
94+
mounted() {
95+
this.filename = getUniqueName(this.suggestedFilename, this.content.map((n) => n.basename))
96+
},
97+
methods: {
98+
t,
99+
stripFileExtension(filename) {
100+
return filename.replace(/\.[^/.]+$/, '')
101+
},
102+
templatePreviewUrl(templateId) {
103+
return generateUrl('apps/richdocuments/template/preview/' + templateId)
104+
},
105+
selectTemplate(templateId) {
106+
this.selectedTemplateId = templateId
107+
},
108+
onCancel() {
109+
this.$emit('close')
110+
},
111+
onCreate() {
112+
if (this.filenameError) {
113+
return
114+
}
115+
116+
this.$emit('close', this.selectedTemplateId, this.filenameWithExtension)
117+
},
118+
},
119+
}
120+
</script>
121+
122+
<style lang="scss" scoped>
123+
.template-picker {
124+
padding: calc(var(--default-grid-baseline) * 3);
125+
126+
.filename-input {
127+
margin-bottom: calc(var(--default-grid-baseline) * 3);
128+
}
129+
130+
.template-container {
131+
display: grid;
132+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
133+
gap: calc(var(--default-grid-baseline) * 3);
134+
margin-bottom: calc(var(--default-grid-baseline) * 3);
135+
}
136+
137+
.template {
138+
cursor: pointer;
139+
padding: 10px;
140+
border: var(--border-width-input-focused) solid transparent;
141+
border-radius: var(--border-radius-large);
142+
transition: all 0.2s ease;
143+
display: flex;
144+
flex-direction: column;
145+
align-items: center;
146+
justify-content: center;
147+
148+
&:hover {
149+
background-color: var(--color-background-hover);
150+
}
151+
152+
&.selected {
153+
border-color: var(--color-primary);
154+
}
155+
156+
img {
157+
width: 100%;
158+
height: auto;
159+
border-radius: var(--border-radius-small);
160+
}
161+
162+
h2 {
163+
margin: calc(var(--default-grid-baseline) * 2) 0 0;
164+
text-align: center;
165+
margin-top: auto;
166+
font-size: var(--font-size-small);
167+
}
168+
}
169+
170+
.buttons {
171+
display: flex;
172+
justify-content: flex-end;
173+
gap: calc(var(--default-grid-baseline) * 2);
174+
}
175+
}
176+
</style>

src/view/NewFileMenu.js

Lines changed: 13 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import axios from '@nextcloud/axios'
77
import { emit } from '@nextcloud/event-bus'
88
import Types from '../helpers/types.js'
99
import { createEmptyFile } from '../services/api.js'
10-
import { generateUrl, generateFilePath, generateOcsUrl } from '@nextcloud/router'
11-
import { showError } from '@nextcloud/dialogs'
10+
import { generateOcsUrl } from '@nextcloud/router'
11+
import { showError, spawnDialog } from '@nextcloud/dialogs'
1212
import { getCapabilities } from '../services/capabilities.ts'
1313
import { File, addNewFileMenuEntry, isFilenameValid, getUniqueName } from '@nextcloud/files'
14+
import TemplatePicker from '../components/Modal/TemplatePicker.vue'
1415

1516
const createFileTypeEntry = (type, displayName, iconClass, index) => ({
1617
id: 'add-' + type.extension,
@@ -87,65 +88,18 @@ const openTemplatePicker = async (context, type, mimetype, filename, content = [
8788
return
8889
}
8990

90-
await showTemplatePickerDialog(context, type, mimetype, filename, templates, content)
91+
await spawnDialog(TemplatePicker, {
92+
templates,
93+
suggestedFilename: filename,
94+
content,
95+
initialTemplateId: templates[0]?.id,
96+
}, (templateId, filename) => {
97+
if (templateId) {
98+
createDocument(context, mimetype, filename, templateId, content)
99+
}
100+
})
91101
} catch (error) {
92102
console.error(error)
93103
showError(t('core', 'Could not load templates'))
94104
}
95105
}
96-
97-
const showTemplatePickerDialog = async (context, type, mimetype, filename, templates, content) => {
98-
const { data: tmpl } = await axios.get(generateFilePath('richdocuments', 'templates', 'templatePicker.html'))
99-
const $tmpl = $(tmpl).eq(2)
100-
const $dlg = $tmpl.octemplate({
101-
dialog_name: 'template-picker',
102-
dialog_title: t('richdocuments', 'Select template'),
103-
})
104-
105-
templates.forEach((template) => {
106-
appendTemplateFromData($dlg[0], template)
107-
})
108-
109-
$('body').append($dlg)
110-
111-
const buttonlist = [
112-
{
113-
text: t('core', 'Cancel'),
114-
classes: 'cancel',
115-
click() {
116-
$(this).ocdialog('close')
117-
},
118-
},
119-
{
120-
text: t('richdocuments', 'Create'),
121-
classes: 'primary',
122-
click() {
123-
const templateId = this.dataset.templateId
124-
createDocument(context, mimetype, filename, templateId, content)
125-
$(this).ocdialog('close')
126-
},
127-
}
128-
]
129-
130-
$('#template-picker').ocdialog({
131-
closeOnEscape: true,
132-
modal: true,
133-
buttons: buttonlist,
134-
})
135-
}
136-
137-
const appendTemplateFromData = (dlg, data) => {
138-
const template = dlg.querySelector('.template-model').cloneNode(true)
139-
template.className = ''
140-
template.querySelector('img').src = generateUrl('apps/richdocuments/template/preview/' + data.id)
141-
template.querySelector('h2').textContent = data.name
142-
template.onclick = function(e) {
143-
e.preventDefault()
144-
dlg.dataset.templateId = data.id
145-
}
146-
if (!dlg.dataset.templateId) {
147-
dlg.dataset.templateId = data.id
148-
}
149-
150-
dlg.querySelector('.template-container').appendChild(template)
151-
}

templates/templatePicker.html

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)