From d4f6d174c81d30f6bae46aabf92d8a0b3a624d0d Mon Sep 17 00:00:00 2001 From: Fabio Manganiello <fabio@manganiello.tech> Date: Tue, 12 Dec 2023 21:13:55 +0100 Subject: [PATCH] Added `FileSelector` UI component. --- .../webapp/src/components/File/Browser.vue | 43 ++++++- .../src/components/elements/FileSelector.vue | 116 ++++++++++++++++++ 2 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 platypush/backend/http/webapp/src/components/elements/FileSelector.vue diff --git a/platypush/backend/http/webapp/src/components/File/Browser.vue b/platypush/backend/http/webapp/src/components/File/Browser.vue index 538065e2..dc41e1db 100644 --- a/platypush/backend/http/webapp/src/components/File/Browser.vue +++ b/platypush/backend/http/webapp/src/components/File/Browser.vue @@ -27,7 +27,7 @@ </div> </div> - <div class="row item" v-for="(file, i) in filteredFiles" :key="i" @click="path = file.path"> + <div class="row item" v-for="(file, i) in filteredFiles" :key="i" @click="onItemSelect(file)"> <div class="col-10"> <i class="icon fa" :class="{'fa-file': file.type !== 'directory', 'fa-folder': file.type === 'directory'}" /> <span class="name"> @@ -35,11 +35,11 @@ </span> </div> - <div class="col-2 actions"> + <div class="col-2 actions" v-if="fileActions.length"> <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())" /> + v-if="hasPlay && file.type !== 'directory'" /> </Dropdown> </div> </div> @@ -58,7 +58,7 @@ export default { name: "Browser", components: {DropdownItem, Dropdown, Loading}, mixins: [Utils, MediaUtils], - emits: ['back', 'path-change', 'play'], + emits: ['back', 'path-change', 'play', 'input'], props: { hasBack: { @@ -96,6 +96,23 @@ export default { return this.files.filter((file) => (file?.name || '').toLowerCase().indexOf(this.filter.toLowerCase()) >= 0) }, + hasPlay() { + return this.isMedia && this.files.some((file) => this.mediaExtensions.has(file.name.split('.').pop()?.toLowerCase())) + }, + + fileActions() { + if (!this.hasPlay) + return [] + + return [ + { + iconClass: 'fa fa-play', + text: 'Play', + onClick: (file) => this.$emit('play', {type: 'file', url: `file://${file.path}`}), + }, + ] + }, + pathTokens() { if (!this.path?.length) return ['/'] @@ -128,10 +145,26 @@ export default { else this.path = [...this.pathTokens].slice(0, -1).join('/').slice(1) }, + + onItemSelect(file) { + if (file.type === 'directory') + this.path = file.path + else + this.$emit('input', file.path) + }, + }, + + watch: { + initialPath() { + this.path = this.initialPath + }, + + path() { + this.refresh() + }, }, mounted() { - this.$watch(() => this.path, () => this.refresh()) this.refresh() }, } diff --git a/platypush/backend/http/webapp/src/components/elements/FileSelector.vue b/platypush/backend/http/webapp/src/components/elements/FileSelector.vue new file mode 100644 index 00000000..175093ad --- /dev/null +++ b/platypush/backend/http/webapp/src/components/elements/FileSelector.vue @@ -0,0 +1,116 @@ +<template> + <div class="file-selector-container"> + <div class="input"> + <input type="text" + :value="value" + :readonly="strict" + @input="$emit('input', $event.target.value)" /> + + <button type="button" + title="Select a file" + @click="$refs.fileSelectorModal.show()"> + <i class="fa fa-folder-open" /> + </button> + </div> + + <Modal title="Select a file" ref="fileSelectorModal"> + <Browser :initialPath="path" + @input="onValueChange($event)" + @path-change="path = $event" /> + </Modal> + </div> +</template> + +<script> +import Modal from "@/components/Modal"; +import Browser from "@/components/File/Browser"; + +export default { + emits: ['input'], + components: { + Browser, + Modal, + }, + + props: { + value: { + type: String, + }, + + strict: { + type: Boolean, + default: false, + }, + }, + + data() { + return { + path: '/', + } + }, + + methods: { + onValueChange(value) { + this.$emit('input', value) + }, + + onFileSelect(value) { + if (value != null && (value.startsWith('/') || value.startsWith('file://'))) + this.path = value.split('/').slice(0, -1).join('/') + else + this.path = '/' + + this.$refs.fileSelectorModal.hide() + }, + }, + + watch: { + value(value) { + this.onFileSelect(value) + }, + }, + + mounted() { + this.onFileSelect(this.value) + }, +} +</script> + +<style lang="scss" scoped> +.file-selector-container { + display: flex; + flex-direction: column; + + .input { + display: flex; + flex-direction: row; + align-items: stretch; + + input { + flex-grow: 1; + border-radius: 1em 0 0 1em; + border-right: none; + } + + button { + border-radius: 0 1em 1em 0; + border-left: none; + } + } + + :deep(.modal) { + .body { + width: 80vw; + height: 80vh; + max-width: 800px; + padding: 0; + + .items { + .item { + padding: 1em 0.5em; + } + } + } + } +} +</style>