From 6dfe2324c1a8c73fdb200fb6a31c20e2082c1d6f Mon Sep 17 00:00:00 2001 From: Fabio Manganiello <fabio@manganiello.tech> Date: Sun, 12 Nov 2023 15:53:46 +0100 Subject: [PATCH] [UI] Added navigation crumbs to the file browser. --- .../webapp/src/components/File/Browser.vue | 118 +++++++++++++++--- 1 file changed, 100 insertions(+), 18 deletions(-) diff --git a/platypush/backend/http/webapp/src/components/File/Browser.vue b/platypush/backend/http/webapp/src/components/File/Browser.vue index 4ff7a2018..437eaef38 100644 --- a/platypush/backend/http/webapp/src/components/File/Browser.vue +++ b/platypush/backend/http/webapp/src/components/File/Browser.vue @@ -2,27 +2,46 @@ <div class="browser-container"> <Loading v-if="loading" /> - <div class="row item" @click="path = (path || '') + '/..'" v-if="path?.length && path !== '/'"> - <div class="col-10 left side"> - <i class="icon fa fa-folder" /> - <span class="name">..</span> - </div> + <div class="nav" ref="nav"> + <span class="path" + v-for="(token, i) in pathTokens" + :key="i" + @click="path = pathTokens.slice(0, i + 1).join('/').slice(1)"> + <span class="token"> + {{ token }} + </span> + + <span class="separator" v-if="(i > 0 || pathTokens.length > 1) && i < pathTokens.length - 1"> + <i class="fa fa-chevron-right" /> + </span> + </span> </div> - <div class="row item" v-for="(file, i) in filteredFiles" :key="i" @click="path = file.path"> - <div class="col-10"> - <i class="icon fa" :class="{'fa-file': file.type !== 'directory', 'fa-folder': file.type === 'directory'}" /> - <span class="name"> - {{ file.name }} - </span> + <div class="items" ref="items"> + <div class="row item" + @click="onBack" + v-if="(path?.length && path !== '/') || hasBack"> + <div class="col-10 left side"> + <i class="icon fa fa-folder" /> + <span class="name">..</span> + </div> </div> - <div class="col-2 actions"> - <Dropdown> - <DropdownItem icon-class="fa fa-play" text="Play" - @click="$emit('play', {type: 'file', url: `file://${file.path}`})" - v-if="isMedia && mediaExtensions.has(file.name.split('.').pop())" /> - </Dropdown> + <div class="row item" v-for="(file, i) in filteredFiles" :key="i" @click="path = file.path"> + <div class="col-10"> + <i class="icon fa" :class="{'fa-file': file.type !== 'directory', 'fa-folder': file.type === 'directory'}" /> + <span class="name"> + {{ file.name }} + </span> + </div> + + <div class="col-2 actions"> + <Dropdown> + <DropdownItem icon-class="fa fa-play" text="Play" + @click="$emit('play', {type: 'file', url: `file://${file.path}`})" + v-if="isMedia && mediaExtensions.has(file.name.split('.').pop()?.toLowerCase())" /> + </Dropdown> + </div> </div> </div> </div> @@ -39,9 +58,14 @@ export default { name: "Browser", components: {DropdownItem, Dropdown, Loading}, mixins: [Utils, MediaUtils], - emits: ['path-change'], + emits: ['back', 'path-change', 'play'], props: { + hasBack: { + type: Boolean, + default: false, + }, + initialPath: { type: String, }, @@ -71,11 +95,24 @@ export default { return this.files.filter((file) => (file?.name || '').toLowerCase().indexOf(this.filter.toLowerCase()) >= 0) }, + + pathTokens() { + if (!this.path?.length) + return ['/'] + + return ['/', ...this.path.split(/(?<!\\)\//).slice(1)] + }, }, methods: { async refresh() { this.loading = true + this.$nextTick(() => { + // Scroll to the end of the path navigator + this.$refs.nav.scrollLeft = 99999 + // Scroll to the top of the items list + this.$refs.items.scrollTop = 0 + }) try { this.files = await this.request('file.list', {path: this.path}) @@ -84,6 +121,13 @@ export default { this.loading = false } }, + + onBack() { + if (!this.path?.length || this.path === '/') + this.$emit('back') + else + this.path = [...this.pathTokens].slice(0, -1).join('/').slice(1) + }, }, mounted() { @@ -96,12 +140,50 @@ export default { <style lang="scss" scoped> @import "src/style/items"; +$nav-height: 2.5em; + .browser-container { + height: 100%; + display: flex; + flex-direction: column; + .item { .actions { display: inline-flex; justify-content: right; } } + + .nav { + width: 100%; + height: $nav-height; + padding: 0.5em 1em; + background: $tab-bg; + box-shadow: $border-shadow-bottom; + white-space: nowrap; + overflow: hidden; + + .path { + cursor: pointer; + + .token { + &:hover { + color: $default-hover-fg; + text-decoration: underline; + } + } + + .separator { + font-size: 1em; + width: 1.2em; + padding: 0 1em; + } + } + } + + .items { + height: calc(100% - #{$nav-height}); + overflow: auto; + } } </style>