Skip to content

Commit f207790

Browse files
committed
fix(types): normalize visible elements typing
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent f622c6f commit f207790

1 file changed

Lines changed: 114 additions & 60 deletions

File tree

src/components/Request/VisibleElements.vue

Lines changed: 114 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
<div v-else class="visible-elements-container">
1616
<div class="sign-details">
1717
<div class="modal_name">
18-
<NcChip :text="statusLabel"
18+
<NcChip :text="statusLabel ?? ''"
1919
:variant="isDraft ? 'warning' : 'primary'"
20-
:aria-label="t('libresign', 'Document status: {status}', { status: statusLabel })"
20+
:aria-label="t('libresign', 'Document status: {status}', { status: statusLabel ?? '' })"
2121
no-close />
2222
<h2 class="name">{{ document.name }}</h2>
2323
</div>
@@ -73,7 +73,7 @@
7373
:file-names="pdfFileNames"
7474
:signers="document.signers"
7575
@pdf-editor:end-init="updateSigners"
76-
@pdf-editor:on-delete-signer="onDeleteSigner">
76+
@pdf-editor:on-delete-signer="handleDeleteSigner">
7777
</PdfEditor>
7878
</div>
7979
</NcModal>
@@ -86,9 +86,12 @@ import axios from '@nextcloud/axios'
8686
import { getCapabilities } from '@nextcloud/capabilities'
8787
import { showSuccess, showError } from '@nextcloud/dialogs'
8888
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
89+
import type { Event as NextcloudEvent, EventHandler } from '@nextcloud/event-bus'
8990
import { loadState } from '@nextcloud/initial-state'
9091
import { generateOcsUrl } from '@nextcloud/router'
9192
import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
93+
import type { ComponentPublicInstance } from 'vue'
94+
import type { PDFElementObject } from '@libresign/pdf-elements'
9295
9396
import NcButton from '@nextcloud/vue/components/NcButton'
9497
import NcChip from '@nextcloud/vue/components/NcChip'
@@ -103,56 +106,54 @@ import { FILE_STATUS } from '../../constants.js'
103106
import { useFilesStore } from '../../store/files.js'
104107
import {
105108
aggregateVisibleElementsByFiles,
109+
type DocumentData,
110+
type FileData,
106111
findFileById,
107112
getFileSigners,
108113
getFileUrl,
109114
getVisibleElementsFromDocument,
110115
idsMatch,
116+
type Signer as VisibleElementsSigner,
117+
type VisibleElement,
111118
} from '../../services/visibleElementsService'
112-
113-
type VisibleElementCoordinate = {
114-
page: number
115-
width: number
116-
height: number
117-
left: number
118-
top: number
119-
}
120-
121-
type VisibleElementPayload = {
122-
type: 'signature'
123-
elementId?: string
124-
fileId?: number
125-
signRequestId?: number | string
126-
coordinates: VisibleElementCoordinate
127-
}
119+
import type { NextcloudCapabilities } from '../../types/capabilities'
128120
129121
type SignerIdentifyMethod = {
130122
method: string
131123
value: string
132124
}
133125
134-
type FileSigner = {
135-
signRequestId?: number | string
126+
type FileSigner = VisibleElementsSigner & {
136127
identifyMethods?: SignerIdentifyMethod[]
137-
[key: string]: unknown
138128
}
139129
140-
type DocumentFile = {
130+
type VisibleElementPayload = VisibleElement & {
131+
type: 'signature'
132+
elementId?: string
133+
fileId?: number
134+
signRequestId?: number | string
135+
coordinates: {
136+
page: number
137+
width: number
138+
height: number
139+
left: number
140+
top: number
141+
}
142+
}
143+
144+
type DocumentFile = FileData & {
141145
id: number
142146
name: string
143147
metadata?: {
144148
extension?: string
145149
p?: number
146150
d?: Array<{ h?: number }>
147151
}
148-
file?: unknown
149-
files?: Array<{ file?: unknown }>
150152
visibleElements?: VisibleElementPayload[] | null
151153
signers?: FileSigner[]
152-
[key: string]: unknown
153154
}
154155
155-
type DocumentModel = {
156+
type DocumentModel = DocumentData & {
156157
id?: number
157158
uuid?: string
158159
name?: string
@@ -162,8 +163,7 @@ type DocumentModel = {
162163
settings?: { signerFileUuid?: string }
163164
files?: DocumentFile[]
164165
visibleElements?: VisibleElementPayload[]
165-
signers?: Array<Record<string, unknown>>
166-
[key: string]: unknown
166+
signers?: FileSigner[]
167167
}
168168
169169
type FilePageInfo = {
@@ -173,13 +173,15 @@ type FilePageInfo = {
173173
fileName: string
174174
}
175175
176+
type PdfInput = string | Blob | ArrayBuffer | ArrayBufferView | Record<string, unknown>
177+
176178
type PdfObjectSigner = {
177179
element?: { elementId?: string }
178180
identifyMethods?: SignerIdentifyMethod[]
179181
[key: string]: unknown
180182
}
181183
182-
type PdfObject = {
184+
type PdfObject = PDFElementObject & {
183185
signer?: PdfObjectSigner
184186
pageNumber: number
185187
x: number
@@ -196,7 +198,7 @@ type PdfElementsRef = {
196198
isAddingMode?: boolean
197199
}
198200
199-
type PdfEditorRef = {
201+
type PdfEditorRef = ComponentPublicInstance & {
200202
$refs?: { pdfElements?: PdfElementsRef }
201203
startAddingSigner?: (signer: Record<string, unknown>, size: { width: number; height: number }) => boolean
202204
cancelAdding?: () => void
@@ -213,32 +215,68 @@ type FilesStore = {
213215
saveOrUpdateSignatureRequest: (payload: { visibleElements: VisibleElementPayload[] }) => Promise<SaveResponse>
214216
}
215217
218+
const normalizeVisibleElements = (elements: VisibleElement[]): VisibleElementPayload[] =>
219+
elements.flatMap((element) => {
220+
if (element.type !== 'signature' || !element.coordinates) {
221+
return []
222+
}
223+
224+
const page = Number(element.coordinates.page)
225+
const left = Number(element.coordinates.left)
226+
const top = Number(element.coordinates.top)
227+
const width = Number((element.coordinates as VisibleElementPayload['coordinates']).width)
228+
const height = Number((element.coordinates as VisibleElementPayload['coordinates']).height)
229+
230+
if (![page, left, top, width, height].every(Number.isFinite)) {
231+
return []
232+
}
233+
234+
return [{
235+
type: 'signature',
236+
elementId: typeof element.elementId === 'string' ? element.elementId : undefined,
237+
fileId: typeof element.fileId === 'number' ? element.fileId : Number(element.fileId),
238+
signRequestId: element.signRequestId,
239+
coordinates: {
240+
page,
241+
left,
242+
top,
243+
width,
244+
height,
245+
},
246+
} satisfies VisibleElementPayload]
247+
})
248+
216249
defineOptions({
217250
name: 'VisibleElements',
218251
})
219252
220-
const filesStore = useFilesStore() as FilesStore
253+
const filesStore = useFilesStore() as unknown as FilesStore
221254
const instance = getCurrentInstance()
222255
const pdfEditor = ref<PdfEditorRef | null>(null)
223256
const canRequestSign = ref(loadState('libresign', 'can_request_sign', false))
224257
const modal = ref(false)
225258
const loading = ref(false)
226259
const signerSelected = ref<Record<string, unknown> | null>(null)
227-
const width = ref(getCapabilities().libresign.config['sign-elements']['full-signature-width'])
228-
const height = ref(getCapabilities().libresign.config['sign-elements']['full-signature-height'])
260+
const capabilities = getCapabilities() as NextcloudCapabilities
261+
const width = ref(capabilities.libresign?.config?.['sign-elements']?.['full-signature-width'] ?? 180)
262+
const height = ref(capabilities.libresign?.config?.['sign-elements']?.['full-signature-height'] ?? 60)
229263
const filePagesMap = ref<Record<number, FilePageInfo>>({})
230264
const elementsLoaded = ref(false)
231265
232-
const document = computed(() => filesStore.getFile())
266+
const document = computed<DocumentModel>(() => filesStore.getFile())
267+
const documentFiles = computed<DocumentFile[]>(() => Array.isArray(document.value.files) ? document.value.files as DocumentFile[] : [])
233268
const status = computed(() => Number(document.value?.status ?? -1))
234269
const isDraft = computed(() => status.value === FILE_STATUS.DRAFT)
235-
const canSave = computed(() => [FILE_STATUS.DRAFT, FILE_STATUS.ABLE_TO_SIGN, FILE_STATUS.PARTIAL_SIGNED].includes(status.value))
270+
const canSave = computed(() => ([FILE_STATUS.DRAFT, FILE_STATUS.ABLE_TO_SIGN, FILE_STATUS.PARTIAL_SIGNED] as number[]).includes(status.value))
236271
const canSign = computed(() => status.value === FILE_STATUS.ABLE_TO_SIGN && (document.value?.settings?.signerFileUuid ?? '').length > 0)
237272
const variantOfSaveButton = computed(() => canSave.value ? 'primary' : 'secondary')
238273
const variantOfSignButton = computed(() => canSave.value ? 'secondary' : 'primary')
239274
const statusLabel = computed(() => document.value.statusText)
240-
const pdfFiles = computed(() => (document.value.files || []).map(file => getFileUrl(file)).filter(Boolean))
241-
const pdfFileNames = computed(() => (document.value.files || []).map(file => `${file.name}.${file.metadata?.extension || 'pdf'}`))
275+
const pdfFiles = computed<PdfInput[]>(() => documentFiles.value.flatMap((file) => {
276+
const fileUrl = getFileUrl(file)
277+
return fileUrl ? [fileUrl] : []
278+
}))
279+
const pdfFileNames = computed(() => documentFiles.value.map(file => `${file.name}.${file.metadata?.extension || 'pdf'}`))
242280
const documentNameWithExtension = computed(() => {
243281
const currentDocument = document.value
244282
if (!currentDocument.metadata?.extension) {
@@ -260,13 +298,13 @@ async function showModal() {
260298
if (!canRequestSign.value) {
261299
return
262300
}
263-
if (getCapabilities()?.libresign?.config?.['sign-elements']?.['is-available'] === false) {
301+
if (capabilities.libresign?.config?.['sign-elements']?.['is-available'] === false) {
264302
return
265303
}
266304
modal.value = true
267305
filesStore.loading = true
268306
269-
if (!document.value.files || document.value.files.length === 0) {
307+
if (documentFiles.value.length === 0) {
270308
await fetchFiles()
271309
}
272310
@@ -282,34 +320,36 @@ async function fetchFiles() {
282320
},
283321
})
284322
const childFiles = response?.data?.ocs?.data?.data || []
285-
document.value.files = Array.isArray(childFiles) ? childFiles : []
323+
document.value.files = Array.isArray(childFiles) ? childFiles as DocumentFile[] : []
286324
287-
const allVisibleElements = aggregateVisibleElementsByFiles(document.value.files)
325+
const allVisibleElements = aggregateVisibleElementsByFiles(documentFiles.value)
288326
if (allVisibleElements.length > 0) {
289-
document.value.visibleElements = allVisibleElements
327+
document.value.visibleElements = normalizeVisibleElements(allVisibleElements)
290328
return
291329
}
292330
293331
const nestedDocumentElements = getVisibleElementsFromDocument(document.value)
294332
if (nestedDocumentElements.length > 0) {
295-
document.value.visibleElements = nestedDocumentElements
333+
document.value.visibleElements = normalizeVisibleElements(nestedDocumentElements)
296334
}
297335
}
298336
299337
function buildFilePagesMap() {
300338
filePagesMap.value = {}
301339
302-
const filesToProcess = document.value.files || []
303-
if (!Array.isArray(filesToProcess)) {
304-
return
305-
}
340+
const filesToProcess = documentFiles.value
306341
307342
let currentPage = 1
308343
filesToProcess.forEach((file, index) => {
309344
const pageCount = file.metadata?.p || 0
345+
const fileId = typeof file.id === 'number' ? file.id : Number(file.id)
346+
if (!Number.isFinite(fileId)) {
347+
currentPage += pageCount
348+
return
349+
}
310350
for (let pageIndex = 0; pageIndex < pageCount; pageIndex++) {
311351
filePagesMap.value[currentPage + pageIndex] = {
312-
id: file.id,
352+
id: fileId,
313353
fileIndex: index,
314354
startPage: currentPage,
315355
fileName: file.name,
@@ -327,13 +367,13 @@ function closeModal() {
327367
}
328368
329369
function getPageHeightForFile(fileId: number, page: number) {
330-
const filesToSearch = document.value.files || []
370+
const filesToSearch = documentFiles.value
331371
const fileInfo = filesToSearch.find(file => file.id === fileId)
332372
return fileInfo?.metadata?.d?.[page - 1]?.h
333373
}
334374
335375
async function updateSigners() {
336-
const filesToProcess = document.value.files || []
376+
const filesToProcess = documentFiles.value
337377
if (elementsLoaded.value || filesToProcess.length === 0) {
338378
return
339379
}
@@ -419,7 +459,7 @@ function stopAddSigner() {
419459
signerSelected.value = null
420460
}
421461
422-
async function onDeleteSigner(object: { signer?: { element?: { elementId?: string } } }) {
462+
async function onDeleteSigner(object: PdfObject) {
423463
if (!object?.signer?.element?.elementId) {
424464
return
425465
}
@@ -429,6 +469,10 @@ async function onDeleteSigner(object: { signer?: { element?: { elementId?: strin
429469
}))
430470
}
431471
472+
function handleDeleteSigner(object: unknown) {
473+
void onDeleteSigner(object as PdfObject)
474+
}
475+
432476
async function goToSign() {
433477
const uuid = document.value.settings?.signerFileUuid
434478
if (await save()) {
@@ -456,14 +500,22 @@ async function save() {
456500
}
457501
}
458502
503+
const handleShowVisibleElements = (() => {
504+
void showModal()
505+
}) as EventHandler<NextcloudEvent>
506+
507+
const handleSelectSigner = ((event: NextcloudEvent) => {
508+
onSelectSigner((event as CustomEvent<Record<string, unknown>>).detail)
509+
}) as EventHandler<NextcloudEvent>
510+
459511
function buildVisibleElements() {
460512
const visibleElements: VisibleElementPayload[] = []
461-
const currentFiles = document.value.files || []
513+
const currentFiles = documentFiles.value
462514
const pdfElements = getPdfElements()
463515
const numDocuments = currentFiles.length
464516
465517
for (let docIndex = 0; docIndex < numDocuments; docIndex++) {
466-
const objects = pdfElements?.getAllObjects(docIndex) || []
518+
const objects = (pdfElements?.getAllObjects(docIndex) || []) as PdfObject[]
467519
objects.forEach((object) => {
468520
if (!object.signer) return
469521
@@ -506,9 +558,11 @@ function buildVisibleElements() {
506558
if (!fileInfo || !Array.isArray(fileInfo.signers)) {
507559
return
508560
}
509-
const envIdMethods = (object.signer.identifyMethods || []).map((method) => `${method.method}:${method.value}`).sort().join('|')
561+
const envIdentifyMethods = Array.isArray(object.signer.identifyMethods) ? object.signer.identifyMethods as SignerIdentifyMethod[] : []
562+
const envIdMethods = envIdentifyMethods.map((method) => `${method.method}:${method.value}`).sort().join('|')
510563
const candidate = fileInfo.signers.find((signer) => {
511-
const childIdMethods = (signer.identifyMethods || []).map((method) => `${method.method}:${method.value}`).sort().join('|')
564+
const childIdentifyMethods = Array.isArray(signer.identifyMethods) ? signer.identifyMethods : []
565+
const childIdMethods = childIdentifyMethods.map((method: SignerIdentifyMethod) => `${method.method}:${method.value}`).sort().join('|')
512566
return childIdMethods === envIdMethods
513567
})
514568
if (!candidate?.signRequestId) {
@@ -524,13 +578,13 @@ function buildVisibleElements() {
524578
}
525579
526580
onMounted(() => {
527-
subscribe('libresign:show-visible-elements', showModal)
528-
subscribe('libresign:visible-elements-select-signer', onSelectSigner)
581+
subscribe('libresign:show-visible-elements', handleShowVisibleElements)
582+
subscribe('libresign:visible-elements-select-signer', handleSelectSigner)
529583
})
530584
531585
onBeforeUnmount(() => {
532-
unsubscribe('libresign:show-visible-elements', showModal)
533-
unsubscribe('libresign:visible-elements-select-signer', onSelectSigner)
586+
unsubscribe('libresign:show-visible-elements', handleShowVisibleElements)
587+
unsubscribe('libresign:visible-elements-select-signer', handleSelectSigner)
534588
})
535589
536590
defineExpose({

0 commit comments

Comments
 (0)