Skip to content

Commit 3202015

Browse files
refactor(vue3): replace FileEntry mixin with composable
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent ad432cd commit 3202015

4 files changed

Lines changed: 160 additions & 103 deletions

File tree

src/composables/useFileEntry.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { computed } from '@vue/reactivity'
7+
8+
import { useSidebarStore } from '../store/sidebar.js'
9+
10+
type FileEntryMetadata = {
11+
extension?: string
12+
}
13+
14+
export type FileEntrySource = {
15+
id: number
16+
name: string
17+
nodeType?: string
18+
created_at?: number | string | Date | null
19+
metadata?: FileEntryMetadata
20+
[key: string]: unknown
21+
}
22+
23+
type FileEntryStore = {
24+
selectFile: (id: number) => void
25+
}
26+
27+
type ActionsMenuStore = {
28+
opened: number | null
29+
}
30+
31+
type FileEntryProps = {
32+
source: FileEntrySource
33+
}
34+
35+
export function useFileEntry(
36+
props: FileEntryProps,
37+
options: {
38+
actionsMenuStore: ActionsMenuStore
39+
filesStore: FileEntryStore
40+
},
41+
) {
42+
const sidebarStore = useSidebarStore()
43+
44+
const mtime = computed(() => new Date(props.source?.created_at || Date.now()))
45+
const openedMenu = computed({
46+
get: () => options.actionsMenuStore.opened === props.source.id,
47+
set: (opened: boolean) => {
48+
options.actionsMenuStore.opened = opened ? props.source.id : null
49+
},
50+
})
51+
const mtimeOpacity = computed(() => {
52+
const maxOpacityTime = 31 * 24 * 60 * 60 * 1000
53+
const timestamp = mtime.value?.getTime?.()
54+
55+
if (!timestamp) {
56+
return {}
57+
}
58+
59+
const ratio = Math.round(Math.min(100, 100 * (maxOpacityTime - (Date.now() - timestamp)) / maxOpacityTime))
60+
if (ratio < 0) {
61+
return {}
62+
}
63+
64+
return {
65+
color: `color-mix(in srgb, var(--color-main-text) ${ratio}%, var(--color-text-maxcontrast))`,
66+
}
67+
})
68+
const fileExtension = computed(() => {
69+
if (props.source.nodeType === 'envelope') {
70+
return ''
71+
}
72+
return props.source.metadata?.extension ? `.${props.source.metadata.extension}` : '.pdf'
73+
})
74+
75+
function onRightClick(event: MouseEvent) {
76+
if (openedMenu.value) {
77+
return
78+
}
79+
80+
options.actionsMenuStore.opened = props.source.id
81+
event.preventDefault()
82+
event.stopPropagation()
83+
84+
const target = event.currentTarget as HTMLElement | null
85+
const root = target?.closest('.app-content') as HTMLElement | null
86+
if (!root) {
87+
return
88+
}
89+
90+
const contentRect = root.getBoundingClientRect()
91+
root.style.setProperty('--mouse-pos-x', `${Math.max(0, event.clientX - contentRect.left - 200)}px`)
92+
root.style.setProperty('--mouse-pos-y', `${Math.max(0, event.clientY - contentRect.top)}px`)
93+
}
94+
95+
function openDetailsIfAvailable(event: Event) {
96+
event.preventDefault()
97+
event.stopPropagation()
98+
options.filesStore.selectFile(props.source.id)
99+
sidebarStore.activeRequestSignatureTab()
100+
}
101+
102+
return {
103+
mtime,
104+
openedMenu,
105+
mtimeOpacity,
106+
fileExtension,
107+
onRightClick,
108+
openDetailsIfAvailable,
109+
}
110+
}

src/views/FilesList/FileEntry/FileEntry.vue

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
<script setup lang="ts">
5656
import { t } from '@nextcloud/l10n'
57-
import { getCurrentInstance, ref } from 'vue'
57+
import { ref } from 'vue'
5858
5959
import { showSuccess } from '@nextcloud/dialogs'
6060
import NcDateTime from '@nextcloud/vue/components/NcDateTime'
@@ -66,32 +66,45 @@ import FileEntryPreview from './FileEntryPreview.vue'
6666
import FileEntrySigners from './FileEntrySigners.vue'
6767
import FileEntryStatus from './FileEntryStatus.vue'
6868
69-
import FileEntryMixin from './FileEntryMixin.js'
69+
import { useFileEntry, type FileEntrySource } from '../../../composables/useFileEntry.js'
7070
import { useActionsMenuStore } from '../../../store/actionsmenu.js'
7171
import { useFilesStore } from '../../../store/files.js'
7272
7373
defineOptions({
7474
name: 'FileEntry',
75-
mixins: [FileEntryMixin],
7675
})
7776
77+
type FileEntryActionsRef = {
78+
doRename: (newName: string) => Promise<void>
79+
}
80+
81+
type FileEntryNameRef = {
82+
startRenaming?: () => void
83+
stopRenaming?: () => void
84+
}
85+
86+
const props = defineProps<{
87+
source: FileEntrySource
88+
loading: boolean
89+
}>()
90+
7891
const actionsMenuStore = useActionsMenuStore()
7992
const filesStore = useFilesStore()
93+
const { mtime, openedMenu, mtimeOpacity, fileExtension, onRightClick, openDetailsIfAvailable } = useFileEntry(props, {
94+
actionsMenuStore,
95+
filesStore,
96+
})
97+
const actions = ref<FileEntryActionsRef | null>(null)
98+
const name = ref<FileEntryNameRef | null>(null)
8099
const isRenaming = ref(false)
81100
const renamingSaving = ref(false)
82-
const instance = getCurrentInstance()
83-
84-
function getVm() {
85-
return instance?.proxy as any
86-
}
87101
88102
async function onRename(newName: string) {
89-
const vm = getVm()
90-
const oldName = vm.source.name
103+
const oldName = props.source.name
91104
renamingSaving.value = true
92105
try {
93-
await vm.$refs.actions.doRename(newName)
94-
vm.$refs.name?.stopRenaming?.()
106+
await actions.value?.doRename(newName)
107+
name.value?.stopRenaming?.()
95108
showSuccess(t('libresign', 'Renamed "{oldName}" to "{newName}"', {
96109
oldName,
97110
newName,
@@ -102,7 +115,7 @@ async function onRename(newName: string) {
102115
}
103116
104117
function onStartRename() {
105-
getVm().$refs.name?.startRenaming?.()
118+
name.value?.startRenaming?.()
106119
}
107120
108121
function onFileRenaming(nextIsRenaming: boolean) {
@@ -112,6 +125,14 @@ function onFileRenaming(nextIsRenaming: boolean) {
112125
defineExpose({
113126
actionsMenuStore,
114127
filesStore,
128+
mtime,
129+
openedMenu,
130+
mtimeOpacity,
131+
fileExtension,
132+
onRightClick,
133+
openDetailsIfAvailable,
134+
actions,
135+
name,
115136
isRenaming,
116137
renamingSaving,
117138
onRename,

src/views/FilesList/FileEntry/FileEntryGrid.vue

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,34 @@ import FileEntryPreview from './FileEntryPreview.vue'
5858
import FileEntrySigners from './FileEntrySigners.vue'
5959
import FileEntryStatus from './FileEntryStatus.vue'
6060
61-
import FileEntryMixin from './FileEntryMixin.js'
61+
import { useFileEntry, type FileEntrySource } from '../../../composables/useFileEntry.js'
6262
import { useActionsMenuStore } from '../../../store/actionsmenu.js'
6363
import { useFilesStore } from '../../../store/files.js'
6464
6565
defineOptions({
6666
name: 'FileEntryGrid',
67-
mixins: [FileEntryMixin],
6867
})
6968
69+
const props = defineProps<{
70+
source: FileEntrySource
71+
loading: boolean
72+
}>()
73+
7074
const actionsMenuStore = useActionsMenuStore()
7175
const filesStore = useFilesStore()
76+
const { mtime, openedMenu, mtimeOpacity, fileExtension, onRightClick, openDetailsIfAvailable } = useFileEntry(props, {
77+
actionsMenuStore,
78+
filesStore,
79+
})
7280
7381
defineExpose({
7482
actionsMenuStore,
7583
filesStore,
84+
mtime,
85+
openedMenu,
86+
mtimeOpacity,
87+
fileExtension,
88+
onRightClick,
89+
openDetailsIfAvailable,
7690
})
7791
</script>

src/views/FilesList/FileEntry/FileEntryMixin.js

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

0 commit comments

Comments
 (0)