Added `FileSelector` UI component.

This commit is contained in:
Fabio Manganiello 2023-12-12 21:13:55 +01:00
parent bac06e9e7b
commit d4f6d174c8
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
2 changed files with 154 additions and 5 deletions

View File

@ -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()
},
}

View File

@ -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>