Skip to content

Commit 5d60830

Browse files
committed
fix(files): make sure the FilesList is always mounted
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
1 parent 927beef commit 5d60830

File tree

4 files changed

+123
-70
lines changed

4 files changed

+123
-70
lines changed

apps/files/src/components/FilesListHeader.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import type { Folder, Header, View } from '@nextcloud/files'
1313
import type { PropType } from 'vue'
1414
15+
import logger from '../logger.ts'
16+
1517
/**
1618
* This component is used to render custom
1719
* elements provided by an API. Vue doesn't allow
@@ -51,8 +53,12 @@ export default {
5153
},
5254
},
5355
mounted() {
54-
console.debug('Mounted', this.header.id)
56+
logger.debug(`Mounted ${this.header.id} FilesListHeader`, { header: this.header })
5557
this.header.render(this.$refs.mount as HTMLElement, this.currentFolder, this.currentView)
5658
},
59+
60+
destroyed() {
61+
logger.debug(`Destroyed ${this.header.id} FilesListHeader`, { header: this.header })
62+
},
5763
}
5864
</script>

apps/files/src/components/FilesListVirtual.vue

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
:nodes="nodes" />
4949
</template>
5050

51+
<!-- Body replacement if no files are available -->
52+
<template #empty>
53+
<slot name="empty" />
54+
</template>
55+
5156
<!-- Tfoot-->
5257
<template #footer>
5358
<FilesListTableFooter :current-view="currentView"
@@ -474,6 +479,8 @@ export default defineComponent({
474479
--icon-preview-size: 32px;
475480
476481
--fixed-block-start-position: var(--default-clickable-area);
482+
display: flex;
483+
flex-direction: column;
477484
overflow: auto;
478485
height: 100%;
479486
will-change: scroll-position;
@@ -570,6 +577,16 @@ export default defineComponent({
570577
top: var(--fixed-block-start-position);
571578
}
572579
580+
// Empty content
581+
.files-list__empty {
582+
display: flex;
583+
flex-direction: column;
584+
align-items: center;
585+
justify-content: center;
586+
width: 100%;
587+
height: 100%;
588+
}
589+
573590
tr {
574591
position: relative;
575592
display: flex;

apps/files/src/components/VirtualList.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@
2020
<slot name="header-overlay" />
2121
</div>
2222

23-
<table class="files-list__table" :class="{ 'files-list__table--with-thead-overlay': !!$scopedSlots['header-overlay'] }">
23+
<div v-if="dataSources.length === 0"
24+
class="files-list__empty">
25+
<slot name="empty" />
26+
</div>
27+
28+
<table v-else
29+
class="files-list__table"
30+
:class="{ 'files-list__table--with-thead-overlay': !!$scopedSlots['header-overlay'] }">
2431
<!-- Accessibility table caption for screen readers -->
2532
<caption v-if="caption" class="hidden-visually">
2633
{{ caption }}

apps/files/src/views/FilesList.vue

Lines changed: 91 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -73,87 +73,92 @@
7373
<!-- Drag and drop notice -->
7474
<DragAndDropNotice v-if="!loading && canUpload && currentFolder" :current-folder="currentFolder" />
7575

76-
<!-- Initial loading -->
77-
<NcLoadingIcon v-if="loading && !isRefreshing"
76+
<!--
77+
Initial current view loading0. This should never happen,
78+
views are supposed to be registered far earlier in the lifecycle.
79+
In case the URL is bad or a view is missing, we show a loading icon.
80+
-->
81+
<NcLoadingIcon v-if="!currentView"
7882
class="files-list__loading-icon"
7983
:size="38"
8084
:name="t('files', 'Loading current folder')" />
8185

82-
<!-- Empty content placeholder -->
83-
<template v-else-if="!loading && isEmptyDir && currentFolder && currentView">
84-
<div class="files-list__before">
85-
<!-- Headers -->
86-
<FilesListHeader v-for="header in headers"
87-
:key="header.id"
88-
:current-folder="currentFolder"
89-
:current-view="currentView"
90-
:header="header" />
91-
</div>
92-
<!-- Empty due to error -->
93-
<NcEmptyContent v-if="error" :name="error" data-cy-files-content-error>
94-
<template #action>
95-
<NcButton type="secondary" @click="fetchContent">
96-
<template #icon>
97-
<IconReload :size="20" />
98-
</template>
99-
{{ t('files', 'Retry') }}
100-
</NcButton>
101-
</template>
102-
<template #icon>
103-
<IconAlertCircleOutline />
104-
</template>
105-
</NcEmptyContent>
106-
<!-- Custom empty view -->
107-
<div v-else-if="currentView?.emptyView" class="files-list__empty-view-wrapper">
108-
<div ref="customEmptyView" />
109-
</div>
110-
<!-- Default empty directory view -->
111-
<NcEmptyContent v-else
112-
:name="currentView?.emptyTitle || t('files', 'No files in here')"
113-
:description="currentView?.emptyCaption || t('files', 'Upload some content or sync with your devices!')"
114-
data-cy-files-content-empty>
115-
<template v-if="directory !== '/'" #action>
116-
<!-- Uploader -->
117-
<UploadPicker v-if="canUpload && !isQuotaExceeded"
118-
allow-folders
119-
class="files-list__header-upload-button"
120-
:content="getContent"
121-
:destination="currentFolder"
122-
:forbidden-characters="forbiddenCharacters"
123-
multiple
124-
@failed="onUploadFail"
125-
@uploaded="onUpload" />
126-
<NcButton v-else :to="toPreviousDir" type="primary">
127-
{{ t('files', 'Go back') }}
128-
</NcButton>
129-
</template>
130-
<template #icon>
131-
<NcIconSvgWrapper :svg="currentView.icon" />
132-
</template>
133-
</NcEmptyContent>
134-
</template>
135-
136-
<!-- File list -->
86+
<!-- File list - always mounted -->
13787
<FilesListVirtual v-else
13888
ref="filesListVirtual"
13989
:current-folder="currentFolder"
14090
:current-view="currentView"
14191
:nodes="dirContentsSorted"
142-
:summary="summary" />
92+
:summary="summary">
93+
<template #empty>
94+
<!-- Initial loading -->
95+
<NcLoadingIcon v-if="loading && !isRefreshing"
96+
class="files-list__loading-icon"
97+
:size="38"
98+
:name="t('files', 'Loading current folder')" />
99+
100+
<!-- Empty due to error -->
101+
<NcEmptyContent v-else-if="error" :name="error" data-cy-files-content-error>
102+
<template #action>
103+
<NcButton type="secondary" @click="fetchContent">
104+
<template #icon>
105+
<IconReload :size="20" />
106+
</template>
107+
{{ t('files', 'Retry') }}
108+
</NcButton>
109+
</template>
110+
<template #icon>
111+
<IconAlertCircleOutline />
112+
</template>
113+
</NcEmptyContent>
114+
115+
<!-- Custom empty view -->
116+
<div v-else-if="currentView?.emptyView" class="files-list__empty-view-wrapper">
117+
<div ref="customEmptyView" />
118+
</div>
119+
120+
<!-- Default empty directory view -->
121+
<NcEmptyContent v-else
122+
:name="currentView?.emptyTitle || t('files', 'No files in here')"
123+
:description="currentView?.emptyCaption || t('files', 'Upload some content or sync with your devices!')"
124+
data-cy-files-content-empty>
125+
<template v-if="directory !== '/'" #action>
126+
<!-- Uploader -->
127+
<UploadPicker v-if="canUpload && !isQuotaExceeded"
128+
allow-folders
129+
class="files-list__header-upload-button"
130+
:content="getContent"
131+
:destination="currentFolder"
132+
:forbidden-characters="forbiddenCharacters"
133+
multiple
134+
@failed="onUploadFail"
135+
@uploaded="onUpload" />
136+
<NcButton v-else :to="toPreviousDir" type="primary">
137+
{{ t('files', 'Go back') }}
138+
</NcButton>
139+
</template>
140+
<template #icon>
141+
<NcIconSvgWrapper :svg="currentView?.icon" />
142+
</template>
143+
</NcEmptyContent>
144+
</template>
145+
</FilesListVirtual>
143146
</NcAppContent>
144147
</template>
145148

146149
<script lang="ts">
147-
import type { ContentsWithRoot, FileListAction, Folder, INode } from '@nextcloud/files'
150+
import type { ContentsWithRoot, FileListAction, INode } from '@nextcloud/files'
148151
import type { Upload } from '@nextcloud/upload'
149152
import type { CancelablePromise } from 'cancelable-promise'
150153
import type { ComponentPublicInstance } from 'vue'
151154
import type { Route } from 'vue-router'
152155
import type { UserConfig } from '../types.ts'
153156
157+
import { getCurrentUser } from '@nextcloud/auth'
154158
import { getCapabilities } from '@nextcloud/capabilities'
155159
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
156-
import { Node, Permission, sortNodes, getFileListActions } from '@nextcloud/files'
160+
import { Folder, Node, Permission, sortNodes, getFileListActions } from '@nextcloud/files'
161+
import { getRemoteURL, getRootPath } from '@nextcloud/files/dav'
157162
import { translate as t } from '@nextcloud/l10n'
158163
import { join, dirname, normalize } from 'path'
159164
import { showError, showSuccess, showWarning } from '@nextcloud/dialogs'
@@ -181,7 +186,6 @@ import ViewGridIcon from 'vue-material-design-icons/ViewGrid.vue'
181186
import { action as sidebarAction } from '../actions/sidebarAction.ts'
182187
import { getSummaryFor } from '../utils/fileUtils.ts'
183188
import { humanizeWebDAVError } from '../utils/davUtils.ts'
184-
import { useFileListHeaders } from '../composables/useFileListHeaders.ts'
185189
import { useFileListWidth } from '../composables/useFileListWidth.ts'
186190
import { useFilesStore } from '../store/files.ts'
187191
import { useFiltersStore } from '../store/filters.ts'
@@ -194,7 +198,6 @@ import { useUserConfigStore } from '../store/userconfig.ts'
194198
import { useViewConfigStore } from '../store/viewConfig.ts'
195199
import BreadCrumbs from '../components/BreadCrumbs.vue'
196200
import DragAndDropNotice from '../components/DragAndDropNotice.vue'
197-
import FilesListHeader from '../components/FilesListHeader.vue'
198201
import FilesListVirtual from '../components/FilesListVirtual.vue'
199202
import filesSortingMixin from '../mixins/filesSorting.ts'
200203
import logger from '../logger.ts'
@@ -207,7 +210,6 @@ export default defineComponent({
207210
components: {
208211
BreadCrumbs,
209212
DragAndDropNotice,
210-
FilesListHeader,
211213
FilesListVirtual,
212214
LinkIcon,
213215
ListViewIcon,
@@ -256,7 +258,6 @@ export default defineComponent({
256258
directory,
257259
fileId,
258260
fileListWidth,
259-
headers: useFileListHeaders(),
260261
t,
261262
262263
filesStore,
@@ -321,12 +322,23 @@ export default defineComponent({
321322
/**
322323
* The current folder.
323324
*/
324-
currentFolder(): Folder | undefined {
325+
currentFolder(): Folder {
326+
// TMP folder to use until we have the first valid folder
327+
// fetched and cached. This allow us to mount the FilesListVirtual
328+
// at all time and avoid unmount/mount and undesired rendering issues.
329+
const dummyFolder = new Folder({
330+
id: 0,
331+
source: getRemoteURL() + getRootPath(),
332+
root: getRootPath(),
333+
owner: getCurrentUser()?.uid || null,
334+
permissions: Permission.NONE,
335+
})
336+
325337
if (!this.currentView?.id) {
326-
return
338+
return dummyFolder
327339
}
328340
329-
return this.filesStore.getDirectoryByPath(this.currentView.id, this.directory)
341+
return this.filesStore.getDirectoryByPath(this.currentView.id, this.directory) || dummyFolder
330342
},
331343
332344
dirContents(): Node[] {
@@ -338,7 +350,7 @@ export default defineComponent({
338350
/**
339351
* The current directory contents.
340352
*/
341-
dirContentsSorted() {
353+
dirContentsSorted(): INode[] {
342354
if (!this.currentView) {
343355
return []
344356
}
@@ -574,9 +586,20 @@ export default defineComponent({
574586
575587
if (!currentView) {
576588
logger.debug('The current view doesn\'t exists or is not ready.', { currentView })
589+
590+
// If we still haven't a valid view, let's wait for the page to load
591+
// then try again. Else redirect to the default view
592+
window.addEventListener('DOMContentLoaded', () => {
593+
if (!this.currentView) {
594+
logger.warn('No current view after DOMContentLoaded, redirecting to the default view')
595+
window.OCP.Files.Router.goToRoute(null, { view: 'files' })
596+
}
597+
}, { once: true })
577598
return
578599
}
579600
601+
logger.debug('Fetching contents for directory', { dir, currentView })
602+
580603
// If we have a cancellable promise ongoing, cancel it
581604
if (this.promise && 'cancel' in this.promise) {
582605
this.promise.cancel()

0 commit comments

Comments
 (0)