264 lines
6.8 KiB
Vue
264 lines
6.8 KiB
Vue
<script setup>
|
|
const props = defineProps({
|
|
id: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
dropDown: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
typeClass: {
|
|
type: String,
|
|
default: undefined,
|
|
},
|
|
v_path: {
|
|
type: String,
|
|
default: 'input',
|
|
},
|
|
v_regex: {
|
|
type: String,
|
|
default: '.',
|
|
},
|
|
v_value: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
v_defaultoptions: {
|
|
type: Array,
|
|
default: function() { return []; },
|
|
},
|
|
})
|
|
const idClass = `workflowid-${props.id}`;
|
|
const selectClasses = [idClass];
|
|
|
|
const options = ref(props.v_defaultoptions);
|
|
const selectElem = useTemplateRef("selectElem");
|
|
const filterElem = useTemplateRef("filterElem");
|
|
const presentationFileElem = useTemplateRef("presentation-file");
|
|
let firstloadDone = false;
|
|
|
|
if (!props.dropDown) {
|
|
selectClasses.push('pres-hide-dropdown');
|
|
}
|
|
|
|
async function upload(event) {
|
|
const files = event.target.files || event.dataTransfer.files || event.clipboardData.files;
|
|
if (!files.length) {
|
|
console.warn('No files in event', event);
|
|
}
|
|
|
|
for (const file of files) {
|
|
await uploadFile(file, props.v_path);
|
|
}
|
|
dispatchLoadFiles();
|
|
}
|
|
|
|
function loadFiles() {
|
|
let loadedValue = null;
|
|
|
|
if (selectElem.value.options.length > 0) {
|
|
// options already loaded, user may have selected something
|
|
loadedValue = selectElem.value.value;
|
|
if (!loadedValue) {
|
|
loadedValue = props.v_value;
|
|
}
|
|
}
|
|
|
|
if (typeof window !== 'undefined') {
|
|
// TODO cache list
|
|
const url = `/api/files?path=${encodeURIComponent(props.v_path)}®ex=${encodeURIComponent(props.v_regex)}`;
|
|
return fetch(
|
|
url
|
|
).then((resp) => {
|
|
return resp.json();
|
|
}).then(async (obj) => {
|
|
if (!obj.files) {
|
|
console.error('no files: ', props.v_path, props.v_regex, obj);
|
|
return;
|
|
}
|
|
|
|
const files = obj.files.sort((a, b) => {
|
|
return b.mtime - a.mtime;
|
|
});
|
|
obj.files.forEach(i => {
|
|
i.url = getViewURL({filename:i.file, subfolder:"", type:props.v_path});
|
|
});
|
|
|
|
await setFilesList(files);
|
|
|
|
if (loadedValue === null) {
|
|
// nothing selected
|
|
loadedValue = selectElem.value.getAttribute('data-loadedvalue');
|
|
}
|
|
selectElem.value.value = loadedValue;
|
|
// select in image list too.
|
|
const fileItem = findFileItem(loadedValue);
|
|
if (fileItem) {
|
|
selectItem({target: fileItem});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function findFileItem(filename) {
|
|
return presentationFileElem.value.querySelector(`.pres-file-item[data-file="${filename}"]`);
|
|
}
|
|
|
|
function change(event) {
|
|
const filename = event.target.value;
|
|
const fileItem = findFileItem(filename);
|
|
if (fileItem) {
|
|
fileItem.click();
|
|
} else {
|
|
console.warn(`Could not find item ${filename}`);
|
|
}
|
|
|
|
dispatchEvents(
|
|
'.pres-event-change-preview',
|
|
'change-preview',
|
|
{
|
|
detail: {
|
|
url: (event.target.value ?
|
|
getViewURL({
|
|
filename,
|
|
subfolder:"",
|
|
type:props.v_path
|
|
})
|
|
: null),
|
|
filename: event.target.value
|
|
}
|
|
},
|
|
presentationFileElem.value,
|
|
);
|
|
}
|
|
|
|
function filterChange() {
|
|
return setFilesList(unfilteredFilesList);
|
|
}
|
|
|
|
let unfilteredFilesList = [];
|
|
async function setFilesList(files) {
|
|
unfilteredFilesList = files;
|
|
const filterValue = filterElem?.value?.value?.trim();
|
|
if (filterValue.length === 0) {
|
|
options.value = files;
|
|
} else {
|
|
const re = new RegExp(filterValue, 'i');
|
|
|
|
options.value = files.filter(
|
|
i => { return re.exec(i.file) ? true : false; }
|
|
);
|
|
}
|
|
}
|
|
|
|
let currentSelectedItem = null;
|
|
function selectItem(event) {
|
|
let target = event.target;
|
|
|
|
while (target && target.tagName !== 'BODY') {
|
|
const file = target.getAttribute('data-file');
|
|
if (file !== null) {
|
|
selectElem.value.value = file;
|
|
if (currentSelectedItem) {
|
|
currentSelectedItem.classList.remove('pres-selected');
|
|
}
|
|
currentSelectedItem = target;
|
|
currentSelectedItem.classList.add('pres-selected');
|
|
break;
|
|
}
|
|
target = target.parentNode;
|
|
}
|
|
}
|
|
|
|
onMounted(() =>{
|
|
watch(options, async () => {
|
|
if(presentationFileElem.value) {
|
|
await nextTick(); // wait for the list of files to change in the ui
|
|
dispatchEvents(
|
|
'.pres-event-change-files', 'change-files',
|
|
{files: options.value}, presentationFileElem.value
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
async function showTab(show) {
|
|
// let's not load the files until someone clicks on the tab.
|
|
if (show && !firstloadDone) {
|
|
await loadFiles();
|
|
firstloadDone = true;
|
|
}
|
|
}
|
|
|
|
|
|
</script>
|
|
<template>
|
|
<div ref='presentation-file' class='presentation-file pres-event-show-tab' :class="[idClass, props.typeClass]" @show-tab='showTab'>
|
|
<label class='pres-node-title'>{{ title }}</label>
|
|
<input ref="filterElem" placeholder="filter" @input="filterChange" />
|
|
<a class='pres-load-files-icon' @click="loadFiles"></a>
|
|
<select ref="selectElem" :data-workflowid="id" class="pres-select pres-input pres-event-load-files pres-load-files" data-valuename="value" size="4" :class="selectClasses" @load-files="loadFiles" @change="change">
|
|
<option value=""></option>
|
|
<option v-for="(option, index) in options" :key="index">{{ option.file }}</option>
|
|
</select>
|
|
<div v-if="$slots.item && options?.length > 0" class='pres-file-item-list'>
|
|
<span v-for="(option, index) in options" :key="index" :data-file="option.file" class="pres-file-item" @click="selectItem">
|
|
<slot name="item" :option="option" :index="index"></slot>
|
|
</span>
|
|
</div>
|
|
<div class="pres-record-buttons">
|
|
<input type="file" class="pres-upload" @change="upload" />
|
|
<slot name="buttons"></slot>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
.pres-file-item-list {
|
|
width: 100%;
|
|
height: 50vh;
|
|
overflow: scroll;
|
|
border-radius: 5px;
|
|
border: 1px solid #ddd;
|
|
}
|
|
|
|
.pres-file-item {
|
|
display: inline-block;
|
|
min-width: 24%;
|
|
width: 24%;
|
|
margin: 4px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.pres-button {
|
|
font-size: 20px;
|
|
}
|
|
|
|
body .pres-hide-dropdown {
|
|
display: none;
|
|
}
|
|
|
|
.pres-load-files {
|
|
display: block;
|
|
max-height: 25vh;
|
|
}
|
|
|
|
.pres-node-title {
|
|
padding-right: 20px;
|
|
}
|
|
|
|
.pres-load-files-icon {
|
|
cursor: pointer;
|
|
display: inline-block;
|
|
width: 24px;
|
|
height: 24px;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M5.46257 4.43262C7.21556 2.91688 9.5007 2 12 2C17.5228 2 22 6.47715 22 12C22 14.1361 21.3302 16.1158 20.1892 17.7406L17 12H20C20 7.58172 16.4183 4 12 4C9.84982 4 7.89777 4.84827 6.46023 6.22842L5.46257 4.43262ZM18.5374 19.5674C16.7844 21.0831 14.4993 22 12 22C6.47715 22 2 17.5228 2 12C2 9.86386 2.66979 7.88416 3.8108 6.25944L7 12H4C4 16.4183 7.58172 20 12 20C14.1502 20 16.1022 19.1517 17.5398 17.7716L18.5374 19.5674Z'%3E%3C/path%3E%3C/svg%3E");
|
|
}
|
|
|
|
</style>
|