|
42 | 42 | </div> |
43 | 43 | </template> |
44 | 44 |
|
45 | | -<script> |
| 45 | +<script setup lang="ts"> |
46 | 46 | import { mdiFilePdfBox } from '@mdi/js' |
47 | 47 | import { formatFileSize } from '@nextcloud/files' |
48 | 48 | import { n, t } from '@nextcloud/l10n' |
49 | 49 | import Moment from '@nextcloud/moment' |
50 | 50 | import axios from '@nextcloud/axios' |
51 | 51 | import { generateOcsUrl } from '@nextcloud/router' |
| 52 | +import { onBeforeUnmount, onMounted, ref } from 'vue' |
52 | 53 |
|
53 | 54 | import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' |
54 | 55 | import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper' |
55 | 56 |
|
56 | 57 | import { FILE_STATUS } from '../constants.js' |
57 | 58 | import { getStatusLabel, getStatusIcon } from '../utils/fileStatus.js' |
58 | 59 |
|
59 | | -export default { |
| 60 | +defineOptions({ |
60 | 61 | name: 'FileStatusList', |
61 | | - components: { |
62 | | - NcEmptyContent, |
63 | | - NcIconSvgWrapper, |
64 | | - }, |
65 | | - props: { |
66 | | - fileIds: { |
67 | | - type: Array, |
68 | | - default: () => [], |
69 | | - }, |
70 | | - updateInterval: { |
71 | | - type: Number, |
72 | | - default: 2000, // 2 seconds |
73 | | - }, |
74 | | - }, |
75 | | - emits: ['file-signed', 'files-updated'], |
76 | | - setup() { |
77 | | - return { |
78 | | - t, |
79 | | - n, |
80 | | - formatFileSize, |
81 | | - getStatusIcon, |
82 | | - getStatusLabel, |
83 | | - mdiFilePdfBox, |
84 | | - } |
85 | | - }, |
86 | | - data() { |
87 | | - return { |
88 | | - files: [], |
89 | | - isLoading: false, |
90 | | - updatePollingInterval: null, |
91 | | - previouslySignedIds: [], |
92 | | - } |
93 | | - }, |
94 | | - mounted() { |
95 | | - this.loadFiles() |
96 | | - this.startUpdatePolling() |
97 | | - }, |
98 | | - beforeUnmount() { |
99 | | - this.stopUpdatePolling() |
100 | | - }, |
101 | | - methods: { |
102 | | - async loadFiles() { |
103 | | - if (this.isLoading || this.fileIds.length === 0) { |
104 | | - return |
105 | | - } |
| 62 | +}) |
| 63 | +
|
| 64 | +type FileEntry = { |
| 65 | + id: number |
| 66 | + uuid?: string |
| 67 | + name: string |
| 68 | + status?: string | number |
| 69 | + size?: number |
| 70 | + signedAt?: string |
| 71 | +} |
106 | 72 |
|
107 | | - this.isLoading = true |
108 | | - try { |
109 | | - const { data } = await axios.get( |
110 | | - generateOcsUrl('/apps/libresign/api/v1/file/list'), |
111 | | - { timeout: 10000 } |
112 | | - ) |
| 73 | +const props = withDefaults(defineProps<{ |
| 74 | + fileIds?: number[] |
| 75 | + updateInterval?: number |
| 76 | +}>(), { |
| 77 | + fileIds: () => [], |
| 78 | + updateInterval: 2000, |
| 79 | +}) |
| 80 | +
|
| 81 | +const emit = defineEmits<{ |
| 82 | + (event: 'file-signed', file: FileEntry): void |
| 83 | + (event: 'files-updated', files: FileEntry[]): void |
| 84 | +}>() |
| 85 | +
|
| 86 | +const files = ref<FileEntry[]>([]) |
| 87 | +const isLoading = ref(false) |
| 88 | +const updatePollingInterval = ref<ReturnType<typeof setInterval> | null>(null) |
| 89 | +const previouslySignedIds = ref<number[]>([]) |
| 90 | +
|
| 91 | +async function loadFiles() { |
| 92 | + if (isLoading.value || props.fileIds.length === 0) { |
| 93 | + return |
| 94 | + } |
113 | 95 |
|
114 | | - const fileList = data.ocs?.data?.data ?? [] |
115 | | - const fileMap = new Map(fileList.map(f => [f.id, f])) |
| 96 | + isLoading.value = true |
| 97 | + try { |
| 98 | + const { data } = await axios.get( |
| 99 | + generateOcsUrl('/apps/libresign/api/v1/file/list'), |
| 100 | + { timeout: 10000 }, |
| 101 | + ) |
116 | 102 |
|
117 | | - this.files = this.fileIds |
118 | | - .map(id => fileMap.get(id)) |
119 | | - .filter(Boolean) |
| 103 | + const fileList = data.ocs?.data?.data ?? [] |
| 104 | + const fileMap = new Map(fileList.map((file: FileEntry) => [file.id, file])) |
120 | 105 |
|
121 | | - this.$emit('files-updated', this.files) |
| 106 | + files.value = props.fileIds |
| 107 | + .map(id => fileMap.get(id)) |
| 108 | + .filter((file): file is FileEntry => Boolean(file)) |
122 | 109 |
|
123 | | - const signedFile = this.files.find(f => f.status === FILE_STATUS.SIGNED) |
124 | | - if (signedFile && !this.previouslySignedIds?.includes(signedFile.id)) { |
125 | | - this.$emit('file-signed', signedFile) |
126 | | - } |
| 110 | + emit('files-updated', files.value) |
127 | 111 |
|
128 | | - this.previouslySignedIds = this.files |
129 | | - .filter(f => f.status === FILE_STATUS.SIGNED) |
130 | | - .map(f => f.id) |
131 | | - } catch (error) { |
132 | | - console.error('[libresign][FileStatusList] Error loading files:', error) |
133 | | - } finally { |
134 | | - this.isLoading = false |
135 | | - } |
136 | | - }, |
137 | | - startUpdatePolling() { |
138 | | - this.updatePollingInterval = setInterval(() => { |
139 | | - this.loadFiles() |
140 | | - }, this.updateInterval) |
141 | | - }, |
142 | | - stopUpdatePolling() { |
143 | | - if (this.updatePollingInterval) { |
144 | | - clearInterval(this.updatePollingInterval) |
145 | | - this.updatePollingInterval = null |
146 | | - } |
147 | | - }, |
148 | | - getStatusClass(status) { |
149 | | - const statusMap = { |
150 | | - [FILE_STATUS.NOT_LIBRESIGN_FILE]: 'not-libresign', |
151 | | - [FILE_STATUS.DRAFT]: 'draft', |
152 | | - [FILE_STATUS.ABLE_TO_SIGN]: 'ready', |
153 | | - [FILE_STATUS.PARTIAL_SIGNED]: 'partial', |
154 | | - [FILE_STATUS.SIGNED]: 'signed', |
155 | | - [FILE_STATUS.DELETED]: 'deleted', |
156 | | - [FILE_STATUS.SIGNING_IN_PROGRESS]: 'signing', |
157 | | - } |
158 | | - return statusMap[status] || 'unknown' |
159 | | - }, |
160 | | - formatDate(date) { |
161 | | - if (!date) return '' |
162 | | - return Moment(date).calendar() |
163 | | - }, |
164 | | - }, |
| 112 | + const signedFile = files.value.find(file => file.status === FILE_STATUS.SIGNED) |
| 113 | + if (signedFile && !previouslySignedIds.value.includes(signedFile.id)) { |
| 114 | + emit('file-signed', signedFile) |
| 115 | + } |
| 116 | +
|
| 117 | + previouslySignedIds.value = files.value |
| 118 | + .filter(file => file.status === FILE_STATUS.SIGNED) |
| 119 | + .map(file => file.id) |
| 120 | + } catch (error) { |
| 121 | + console.error('[libresign][FileStatusList] Error loading files:', error) |
| 122 | + } finally { |
| 123 | + isLoading.value = false |
| 124 | + } |
165 | 125 | } |
| 126 | +
|
| 127 | +function startUpdatePolling() { |
| 128 | + updatePollingInterval.value = setInterval(() => { |
| 129 | + loadFiles() |
| 130 | + }, props.updateInterval) |
| 131 | +} |
| 132 | +
|
| 133 | +function stopUpdatePolling() { |
| 134 | + if (updatePollingInterval.value) { |
| 135 | + clearInterval(updatePollingInterval.value) |
| 136 | + updatePollingInterval.value = null |
| 137 | + } |
| 138 | +} |
| 139 | +
|
| 140 | +function getStatusClass(status: string | number | undefined) { |
| 141 | + const statusMap: Record<string | number, string> = { |
| 142 | + [FILE_STATUS.NOT_LIBRESIGN_FILE]: 'not-libresign', |
| 143 | + [FILE_STATUS.DRAFT]: 'draft', |
| 144 | + [FILE_STATUS.ABLE_TO_SIGN]: 'ready', |
| 145 | + [FILE_STATUS.PARTIAL_SIGNED]: 'partial', |
| 146 | + [FILE_STATUS.SIGNED]: 'signed', |
| 147 | + [FILE_STATUS.DELETED]: 'deleted', |
| 148 | + [FILE_STATUS.SIGNING_IN_PROGRESS]: 'signing', |
| 149 | + } |
| 150 | + return status === undefined ? 'unknown' : statusMap[status] || 'unknown' |
| 151 | +} |
| 152 | +
|
| 153 | +function formatDate(date: string | undefined) { |
| 154 | + if (!date) { |
| 155 | + return '' |
| 156 | + } |
| 157 | + return Moment(date).calendar() |
| 158 | +} |
| 159 | +
|
| 160 | +onMounted(() => { |
| 161 | + loadFiles() |
| 162 | + startUpdatePolling() |
| 163 | +}) |
| 164 | +
|
| 165 | +onBeforeUnmount(() => { |
| 166 | + stopUpdatePolling() |
| 167 | +}) |
| 168 | +
|
| 169 | +defineExpose({ |
| 170 | + files, |
| 171 | + isLoading, |
| 172 | + updatePollingInterval, |
| 173 | + previouslySignedIds, |
| 174 | + loadFiles, |
| 175 | + startUpdatePolling, |
| 176 | + stopUpdatePolling, |
| 177 | + getStatusClass, |
| 178 | + formatDate, |
| 179 | + getStatusIcon, |
| 180 | + getStatusLabel, |
| 181 | +}) |
166 | 182 | </script> |
167 | 183 |
|
168 | 184 | <style lang="scss" scoped> |
|
0 commit comments