major server manager overhaul
This commit is contained in:
+426
@@ -0,0 +1,426 @@
|
||||
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
|
||||
Reference in New Issue
Block a user