Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web-app/packages/admin-app/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const createRouter = (pinia: Pinia) => {
props: true
},
{
path: 'tree/:location?',
path: 'tree/:location(.*)?',
name: AdminRoutes.ProjectTree,
component: ProjectFilesView,
props: true
Expand Down
6 changes: 3 additions & 3 deletions web-app/packages/app/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,12 @@ export const createRouter = (pinia: Pinia) => {
meta: {
breadcrump: [{ title: 'Projects', path: '/projects' }]
},
redirect: { name: 'project-tree' },
redirect: { name: ProjectRouteName.ProjectTree },

children: [
{
path: 'tree/:location?',
name: 'project-tree',
path: 'tree/:location(.*)?',
name: ProjectRouteName.ProjectTree,
component: FileBrowserView,
props: true,
meta: { public: true }
Expand Down
209 changes: 126 additions & 83 deletions web-app/packages/lib/src/modules/project/components/FilesTable.vue
Original file line number Diff line number Diff line change
@@ -1,69 +1,110 @@
<template>
<PDataView
:value="items"
:data-key="'path'"
:paginator="items.length > itemPerPage"
:sort-field="options.sortBy"
:sort-order="options.sortDesc"
:rows="itemPerPage"
:paginator-template="'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink'"
data-cy="file-browser-table"
>
<template #header>
<div class="grid grid-nogutter">
<div
v-for="col in columns"
:class="[`col-${col.cols ?? 3}`, 'paragraph-p6 hidden lg:flex']"
:key="col.text"
<div>
<PBreadcrumb
:model="breadcrumps"
:pt="{
root: {
style: {
backgroundColor: 'transparent'
},
class: 'border-none p-4 w-full overflow-x-auto'
}
}"
>
<template #separator>
<i class="ti ti-slash" />
</template>
<template #item="{ item, props }">
<router-link
v-if="item.path"
v-slot="{ href, navigate }"
:to="{
path: item.path
}"
custom
>
{{ col.text }}
<a :href="href" v-bind="props.action" @click="navigate">
<span :class="[item.icon, 'paragraph-p6']" />
{{ '&nbsp;' }}
<span
:class="[
'opacity-80 paragraph-p6',
item.active ? 'text-color-forest font-semibold' : 'text-color'
]"
>{{ item.label }}</span
>
</a>
</router-link>
</template>
</PBreadcrumb>
<PDataView
:value="items"
:data-key="'path'"
:paginator="items.length > ITEMS_PER_PAGE"
:sort-field="options.sortBy"
:sort-order="options.sortDesc"
:rows="ITEMS_PER_PAGE"
:paginator-template="'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink'"
data-cy="file-browser-table"
>
<template #header>
<div class="grid grid-nogutter">
<div
v-for="col in columns"
:class="[`col-${col.cols ?? 2}`, 'paragraph-p6 hidden lg:flex']"
:key="col.text"
>
{{ col.text }}
</div>
<div class="col-12 flex lg:hidden">Files</div>
</div>
<div class="col-12 flex lg:hidden">Files</div>
</div>
</template>
<template #list="slotProps">
<div
v-for="item in slotProps.items"
:key="item.id"
class="grid grid-nogutter px-4 py-3 mt-0 border-bottom-1 border-gray-200 paragraph-p6 hover:bg-gray-50 cursor-pointer row-gap-2"
@click.prevent="rowClick(item)"
>
</template>
<template #list="slotProps">
<div
v-for="col in columns"
:class="[`lg:col-${col.cols ?? 3}`, 'flex align-items-center col-12']"
:key="col.key"
v-for="item in slotProps.items"
:key="item.id"
class="grid grid-nogutter px-4 py-3 mt-0 border-bottom-1 border-gray-200 paragraph-p6 hover:bg-gray-50 cursor-pointer row-gap-2"
@click.prevent="rowClick(item)"
>
<span
v-if="col.key === 'name'"
class="font-semibold mb-2 lg:mb-0 m-0"
<div
v-for="col in columns"
:class="[
`lg:col-${col.cols ?? 2}`,
'flex align-items-center col-12'
]"
:key="col.key"
>
<file-icon :file="item" />{{ item.name }}
</span>
<span
v-else-if="col.key === 'mtime'"
v-tooltip.bottom="{ value: $filters.datetime(item.mtime) }"
class="opacity-80"
>
{{ $filters.timediff(item.mtime) }}
</span>
<span v-else class="opacity-80">{{
item.size !== undefined ? $filters.filesize(item.size) : ''
}}</span>
<span
v-if="col.key === 'name'"
class="font-semibold mb-2 lg:mb-0 m-0"
>
<file-icon :file="item" />{{ item.name }}
</span>
<span
v-else-if="col.key === 'mtime'"
v-tooltip.bottom="{ value: $filters.datetime(item.mtime) }"
class="opacity-80"
>
{{ item.mtime ? $filters.timediff(item.mtime) : '' }}
</span>
<span v-else class="opacity-80">{{
item.size !== undefined ? $filters.filesize(item.size) : ''
}}</span>
</div>
</div>
</template>
<template #empty>
<div class="w-full text-center p-4">
<span>No files found.</span>
</div>
</div>
</template>
<template #empty>
<div class="w-full text-center p-4">
<span>No files found.</span>
</div>
</template>
</PDataView>
</template>
</PDataView>
</div>
</template>

<script setup lang="ts">
import escapeRegExp from 'lodash/escapeRegExp'
import max from 'lodash/max'
import orderBy from 'lodash/orderBy'
import Path from 'path'
import { computed } from 'vue'

Expand All @@ -81,7 +122,7 @@ interface Column {
interface FileItem {
name?: string
path: string
type?: string
type?: 'folder' | 'file'
link?: string
size?: number
mtime?: string
Expand All @@ -108,14 +149,37 @@ const emit = defineEmits<{

const projectStore = useProjectStore()

const itemPerPage = 50
const ITEMS_PER_PAGE = 100

const columns: Column[] = [
{ text: 'Name', key: 'name', cols: 6 },
{ text: 'Name', key: 'name', cols: 8 },
{ text: 'Modified', key: 'mtime' },
{ text: 'Size', key: 'size' }
]

const breadcrumps = computed(() => {
const location = props.location
const parts = location.split('/').filter(Boolean)
let path = ''
const result = parts.map((part, index) => {
path = Path.join(path, part)
return {
label: part,
path: folderLink(path),
active: index === parts.length - 1
}
})
return [
{
icon: 'ti ti-folder',
label: 'Files',
path: folderLink(''),
active: parts.length === 0
},
...result
]
})

const projectFiles = computed(() => {
let files = projectStore.project.files
if (projectStore.uploads[projectStore.project.path] && diff.value) {
Expand All @@ -133,7 +197,7 @@ const directoryFiles = computed(() => {
)
})

const folders = computed(() => {
const folders = computed<FileItem[]>(() => {
const folders: { [key: string]: FileItem[] } = {}
const prefix = props.location ? escapeRegExp(props.location + '/') : ''
const regex = new RegExp(`^${prefix}([^/]+)/`)
Expand Down Expand Up @@ -190,34 +254,13 @@ function filterByLocation(files) {
}

const items = computed(() => {
const result: FileItem[] = [...folders.value, ...directoryFiles.value]
if (props.search) {
const regex = new RegExp(escapeRegExp(removeAccents(props.search)), 'i')
// TODO: Replace with DataView sorting instead this order_by with lodash
return orderBy(
filterByLocation(projectFiles.value).filter(
(f) => f.path.search(regex) !== -1
),
props.options.sortBy,
props.options.sortDesc < 0 ? 'desc' : 'asc'
)
return filterByLocation(result).filter((f) => f.path.search(regex) !== -1)
}

let result: FileItem[] = []
if (props.location) {
result.push({
name: '..',
type: 'folder',
link: Path.normalize(Path.join(props.location, '..')),
mtime: '',
path: ''
})
}
result = result.concat(folders.value, directoryFiles.value)
return orderBy(
result,
props.options?.sortBy,
props.options.sortDesc < 0 ? 'desc' : 'asc'
)
return result
})

function fileFlags(file: FileItem) {
Expand Down Expand Up @@ -250,7 +293,7 @@ function folderDiffStats(files: FileItem[]) {
}
}

function fileTreeView(file: FileItem) {
function fileTreeView(file): FileItem {
const filename = Path.basename(file.path)
return {
...file,
Expand Down
39 changes: 19 additions & 20 deletions web-app/packages/lib/src/modules/project/views/FileBrowserView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

<template>
<div>
<!-- Searching -->
<app-container
v-if="searchFilter !== '' || Object.keys(project?.files ?? {}).length > 0"
>
<app-section ground>
<div class="flex align-items-center">
<span class="p-input-icon-left flex-grow-1">
<i class="ti ti-search paragraph-p3"></i>
<PInputText
placeholder="Search files"
data-cy="search-files-field"
v-model="searchFilter"
class="w-full"
/>
</span>
<AppMenu :items="filterMenuItems" />
</div>
</app-section>
</app-container>

<app-container
v-if="project && $route.name === 'project-tree' && isProjectWriter"
>
Expand Down Expand Up @@ -65,6 +45,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
</div>
</app-panel-toggleable>
</app-container>
<!-- Searching -->
<app-container
v-if="searchFilter !== '' || Object.keys(project?.files ?? {}).length > 0"
>
<app-section ground>
<div class="flex align-items-center">
<span class="p-input-icon-left flex-grow-1">
<i class="ti ti-search paragraph-p3"></i>
<PInputText
placeholder="Search files"
data-cy="search-files-field"
v-model="searchFilter"
class="w-full"
/>
</span>
<AppMenu :items="filterMenuItems" />
</div>
</app-section>
</app-container>

<app-container v-if="dataTableOpen && projectName">
<app-section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
<app-section
><template #title
>Requests
<span class="opacity-80"
<span class="text-color-medium-green"
>({{ projectStore.accessRequestsCount }})</span
></template
><project-access-requests
Expand Down
Loading