Add file browsing components.

Essentially, these are a file selection dialog.
This commit is contained in:
D. Berge
2023-10-29 15:12:19 +01:00
parent d919fb12db
commit e7c29ba14c
2 changed files with 233 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
<template>
<v-dialog
max-width="600"
v-model="open"
>
<template v-slot:activator="{ on, attrs }">
<v-icon
v-bind="attrs"
v-on="on"
:title="title"
>mdi-folder-network-outline</v-icon>
</template>
<v-card>
<v-card-title>File picker</v-card-title>
<v-divider></v-divider>
<v-card-text>
<dougal-file-browser
v-model="selected"
:mimetypes="mimetypes"
:root="root"
ref="browser"
>
</dougal-file-browser>
</v-card-text>
<v-card-actions>
<v-btn text @click="save" :disabled="!selected">
<v-icon small flat color="primary" class="mr-2">mdi-content-save</v-icon>
Ok
</v-btn>
<v-spacer></v-spacer>
<v-btn text @click="refresh">
<v-icon small flat class="mr-2">mdi-reload</v-icon>
Refresh
</v-btn>
<v-spacer></v-spacer>
<v-btn text @click="close">
<v-icon small flat color="red" class="mr-2">mdi-close</v-icon>
Cancel
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import DougalFileBrowser from './file-browser';
export default {
name: "DougalFileBrowserDialog",
components: { DougalFileBrowser },
props: [ "path", "mimetypes", "root", "title" ],
data () {
return {
open: false,
selected: ""
}
},
methods: {
refresh () {
this.$refs.browser.refresh();
},
close () {
this.open = false;
},
save () {
this.$emit('input', this.selected);
this.close();
}
},
mounted () {
this.selected = this.path;
}
}
</script>

View File

@@ -0,0 +1,150 @@
<template>
<v-layout fill-height style="overflow-y:auto;max-height:400px;">
<v-progress-circular v-if="loading && !items.length"></v-progress-circular>
<v-treeview v-else
activatable
:active.sync="active"
:items="items"
item-key="path"
item-name="basename"
:load-children="readdir"
@update:active="activeChanged"
style="min-width:100%"
>
<template v-slot:label="{item}">
<div style="cursor:pointer;">
{{ item.basename }}
</div>
</template>
</v-treeview>
</v-layout>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
function find(haystack, needle) {
for (const item of haystack) {
if (item.path == needle) {
return item;
} else if (item.children) {
const found = find(item.children, needle);
if (found) {
return found;
}
}
}
}
export default {
name: "DougalFileBrowser",
props: [ "value", "mimetypes", "root" ],
data () {
return {
loading: false,
items: [],
active: [],
selected: null,
path: "",
};
},
computed: {
dirsAreSelectable () {
return !this.mimetypes ||
this.mimetypes == "inode/directory" ||
(Array.isArray(this.mimetypes) && this.mimetypes.includes("inode/directory"));
}
},
watch: {
},
methods: {
activeChanged (active) {
const candidate = find(this.items, active[0]);
if (!this.dirsAreSelectable && this.isDirectory(candidate)) {
this.selected = null;
} else {
this.selected = candidate;
}
this.$emit("input", this.selected?.path);
},
isDirectory (item) {
return item && item["Content-Type"] == "inode/directory";
},
filterMimetypes (item) {
if (!this.mimetypes) {
return true;
} else if (Array.isArray(this.mimetypes)) {
return item["Content-Type"] == "inode/directory" ||
this.mimetypes.includes(item["Content-Type"].split(";")[0]) ||
this.filterGlob(item);
} else {
return item["Content-Type"] == "inode/directory" ||
this.mimetypes == item["Content-Type"].split(";")[0];
}
return false;
},
filterGlob (item) {
const globs = (Array.isArray(this.mimetypes)
? this.mimetypes
: [ this.mimetypes ])
.filter(i => /^\*\..+$/.test(i));
for (const glob of globs) {
const ext = (glob.match(/^\*\.(.+)$/)||[])[1];
if (item.path.toLowerCase().endsWith(ext.toLowerCase())) {
return true;
}
}
return false;
},
async readdir (item) {
this.loading = true;
const url = `/files/${item? item.path : (this.root || this.path || "")}`;
const list = await this.api([url]);
this.loading = false;
const items = list.map(item => {
if (item["Content-Type"] == "inode/directory") {
item.children = [];
}
item.id = item.path;
item.name = item.basename;
return item;
}).filter(this.filterMimetypes);
if (item) {
item.children = items;
} else {
this.items = items;
}
},
async refresh () {
this.items = []
this.$nextTick(this.readdir);
},
...mapActions(["api"])
},
mounted () {
if (this.value) {
this.path = this.value;
}
this.readdir();
}
}
</script>