427 lines
13 KiB
JavaScript
427 lines
13 KiB
JavaScript
import {
|
|
VCounter,
|
|
VField,
|
|
makeVFieldProps
|
|
} from "./chunk-ITO3JWXJ.js";
|
|
import {
|
|
VChip
|
|
} from "./chunk-PLNIG6WP.js";
|
|
import {
|
|
VInput,
|
|
makeVInputProps,
|
|
useFocus
|
|
} from "./chunk-IK67YVHF.js";
|
|
import {
|
|
forwardRefs
|
|
} from "./chunk-GUNW7S2L.js";
|
|
import {
|
|
useLocale
|
|
} from "./chunk-ENTLCOCZ.js";
|
|
import {
|
|
useProxiedModel
|
|
} from "./chunk-KA77II4V.js";
|
|
import {
|
|
callEvent,
|
|
filterInputAttrs,
|
|
genericComponent,
|
|
humanReadableFileSize,
|
|
propsFactory,
|
|
useRender,
|
|
wrapInArray
|
|
} from "./chunk-LZ5T2ERG.js";
|
|
import {
|
|
Fragment,
|
|
computed,
|
|
createBaseVNode,
|
|
createVNode,
|
|
mergeProps,
|
|
nextTick,
|
|
normalizeClass,
|
|
ref,
|
|
shallowRef,
|
|
toRef,
|
|
watch
|
|
} from "./chunk-2MKFL3BX.js";
|
|
|
|
// node_modules/vuetify/lib/components/VFileInput/VFileInput.js
|
|
import "/home/david/gitea_repo/personal_development/ServerManagerWebApp/frontend/node_modules/vuetify/lib/components/VFileInput/VFileInput.css";
|
|
|
|
// node_modules/vuetify/lib/composables/fileDrop.js
|
|
function useFileDrop() {
|
|
function hasFilesOrFolders(e) {
|
|
const entries = [...e.dataTransfer?.items ?? []].filter((x) => x.kind === "file").map((x) => x.webkitGetAsEntry()).filter(Boolean);
|
|
return entries.length > 0 || [...e.dataTransfer?.files ?? []].length > 0;
|
|
}
|
|
async function handleDrop(e) {
|
|
const result = [];
|
|
const entries = [...e.dataTransfer?.items ?? []].filter((x) => x.kind === "file").map((x) => x.webkitGetAsEntry()).filter(Boolean);
|
|
if (entries.length) {
|
|
for (const entry of entries) {
|
|
const files = await traverseFileTree(entry, appendIfDirectory(".", entry));
|
|
result.push(...files.map((x) => x.file));
|
|
}
|
|
} else {
|
|
result.push(...[...e.dataTransfer?.files ?? []]);
|
|
}
|
|
return result;
|
|
}
|
|
return {
|
|
handleDrop,
|
|
hasFilesOrFolders
|
|
};
|
|
}
|
|
function traverseFileTree(item) {
|
|
let path = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "";
|
|
return new Promise((resolve, reject) => {
|
|
if (item.isFile) {
|
|
const fileEntry = item;
|
|
fileEntry.file((file) => resolve([{
|
|
file,
|
|
path
|
|
}]), reject);
|
|
} else if (item.isDirectory) {
|
|
const directoryReader = item.createReader();
|
|
directoryReader.readEntries(async (entries) => {
|
|
const files = [];
|
|
for (const entry of entries) {
|
|
files.push(...await traverseFileTree(entry, appendIfDirectory(path, entry)));
|
|
}
|
|
resolve(files);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function appendIfDirectory(path, item) {
|
|
return item.isDirectory ? `${path}/${item.name}` : path;
|
|
}
|
|
|
|
// node_modules/vuetify/lib/composables/fileFilter.js
|
|
var makeFileFilterProps = propsFactory({
|
|
filterByType: String
|
|
}, "file-accept");
|
|
function useFileFilter(props) {
|
|
const fileFilter = computed(() => props.filterByType ? createFilter(props.filterByType) : null);
|
|
function filterAccepted(files) {
|
|
if (fileFilter.value) {
|
|
const accepted = files.filter(fileFilter.value);
|
|
return {
|
|
accepted,
|
|
rejected: files.filter((f) => !accepted.includes(f))
|
|
};
|
|
}
|
|
return {
|
|
accepted: files,
|
|
rejected: []
|
|
};
|
|
}
|
|
return {
|
|
filterAccepted
|
|
};
|
|
}
|
|
function createFilter(v) {
|
|
const types = v.split(",").map((x) => x.trim().toLowerCase());
|
|
const extensionsToMatch = types.filter((x) => x.startsWith("."));
|
|
const wildcards = types.filter((x) => x.endsWith("/*"));
|
|
const typesToMatch = types.filter((x) => !extensionsToMatch.includes(x) && !wildcards.includes(x));
|
|
return (file) => {
|
|
const extension = file.name.split(".").at(-1)?.toLowerCase() ?? "";
|
|
const typeGroup = file.type.split("/").at(0)?.toLowerCase() ?? "";
|
|
return typesToMatch.includes(file.type) || extensionsToMatch.includes(`.${extension}`) || wildcards.includes(`${typeGroup}/*`);
|
|
};
|
|
}
|
|
|
|
// node_modules/vuetify/lib/components/VFileInput/VFileInput.js
|
|
var makeVFileInputProps = propsFactory({
|
|
chips: Boolean,
|
|
counter: Boolean,
|
|
counterSizeString: {
|
|
type: String,
|
|
default: "$vuetify.fileInput.counterSize"
|
|
},
|
|
counterString: {
|
|
type: String,
|
|
default: "$vuetify.fileInput.counter"
|
|
},
|
|
hideInput: Boolean,
|
|
multiple: Boolean,
|
|
showSize: {
|
|
type: [Boolean, Number, String],
|
|
default: false,
|
|
validator: (v) => {
|
|
return typeof v === "boolean" || [1e3, 1024].includes(Number(v));
|
|
}
|
|
},
|
|
truncateLength: {
|
|
type: [Number, String],
|
|
default: 22
|
|
},
|
|
...makeVInputProps({
|
|
prependIcon: "$file"
|
|
}),
|
|
modelValue: {
|
|
type: [Array, Object],
|
|
default: (props) => props.multiple ? [] : null,
|
|
validator: (val) => {
|
|
return wrapInArray(val).every((v) => v != null && typeof v === "object");
|
|
}
|
|
},
|
|
...makeFileFilterProps(),
|
|
...makeVFieldProps({
|
|
clearable: true
|
|
})
|
|
}, "VFileInput");
|
|
var VFileInput = genericComponent()({
|
|
name: "VFileInput",
|
|
inheritAttrs: false,
|
|
props: makeVFileInputProps(),
|
|
emits: {
|
|
"click:control": (e) => true,
|
|
"mousedown:control": (e) => true,
|
|
"update:focused": (focused) => true,
|
|
"update:modelValue": (files) => true,
|
|
rejected: (files) => true
|
|
},
|
|
setup(props, _ref) {
|
|
let {
|
|
attrs,
|
|
emit,
|
|
slots
|
|
} = _ref;
|
|
const {
|
|
t
|
|
} = useLocale();
|
|
const {
|
|
filterAccepted
|
|
} = useFileFilter(props);
|
|
const model = useProxiedModel(props, "modelValue", props.modelValue, (val) => wrapInArray(val), (val) => !props.multiple && Array.isArray(val) ? val[0] : val);
|
|
const {
|
|
isFocused,
|
|
focus,
|
|
blur
|
|
} = useFocus(props);
|
|
const base = computed(() => typeof props.showSize !== "boolean" ? props.showSize : void 0);
|
|
const totalBytes = computed(() => (model.value ?? []).reduce((bytes, _ref2) => {
|
|
let {
|
|
size = 0
|
|
} = _ref2;
|
|
return bytes + size;
|
|
}, 0));
|
|
const totalBytesReadable = computed(() => humanReadableFileSize(totalBytes.value, base.value));
|
|
const fileNames = computed(() => (model.value ?? []).map((file) => {
|
|
const {
|
|
name = "",
|
|
size = 0
|
|
} = file;
|
|
const truncatedText = truncateText(name);
|
|
return !props.showSize ? truncatedText : `${truncatedText} (${humanReadableFileSize(size, base.value)})`;
|
|
}));
|
|
const counterValue = computed(() => {
|
|
const fileCount = model.value?.length ?? 0;
|
|
if (props.showSize) return t(props.counterSizeString, fileCount, totalBytesReadable.value);
|
|
else return t(props.counterString, fileCount);
|
|
});
|
|
const vInputRef = ref();
|
|
const vFieldRef = ref();
|
|
const inputRef = ref();
|
|
const isActive = toRef(() => isFocused.value || props.active);
|
|
const isPlainOrUnderlined = computed(() => ["plain", "underlined"].includes(props.variant));
|
|
const isDragging = shallowRef(false);
|
|
const {
|
|
handleDrop,
|
|
hasFilesOrFolders
|
|
} = useFileDrop();
|
|
function onFocus() {
|
|
if (inputRef.value !== document.activeElement) {
|
|
inputRef.value?.focus();
|
|
}
|
|
if (!isFocused.value) focus();
|
|
}
|
|
function onClickPrepend(e) {
|
|
inputRef.value?.click();
|
|
}
|
|
function onControlMousedown(e) {
|
|
emit("mousedown:control", e);
|
|
}
|
|
function onControlClick(e) {
|
|
inputRef.value?.click();
|
|
emit("click:control", e);
|
|
}
|
|
function onClear(e) {
|
|
e.stopPropagation();
|
|
onFocus();
|
|
nextTick(() => {
|
|
model.value = [];
|
|
callEvent(props["onClick:clear"], e);
|
|
});
|
|
}
|
|
function truncateText(str) {
|
|
if (str.length < Number(props.truncateLength)) return str;
|
|
const charsKeepOneSide = Math.floor((Number(props.truncateLength) - 1) / 2);
|
|
return `${str.slice(0, charsKeepOneSide)}…${str.slice(str.length - charsKeepOneSide)}`;
|
|
}
|
|
function onDragover(e) {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
isDragging.value = true;
|
|
}
|
|
function onDragleave(e) {
|
|
e.preventDefault();
|
|
isDragging.value = false;
|
|
}
|
|
async function onDrop(e) {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
isDragging.value = false;
|
|
if (!inputRef.value || !hasFilesOrFolders(e)) return;
|
|
const allDroppedFiles = await handleDrop(e);
|
|
selectAccepted(allDroppedFiles);
|
|
}
|
|
function onFileSelection(e) {
|
|
if (!e.target || e.repack) return;
|
|
if (!props.filterByType) {
|
|
const target = e.target;
|
|
model.value = [...target.files ?? []];
|
|
} else {
|
|
selectAccepted([...e.target.files]);
|
|
}
|
|
}
|
|
function selectAccepted(files) {
|
|
const dataTransfer = new DataTransfer();
|
|
const {
|
|
accepted,
|
|
rejected
|
|
} = filterAccepted(files);
|
|
if (rejected.length) {
|
|
emit("rejected", rejected);
|
|
}
|
|
for (const file of accepted) {
|
|
dataTransfer.items.add(file);
|
|
}
|
|
inputRef.value.files = dataTransfer.files;
|
|
model.value = [...dataTransfer.files];
|
|
const event = new Event("change", {
|
|
bubbles: true
|
|
});
|
|
event.repack = true;
|
|
inputRef.value.dispatchEvent(event);
|
|
}
|
|
watch(model, (newValue) => {
|
|
const hasModelReset = !Array.isArray(newValue) || !newValue.length;
|
|
if (hasModelReset && inputRef.value) {
|
|
inputRef.value.value = "";
|
|
}
|
|
});
|
|
useRender(() => {
|
|
const hasCounter = !!(slots.counter || props.counter);
|
|
const hasDetails = !!(hasCounter || slots.details);
|
|
const [rootAttrs, inputAttrs] = filterInputAttrs(attrs);
|
|
const {
|
|
modelValue: _,
|
|
...inputProps
|
|
} = VInput.filterProps(props);
|
|
const fieldProps = {
|
|
...VField.filterProps(props),
|
|
"onClick:clear": onClear
|
|
};
|
|
const expectsDirectory = attrs.webkitdirectory !== void 0 && attrs.webkitdirectory !== false;
|
|
const acceptFallback = attrs.accept ? String(attrs.accept) : void 0;
|
|
const inputAccept = expectsDirectory ? void 0 : props.filterByType ?? acceptFallback;
|
|
return createVNode(VInput, mergeProps({
|
|
"ref": vInputRef,
|
|
"modelValue": props.multiple ? model.value : model.value[0],
|
|
"class": ["v-file-input", {
|
|
"v-file-input--chips": !!props.chips,
|
|
"v-file-input--dragging": isDragging.value,
|
|
"v-file-input--hide": props.hideInput,
|
|
"v-input--plain-underlined": isPlainOrUnderlined.value
|
|
}, props.class],
|
|
"style": props.style,
|
|
"onClick:prepend": onClickPrepend
|
|
}, rootAttrs, inputProps, {
|
|
"centerAffix": !isPlainOrUnderlined.value,
|
|
"focused": isFocused.value
|
|
}), {
|
|
...slots,
|
|
default: (_ref3) => {
|
|
let {
|
|
id,
|
|
isDisabled,
|
|
isDirty,
|
|
isReadonly,
|
|
isValid,
|
|
hasDetails: hasDetails2
|
|
} = _ref3;
|
|
return createVNode(VField, mergeProps({
|
|
"ref": vFieldRef,
|
|
"prependIcon": props.prependIcon,
|
|
"onMousedown": onControlMousedown,
|
|
"onClick": onControlClick,
|
|
"onClick:prependInner": props["onClick:prependInner"],
|
|
"onClick:appendInner": props["onClick:appendInner"]
|
|
}, fieldProps, {
|
|
"id": id.value,
|
|
"active": isActive.value || isDirty.value,
|
|
"dirty": isDirty.value || props.dirty,
|
|
"disabled": isDisabled.value,
|
|
"focused": isFocused.value,
|
|
"details": hasDetails2.value,
|
|
"error": isValid.value === false,
|
|
"onDragover": onDragover,
|
|
"onDrop": onDrop
|
|
}), {
|
|
...slots,
|
|
default: (_ref4) => {
|
|
let {
|
|
props: {
|
|
class: fieldClass,
|
|
...slotProps
|
|
},
|
|
controlRef
|
|
} = _ref4;
|
|
return createBaseVNode(Fragment, null, [createBaseVNode("input", mergeProps({
|
|
"ref": (val) => inputRef.value = controlRef.value = val,
|
|
"type": "file",
|
|
"accept": inputAccept,
|
|
"readonly": isReadonly.value,
|
|
"disabled": isDisabled.value,
|
|
"multiple": props.multiple,
|
|
"name": props.name,
|
|
"onClick": (e) => {
|
|
e.stopPropagation();
|
|
if (isReadonly.value) e.preventDefault();
|
|
onFocus();
|
|
},
|
|
"onChange": onFileSelection,
|
|
"onDragleave": onDragleave,
|
|
"onFocus": onFocus,
|
|
"onBlur": blur
|
|
}, slotProps, inputAttrs), null), createBaseVNode("div", {
|
|
"class": normalizeClass(fieldClass)
|
|
}, [!!model.value?.length && !props.hideInput && (slots.selection ? slots.selection({
|
|
fileNames: fileNames.value,
|
|
totalBytes: totalBytes.value,
|
|
totalBytesReadable: totalBytesReadable.value
|
|
}) : props.chips ? fileNames.value.map((text) => createVNode(VChip, {
|
|
"key": text,
|
|
"size": "small",
|
|
"text": text
|
|
}, null)) : fileNames.value.join(", "))])]);
|
|
}
|
|
});
|
|
},
|
|
details: hasDetails ? (slotProps) => createBaseVNode(Fragment, null, [slots.details?.(slotProps), hasCounter && createBaseVNode(Fragment, null, [createBaseVNode("span", null, null), createVNode(VCounter, {
|
|
"active": !!model.value?.length,
|
|
"value": counterValue.value,
|
|
"disabled": props.disabled
|
|
}, slots.counter)])]) : void 0
|
|
});
|
|
});
|
|
return forwardRefs({}, vInputRef, vFieldRef, inputRef);
|
|
}
|
|
});
|
|
|
|
export {
|
|
VFileInput
|
|
};
|
|
//# sourceMappingURL=chunk-EKMPZMZF.js.map
|