|
124 | 124 | </div> |
125 | 125 | </template> |
126 | 126 |
|
127 | | -<script> |
| 127 | +<script setup lang="ts"> |
128 | 128 | import { t } from '@nextcloud/l10n' |
| 129 | +import { nextTick, onMounted, ref, computed } from 'vue' |
129 | 130 |
|
130 | 131 | import debounce from 'debounce' |
131 | 132 |
|
@@ -155,167 +156,203 @@ import { |
155 | 156 | } from '@mdi/js' |
156 | 157 | import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' |
157 | 158 |
|
158 | | -export default { |
| 159 | +defineOptions({ |
159 | 160 | name: 'FooterTemplateEditor', |
160 | | - emits: ['template-reset'], |
161 | | - directives: { |
162 | | - Linkify, |
163 | | - }, |
164 | | - components: { |
165 | | - CodeEditor, |
166 | | - NcButton, |
167 | | - NcDialog, |
168 | | - NcFormBoxButton, |
169 | | - NcIconSvgWrapper, |
170 | | - NcLoadingIcon, |
171 | | - NcTextField, |
172 | | - PDFElements, |
173 | | - }, |
174 | | - setup() { |
175 | | - return { |
176 | | - mdiCheck, |
177 | | - mdiContentCopy, |
178 | | - mdiHelpCircleOutline, |
179 | | - mdiMagnifyMinusOutline, |
180 | | - mdiMagnifyPlusOutline, |
181 | | - mdiUndoVariant, |
182 | | - } |
183 | | - }, |
184 | | - data() { |
185 | | - const DEFAULT_PREVIEW_WIDTH = 595 |
186 | | - const DEFAULT_PREVIEW_HEIGHT = 100 |
187 | | - return { |
188 | | - DEFAULT_PREVIEW_WIDTH, |
189 | | - DEFAULT_PREVIEW_HEIGHT, |
190 | | - footerDescription: t('libresign', 'Configure the content displayed at the footer of the PDF. The text template uses Twig syntax: https://twig.symfony.com/'), |
191 | | - footerTemplate: '', |
192 | | - pdfPreviewFile: null, |
193 | | - loadingPreview: false, |
194 | | - pdfKey: 0, |
195 | | - zoomLevel: loadState('libresign', 'footer_preview_zoom_level', 100), |
196 | | - previewWidth: DEFAULT_PREVIEW_WIDTH, |
197 | | - previewHeight: DEFAULT_PREVIEW_HEIGHT, |
198 | | - containerHeight: null, |
199 | | - showVariablesDialog: false, |
200 | | - templateVariables: loadState('libresign', 'footer_template_variables', {}), |
201 | | - copiedVariable: null, |
202 | | - } |
203 | | - }, |
204 | | - computed: { |
205 | | - showResetDimensions() { |
206 | | - return Number(this.previewWidth) !== this.DEFAULT_PREVIEW_WIDTH || Number(this.previewHeight) !== this.DEFAULT_PREVIEW_HEIGHT |
207 | | - }, |
208 | | - }, |
209 | | - created() { |
210 | | - ensurePdfWorker() |
211 | | - this.debouncedSaveFooterTemplate = debounce(this.saveFooterTemplate, 500) |
212 | | - this.debouncedUpdateScale = debounce(this.updateScale, 300) |
213 | | - this.debouncedSaveDimensions = debounce(this.saveDimensions, 500) |
214 | | - }, |
215 | | - mounted() { |
216 | | - axios.get(generateOcsUrl('/apps/libresign/api/v1/admin/footer-template')) |
217 | | - .then(response => { |
218 | | - this.footerTemplate = response.data.ocs.data.template |
219 | | - this.previewHeight = response.data.ocs.data.preview_height |
220 | | - this.previewWidth = response.data.ocs.data.preview_width |
221 | | - this.saveFooterTemplate() |
222 | | - }) |
223 | | - }, |
224 | | - methods: { |
225 | | - t, |
226 | | - getVariableText(name) { |
227 | | - return `{{ ${name} }}` |
228 | | - }, |
229 | | - isCopied(name) { |
230 | | - return this.copiedVariable === this.getVariableText(name) |
231 | | - }, |
232 | | - copyToClipboard(text) { |
233 | | - if (this.copiedVariable === text) { |
234 | | - return |
235 | | - } |
236 | | -
|
237 | | - const value = text |
238 | | - try { |
239 | | - navigator.clipboard.writeText(value) |
240 | | - } catch { |
241 | | - // Fallback for a case when clipboard API is not available or permission denied |
242 | | - // eslint-disable-next-line no-alert |
243 | | - prompt('', value) |
244 | | - } |
245 | | -
|
246 | | - this.copiedVariable = text |
247 | | - setTimeout(() => { |
248 | | - this.copiedVariable = null |
249 | | - }, 2000) |
250 | | - }, |
251 | | - async resetFooterTemplate() { |
252 | | - this.$emit('template-reset') |
253 | | - this.resetDimensions() |
254 | | - axios.post(generateOcsUrl('/apps/libresign/api/v1/admin/footer-template')) |
255 | | - }, |
256 | | - resetDimensions() { |
257 | | - this.previewWidth = this.DEFAULT_PREVIEW_WIDTH |
258 | | - this.previewHeight = this.DEFAULT_PREVIEW_HEIGHT |
259 | | - OCP.AppConfig.deleteKey('libresign', 'footer_preview_width') |
260 | | - OCP.AppConfig.deleteKey('libresign', 'footer_preview_height') |
261 | | - }, |
262 | | - saveDimensions() { |
263 | | - if (Number(this.previewWidth) === this.DEFAULT_PREVIEW_WIDTH && Number(this.previewHeight) === this.DEFAULT_PREVIEW_HEIGHT) { |
264 | | - OCP.AppConfig.deleteKey('libresign', 'footer_preview_width') |
265 | | - OCP.AppConfig.deleteKey('libresign', 'footer_preview_height') |
266 | | - } else { |
267 | | - OCP.AppConfig.setValue('libresign', 'footer_preview_width', this.previewWidth) |
268 | | - OCP.AppConfig.setValue('libresign', 'footer_preview_height', this.previewHeight) |
269 | | - } |
270 | | - this.saveFooterTemplate() |
271 | | - }, |
272 | | - saveFooterTemplate() { |
273 | | - axios.post( |
274 | | - generateOcsUrl('/apps/libresign/api/v1/admin/footer-template'), |
275 | | - { |
276 | | - template: this.footerTemplate, |
277 | | - width: Number(this.previewWidth), |
278 | | - height: Number(this.previewHeight), |
279 | | - }, |
280 | | - { responseType: 'blob' } |
281 | | - ).then(response => { |
282 | | - this.setPdfPreview(response.data) |
283 | | - }).catch(error => { |
284 | | - console.error('Error saving footer template:', error) |
285 | | - }) |
286 | | - }, |
287 | | - setPdfPreview(blob) { |
288 | | - this.loadingPreview = true |
289 | | -
|
290 | | - if (this.$refs.pdfContainer) { |
291 | | - this.containerHeight = this.$refs.pdfContainer.offsetHeight |
292 | | - } |
293 | | -
|
294 | | - this.$nextTick(() => { |
295 | | - const timestamp = Date.now() |
296 | | - const pdfFile = new File([blob], `footer-preview-${timestamp}.pdf`, { type: 'application/pdf' }) |
297 | | - this.pdfPreviewFile = pdfFile |
298 | | - this.pdfKey++ |
299 | | - }) |
300 | | - }, |
301 | | - onPdfReady() { |
302 | | - this.loadingPreview = false |
303 | | - this.containerHeight = null |
304 | | - }, |
305 | | - changeZoomLevel(delta) { |
306 | | - this.zoomLevel = Number(this.zoomLevel) + delta |
307 | | - this.updateScale() |
308 | | - }, |
309 | | - onZoomInput() { |
310 | | - this.debouncedUpdateScale() |
311 | | - }, |
312 | | - updateScale() { |
313 | | - if (this.$refs.pdfPreview) { |
314 | | - this.$refs.pdfPreview.scale = this.zoomLevel / 100 |
315 | | - } |
| 161 | +}) |
| 162 | +
|
| 163 | +const emit = defineEmits<{ |
| 164 | + (event: 'template-reset'): void |
| 165 | +}>() |
| 166 | +
|
| 167 | +const vLinkify = Linkify |
| 168 | +
|
| 169 | +type TemplateVariableMeta = { |
| 170 | + description?: string |
| 171 | + type?: string |
| 172 | + example?: string |
| 173 | + default?: string |
| 174 | +} |
| 175 | +
|
| 176 | +type PdfPreviewRef = { |
| 177 | + scale: number |
| 178 | +} |
| 179 | +
|
| 180 | +type AppConfigApi = { |
| 181 | + deleteKey: (app: string, key: string) => void |
| 182 | + setValue: (app: string, key: string, value: string | number) => void |
| 183 | +} |
| 184 | +
|
| 185 | +const DEFAULT_PREVIEW_WIDTH = 595 |
| 186 | +const DEFAULT_PREVIEW_HEIGHT = 100 |
| 187 | +
|
| 188 | +const footerDescription = t('libresign', 'Configure the content displayed at the footer of the PDF. The text template uses Twig syntax: https://twig.symfony.com/') |
| 189 | +const footerTemplate = ref('') |
| 190 | +const pdfPreviewFile = ref<File | null>(null) |
| 191 | +const loadingPreview = ref(false) |
| 192 | +const pdfKey = ref(0) |
| 193 | +const zoomLevel = ref(loadState('libresign', 'footer_preview_zoom_level', 100)) |
| 194 | +const previewWidth = ref<number | string>(DEFAULT_PREVIEW_WIDTH) |
| 195 | +const previewHeight = ref<number | string>(DEFAULT_PREVIEW_HEIGHT) |
| 196 | +const containerHeight = ref<number | null>(null) |
| 197 | +const showVariablesDialog = ref(false) |
| 198 | +const templateVariables = ref<Record<string, TemplateVariableMeta>>(loadState('libresign', 'footer_template_variables', {})) |
| 199 | +const copiedVariable = ref<string | null>(null) |
| 200 | +
|
| 201 | +const pdfContainer = ref<HTMLElement | null>(null) |
| 202 | +const pdfPreview = ref<PdfPreviewRef | null>(null) |
| 203 | +
|
| 204 | +const showResetDimensions = computed(() => Number(previewWidth.value) !== DEFAULT_PREVIEW_WIDTH || Number(previewHeight.value) !== DEFAULT_PREVIEW_HEIGHT) |
| 205 | +
|
| 206 | +const appConfig = (globalThis as typeof globalThis & { OCP?: { AppConfig: AppConfigApi } }).OCP?.AppConfig |
| 207 | +
|
| 208 | +ensurePdfWorker() |
| 209 | +
|
| 210 | +function getVariableText(name: string) { |
| 211 | + return `{{ ${name} }}` |
| 212 | +} |
| 213 | +
|
| 214 | +function isCopied(name: string) { |
| 215 | + return copiedVariable.value === getVariableText(name) |
| 216 | +} |
| 217 | +
|
| 218 | +function copyToClipboard(text: string) { |
| 219 | + if (copiedVariable.value === text) { |
| 220 | + return |
| 221 | + } |
| 222 | +
|
| 223 | + try { |
| 224 | + navigator.clipboard.writeText(text) |
| 225 | + } catch { |
| 226 | + prompt('', text) |
| 227 | + } |
| 228 | +
|
| 229 | + copiedVariable.value = text |
| 230 | + setTimeout(() => { |
| 231 | + copiedVariable.value = null |
| 232 | + }, 2000) |
| 233 | +} |
| 234 | +
|
| 235 | +async function resetFooterTemplate() { |
| 236 | + emit('template-reset') |
| 237 | + resetDimensions() |
| 238 | + await axios.post(generateOcsUrl('/apps/libresign/api/v1/admin/footer-template')) |
| 239 | +} |
| 240 | +
|
| 241 | +function resetDimensions() { |
| 242 | + previewWidth.value = DEFAULT_PREVIEW_WIDTH |
| 243 | + previewHeight.value = DEFAULT_PREVIEW_HEIGHT |
| 244 | + appConfig?.deleteKey('libresign', 'footer_preview_width') |
| 245 | + appConfig?.deleteKey('libresign', 'footer_preview_height') |
| 246 | +} |
| 247 | +
|
| 248 | +function saveDimensions() { |
| 249 | + if (Number(previewWidth.value) === DEFAULT_PREVIEW_WIDTH && Number(previewHeight.value) === DEFAULT_PREVIEW_HEIGHT) { |
| 250 | + appConfig?.deleteKey('libresign', 'footer_preview_width') |
| 251 | + appConfig?.deleteKey('libresign', 'footer_preview_height') |
| 252 | + } else { |
| 253 | + appConfig?.setValue('libresign', 'footer_preview_width', previewWidth.value) |
| 254 | + appConfig?.setValue('libresign', 'footer_preview_height', previewHeight.value) |
| 255 | + } |
| 256 | + saveFooterTemplate() |
| 257 | +} |
| 258 | +
|
| 259 | +function saveFooterTemplate() { |
| 260 | + axios.post( |
| 261 | + generateOcsUrl('/apps/libresign/api/v1/admin/footer-template'), |
| 262 | + { |
| 263 | + template: footerTemplate.value, |
| 264 | + width: Number(previewWidth.value), |
| 265 | + height: Number(previewHeight.value), |
316 | 266 | }, |
317 | | - }, |
| 267 | + { responseType: 'blob' }, |
| 268 | + ).then(response => { |
| 269 | + setPdfPreview(response.data) |
| 270 | + }).catch(error => { |
| 271 | + console.error('Error saving footer template:', error) |
| 272 | + }) |
318 | 273 | } |
| 274 | +
|
| 275 | +function setPdfPreview(blob: Blob) { |
| 276 | + loadingPreview.value = true |
| 277 | +
|
| 278 | + if (pdfContainer.value) { |
| 279 | + containerHeight.value = pdfContainer.value.offsetHeight |
| 280 | + } |
| 281 | +
|
| 282 | + nextTick(() => { |
| 283 | + const timestamp = Date.now() |
| 284 | + pdfPreviewFile.value = new File([blob], `footer-preview-${timestamp}.pdf`, { type: 'application/pdf' }) |
| 285 | + pdfKey.value += 1 |
| 286 | + }) |
| 287 | +} |
| 288 | +
|
| 289 | +function onPdfReady() { |
| 290 | + loadingPreview.value = false |
| 291 | + containerHeight.value = null |
| 292 | +} |
| 293 | +
|
| 294 | +function changeZoomLevel(delta: number) { |
| 295 | + zoomLevel.value = Number(zoomLevel.value) + delta |
| 296 | + updateScale() |
| 297 | +} |
| 298 | +
|
| 299 | +function onZoomInput() { |
| 300 | + debouncedUpdateScale() |
| 301 | +} |
| 302 | +
|
| 303 | +function updateScale() { |
| 304 | + if (pdfPreview.value) { |
| 305 | + pdfPreview.value.scale = Number(zoomLevel.value) / 100 |
| 306 | + } |
| 307 | +} |
| 308 | +
|
| 309 | +const debouncedSaveFooterTemplate = debounce(saveFooterTemplate, 500) |
| 310 | +const debouncedUpdateScale = debounce(updateScale, 300) |
| 311 | +const debouncedSaveDimensions = debounce(saveDimensions, 500) |
| 312 | +
|
| 313 | +onMounted(() => { |
| 314 | + axios.get(generateOcsUrl('/apps/libresign/api/v1/admin/footer-template')) |
| 315 | + .then(response => { |
| 316 | + footerTemplate.value = response.data.ocs.data.template |
| 317 | + previewHeight.value = response.data.ocs.data.preview_height |
| 318 | + previewWidth.value = response.data.ocs.data.preview_width |
| 319 | + saveFooterTemplate() |
| 320 | + }) |
| 321 | +}) |
| 322 | +
|
| 323 | +defineExpose({ |
| 324 | + DEFAULT_PREVIEW_WIDTH, |
| 325 | + DEFAULT_PREVIEW_HEIGHT, |
| 326 | + footerDescription, |
| 327 | + footerTemplate, |
| 328 | + pdfPreviewFile, |
| 329 | + loadingPreview, |
| 330 | + pdfKey, |
| 331 | + zoomLevel, |
| 332 | + previewWidth, |
| 333 | + previewHeight, |
| 334 | + containerHeight, |
| 335 | + showVariablesDialog, |
| 336 | + templateVariables, |
| 337 | + copiedVariable, |
| 338 | + showResetDimensions, |
| 339 | + getVariableText, |
| 340 | + isCopied, |
| 341 | + copyToClipboard, |
| 342 | + resetFooterTemplate, |
| 343 | + resetDimensions, |
| 344 | + saveDimensions, |
| 345 | + saveFooterTemplate, |
| 346 | + setPdfPreview, |
| 347 | + onPdfReady, |
| 348 | + changeZoomLevel, |
| 349 | + onZoomInput, |
| 350 | + updateScale, |
| 351 | + debouncedSaveFooterTemplate, |
| 352 | + debouncedUpdateScale, |
| 353 | + debouncedSaveDimensions, |
| 354 | + pdfPreview, |
| 355 | +}) |
319 | 356 | </script> |
320 | 357 |
|
321 | 358 | <style lang="scss" scoped> |
|
0 commit comments