@morscherlab/mld-sdk 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AutoGroupModal.vue.d.ts +118 -0
- package/dist/components/AutoGroupModal.vue.js.map +1 -1
- package/dist/components/DataFrame.vue.js +1 -1
- package/dist/components/DataFrame.vue.js.map +1 -1
- package/dist/components/ExperimentPopover.vue.d.ts +1 -0
- package/dist/components/ExperimentPopover.vue.js +72 -46
- package/dist/components/ExperimentPopover.vue.js.map +1 -1
- package/dist/components/ExperimentSelectorModal.vue.js +1 -1
- package/dist/components/ExperimentSelectorModal.vue.js.map +1 -1
- package/dist/components/FileUploader.vue.js +13 -10
- package/dist/components/FileUploader.vue.js.map +1 -1
- package/dist/components/FormBuilder.vue.d.ts +287 -0
- package/dist/components/StepWizard.vue.d.ts +1 -1
- package/dist/components/StepWizard.vue.js.map +1 -1
- package/dist/composables/useApi.js +26 -12
- package/dist/composables/useApi.js.map +1 -1
- package/dist/composables/useAuth.js +7 -6
- package/dist/composables/useAuth.js.map +1 -1
- package/dist/composables/useExperimentSelector.js +14 -5
- package/dist/composables/useExperimentSelector.js.map +1 -1
- package/dist/composables/useForm.js +2 -2
- package/dist/composables/useForm.js.map +1 -1
- package/dist/composables/usePlatformContext.js +11 -0
- package/dist/composables/usePlatformContext.js.map +1 -1
- package/dist/styles.css +165 -98
- package/package.json +1 -1
- package/src/components/AutoGroupModal.vue +1 -1
- package/src/components/DataFrame.vue +1 -1
- package/src/components/ExperimentPopover.vue +32 -19
- package/src/components/ExperimentSelectorModal.vue +1 -1
- package/src/components/FileUploader.vue +14 -11
- package/src/components/StepWizard.vue +1 -1
- package/src/composables/useApi.ts +33 -17
- package/src/composables/useAuth.ts +11 -8
- package/src/composables/useExperimentSelector.ts +19 -7
- package/src/composables/useForm.ts +3 -3
- package/src/composables/usePlatformContext.ts +11 -1
- package/src/styles/components/experiment-popover.css +85 -49
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExperimentPopover.vue.js","sources":["../../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch, onMounted, onUnmounted } from 'vue'\n\ninterface Props {\n experimentName?: string\n experimentStatus?: string\n showSave?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSave: false,\n saveDisabled: false,\n saveLoading: false,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n}>()\n\nconst isOpen = ref(false)\nconst popoverRef = ref<HTMLElement | null>(null)\nconst showSuccess = ref(false)\n\nfunction toggle() {\n isOpen.value = !isOpen.value\n}\n\nfunction close() {\n isOpen.value = false\n}\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n emit('save')\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n if (popoverRef.value && !popoverRef.value.contains(event.target as Node)) {\n close()\n }\n}\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (msg) {\n showSuccess.value = true\n setTimeout(() => {\n showSuccess.value = false\n }, 3000)\n }\n})\n\nonMounted(() => {\n document.addEventListener('click', handleClickOutside)\n})\n\nonUnmounted(() => {\n document.removeEventListener('click', handleClickOutside)\n})\n\n// Format status for display (e.g., \"ready_to_extract\" -> \"Ready to extract\")\nfunction formatStatus(status: string): string {\n return status.replace(/_/g, ' ').replace(/^\\w/, c => c.toUpperCase())\n}\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mld-experiment-popover\">\n <!-- Trigger button -->\n <button\n type=\"button\"\n :class=\"[\n 'mld-experiment-popover__trigger',\n { 'mld-experiment-popover__trigger--active': isOpen },\n { 'mld-experiment-popover__trigger--empty': !experimentName },\n ]\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mld-experiment-popover__trigger-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <span class=\"mld-experiment-popover__trigger-text\">\n {{ experimentName || 'No experiment' }}\n </span>\n <!-- Chevron -->\n <svg class=\"mld-experiment-popover__trigger-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mld-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mld-experiment-popover__header\">\n <div class=\"mld-experiment-popover__title\">Experiment</div>\n </div>\n\n <!-- No experiment selected -->\n <div v-if=\"!experimentName\" class=\"mld-experiment-popover__empty\">\n <button type=\"button\" class=\"mld-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mld-experiment-popover__card\">\n <div class=\"mld-experiment-popover__card-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n </div>\n <div class=\"mld-experiment-popover__card-info\">\n <div class=\"mld-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mld-experiment-popover__card-status\">\n {{ formatStatus(experimentStatus) }}\n </div>\n </div>\n <button type=\"button\" class=\"mld-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n </div>\n\n <!-- Save section -->\n <template v-if=\"showSave\">\n <div class=\"mld-experiment-popover__divider\" />\n <div class=\"mld-experiment-popover__footer\">\n <button\n type=\"button\"\n :class=\"[\n 'mld-experiment-popover__save-btn',\n { 'mld-experiment-popover__save-btn--loading': saveLoading },\n { 'mld-experiment-popover__save-btn--success': showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n @click=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mld-experiment-popover__spinner\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mld-experiment-popover__check-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Label -->\n <span>{{ showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment' }}</span>\n </button>\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_normalizeClass","_toDisplayString","_openBlock","_Fragment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExperimentPopover.vue.js","sources":["../../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch, onMounted, onUnmounted } from 'vue'\n\ninterface Props {\n experimentName?: string\n experimentStatus?: string\n showSave?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSave: false,\n saveDisabled: false,\n saveLoading: false,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n}>()\n\nconst isOpen = ref(false)\nconst popoverRef = ref<HTMLElement | null>(null)\nconst showSuccess = ref(false)\n\nfunction toggle() {\n isOpen.value = !isOpen.value\n}\n\nfunction close() {\n isOpen.value = false\n}\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n emit('save')\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n if (popoverRef.value && !popoverRef.value.contains(event.target as Node)) {\n close()\n }\n}\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (msg) {\n showSuccess.value = true\n setTimeout(() => {\n showSuccess.value = false\n }, 3000)\n }\n})\n\nonMounted(() => {\n document.addEventListener('click', handleClickOutside)\n})\n\nonUnmounted(() => {\n document.removeEventListener('click', handleClickOutside)\n})\n\n// Format status for display (e.g., \"ready_to_extract\" -> \"Ready to extract\")\nfunction formatStatus(status: string): string {\n return status.replace(/_/g, ' ').replace(/^\\w/, c => c.toUpperCase())\n}\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mld-experiment-popover\">\n <!-- Trigger button -->\n <button\n type=\"button\"\n :class=\"[\n 'mld-experiment-popover__trigger',\n { 'mld-experiment-popover__trigger--active': isOpen },\n { 'mld-experiment-popover__trigger--empty': !experimentName },\n ]\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mld-experiment-popover__trigger-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <span class=\"mld-experiment-popover__trigger-text\">\n {{ experimentName || 'No experiment' }}\n </span>\n <!-- Chevron -->\n <svg class=\"mld-experiment-popover__trigger-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mld-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mld-experiment-popover__header\">\n <div class=\"mld-experiment-popover__title\">Experiment</div>\n <div class=\"mld-experiment-popover__subtitle\">\n {{ experimentName ? 'Linked experiment context' : 'Link to an MLD experiment' }}\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-if=\"!experimentName\" class=\"mld-experiment-popover__body\">\n <button type=\"button\" class=\"mld-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mld-experiment-popover__body\">\n <div class=\"mld-experiment-popover__card\">\n <div class=\"mld-experiment-popover__card-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n </div>\n <div class=\"mld-experiment-popover__card-info\">\n <div class=\"mld-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mld-experiment-popover__card-status\">\n {{ formatStatus(experimentStatus) }}\n </div>\n </div>\n <button type=\"button\" class=\"mld-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n </div>\n </div>\n\n <!-- Save section -->\n <template v-if=\"showSave\">\n <div class=\"mld-experiment-popover__divider\" />\n <div class=\"mld-experiment-popover__footer\">\n <button\n type=\"button\"\n :class=\"[\n 'mld-experiment-popover__save-btn',\n { 'mld-experiment-popover__save-btn--loading': saveLoading },\n { 'mld-experiment-popover__save-btn--success': showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n @click=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mld-experiment-popover__spinner\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mld-experiment-popover__check-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mld-experiment-popover__save-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n <!-- Label -->\n <span>{{ showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment' }}</span>\n </button>\n <div v-if=\"saveDisabled && saveDisabledMessage && !showSuccess\" class=\"mld-experiment-popover__save-hint\">\n {{ saveDisabledMessage }}\n </div>\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_normalizeClass","_toDisplayString","_openBlock","_Fragment"],"mappings":"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"}
|
|
@@ -111,7 +111,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
111
111
|
return groupedByProject.value.flatMap(([, exps]) => exps);
|
|
112
112
|
});
|
|
113
113
|
function setFilter(key, value) {
|
|
114
|
-
filters[key] = String(value) ||
|
|
114
|
+
filters[key] = String(value) || void 0;
|
|
115
115
|
}
|
|
116
116
|
function handleSortChange(value) {
|
|
117
117
|
sortKey.value = String(value) || "created_at:desc";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExperimentSelectorModal.vue.js","sources":["../../src/components/ExperimentSelectorModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, reactive, computed, watch, nextTick } from 'vue'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport {\n formatExperimentDate,\n EXPERIMENT_STATUS_OPTIONS,\n EXPERIMENT_STATUS_VARIANT_MAP,\n DATE_PRESET_OPTIONS,\n SORT_OPTIONS,\n} from '../composables/experiment-utils'\nimport BaseModal from './BaseModal.vue'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport BasePill from './BasePill.vue'\nimport Skeleton from './Skeleton.vue'\nimport EmptyState from './EmptyState.vue'\nimport ExperimentCodeBadge from './ExperimentCodeBadge.vue'\n\ninterface Props {\n modelValue: boolean\n experimentType?: string\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n groupByProject?: boolean\n showFilters?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'full',\n groupByProject: false,\n showFilters: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n select: [experiment: ExperimentSummary]\n deselect: []\n}>()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n fetchFilterOptions,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n})\n\nconst activeIndex = ref(-1)\nconst listRef = ref<HTMLElement | null>(null)\nconst showAdvanced = ref(props.showFilters)\nconst groupToggle = ref(props.groupByProject)\n\n// Track whether any advanced filter is active (for badge dot)\nconst hasActiveAdvancedFilters = computed(() =>\n !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== 'created_at:desc'),\n)\n\n// Build type filter options from fetched experiment types\nconst typeFilterOptions = computed(() => [\n { value: '', label: 'All types' },\n ...experimentTypes.value.map(t => ({ value: t.value, label: t.label })),\n])\n\n// Build project filter options from fetched projects\nconst projectFilterOptions = computed(() => [\n { value: '', label: 'All projects' },\n ...projects.value,\n])\n\n// Flat list of experiments for keyboard navigation (works across groups too)\nconst flatExperiments = computed(() => {\n if (!groupToggle.value) return experiments.value\n return groupedByProject.value.flatMap(([, exps]) => exps)\n})\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || null\n}\n\nfunction handleSortChange(value: string | number) {\n sortKey.value = String(value) || 'created_at:desc'\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n emit('select', experiment)\n emit('update:modelValue', false)\n}\n\nfunction handleDeselect() {\n emit('deselect')\n emit('update:modelValue', false)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const list = flatExperiments.value\n if (!list.length) return\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n activeIndex.value = Math.min(activeIndex.value + 1, list.length - 1)\n scrollActiveIntoView()\n break\n case 'ArrowUp':\n event.preventDefault()\n activeIndex.value = Math.max(activeIndex.value - 1, 0)\n scrollActiveIntoView()\n break\n case 'Enter':\n event.preventDefault()\n if (activeIndex.value >= 0 && activeIndex.value < list.length) {\n handleSelect(list[activeIndex.value])\n }\n break\n }\n}\n\nfunction scrollActiveIntoView() {\n nextTick(() => {\n const row = listRef.value?.querySelector('.mld-experiment-selector__row--focused')\n row?.scrollIntoView({ block: 'nearest' })\n })\n}\n\n// Precomputed id → flat index for O(1) lookup in grouped mode\nconst flatIndexMap = computed(() => {\n const map = new Map<number, number>()\n flatExperiments.value.forEach((exp, i) => map.set(exp.id, i))\n return map\n})\n\nfunction getFlatIndex(experiment: ExperimentSummary): number {\n return flatIndexMap.value.get(experiment.id) ?? -1\n}\n\n// Track collapsed groups (reactive Set tracks .add/.delete/.has automatically)\nconst collapsedGroups = reactive(new Set<string>())\n\nfunction toggleGroup(groupName: string) {\n if (collapsedGroups.has(groupName)) {\n collapsedGroups.delete(groupName)\n } else {\n collapsedGroups.add(groupName)\n }\n}\n\n// Reset active index when experiments change\nwatch(experiments, () => { activeIndex.value = -1 })\n\n// Fetch on open\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (isOpen) {\n activeIndex.value = -1\n collapsedGroups.clear()\n fetchFilterOptions()\n fetchExperiments()\n }\n },\n)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mld-experiment-selector\" @keydown=\"handleKeydown\">\n <!-- Filter bar row 1 -->\n <div class=\"mld-experiment-selector__filters-row\">\n <div class=\"mld-experiment-selector__search\">\n <BaseInput\n v-model=\"filters.search\"\n placeholder=\"Search experiments...\"\n size=\"sm\"\n type=\"search\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.status ?? ''\"\n :options=\"EXPERIMENT_STATUS_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('status', v)\"\n />\n </div>\n <div v-if=\"typeFilterOptions.length > 1\" class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.experimentType ?? ''\"\n :options=\"typeFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('experimentType', v)\"\n />\n </div>\n <button\n class=\"mld-experiment-selector__filters-toggle\"\n :class=\"{ 'mld-experiment-selector__filters-toggle--active': hasActiveAdvancedFilters }\"\n type=\"button\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" /><line x1=\"8\" y1=\"12\" x2=\"20\" y2=\"12\" /><line x1=\"12\" y1=\"18\" x2=\"20\" y2=\"18\" />\n <circle cx=\"6\" cy=\"12\" r=\"2\" /><circle cx=\"10\" cy=\"18\" r=\"2\" /><circle cx=\"6\" cy=\"6\" r=\"2\" />\n </svg>\n Filters\n <span v-if=\"hasActiveAdvancedFilters\" class=\"mld-experiment-selector__filters-dot\" />\n </button>\n </div>\n\n <!-- Filter bar row 2 (advanced, collapsible) -->\n <div v-if=\"showAdvanced\" class=\"mld-experiment-selector__filters-advanced\">\n <div v-if=\"projectFilterOptions.length > 1\" class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.project ?? ''\"\n :options=\"projectFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('project', v)\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.datePreset ?? ''\"\n :options=\"DATE_PRESET_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('datePreset', v)\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"sortKey\"\n :options=\"SORT_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"handleSortChange\"\n />\n </div>\n <label class=\"mld-experiment-selector__group-toggle\">\n <input\n v-model=\"groupToggle\"\n type=\"checkbox\"\n class=\"mld-experiment-selector__group-checkbox\"\n />\n Group by project\n </label>\n </div>\n\n <!-- Loading skeleton -->\n <div v-if=\"isLoading\" class=\"mld-experiment-selector__skeleton\">\n <div v-for=\"n in 4\" :key=\"n\" class=\"mld-experiment-selector__skeleton-row\">\n <div class=\"mld-experiment-selector__skeleton-content\">\n <Skeleton :width=\"120 + n * 20\" height=\"14px\" />\n <Skeleton width=\"80px\" height=\"10px\" />\n </div>\n <Skeleton width=\"60px\" height=\"20px\" variant=\"rounded\" />\n </div>\n </div>\n\n <!-- Error -->\n <div v-else-if=\"error\" class=\"mld-experiment-selector__error\">\n {{ error }}\n </div>\n\n <!-- Empty -->\n <EmptyState\n v-else-if=\"experiments.length === 0\"\n title=\"No experiments found\"\n description=\"Try adjusting your search or filters.\"\n size=\"sm\"\n />\n\n <!-- Experiment list: grouped mode -->\n <div v-else-if=\"groupToggle\" ref=\"listRef\" class=\"mld-experiment-selector__list\">\n <template v-for=\"([groupName, groupExps]) in groupedByProject\" :key=\"groupName\">\n <button\n type=\"button\"\n class=\"mld-experiment-selector__group-header\"\n @click=\"toggleGroup(groupName)\"\n >\n <svg\n class=\"mld-experiment-selector__group-chevron\"\n :class=\"{ 'mld-experiment-selector__group-chevron--collapsed': collapsedGroups.has(groupName) }\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n <span class=\"mld-experiment-selector__group-name\">{{ groupName }}</span>\n <span class=\"mld-experiment-selector__group-count\">{{ groupExps.length }}</span>\n </button>\n <template v-if=\"!collapsedGroups.has(groupName)\">\n <div\n v-for=\"exp in groupExps\"\n :key=\"exp.id\"\n class=\"mld-experiment-selector__row\"\n :class=\"{\n 'mld-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mld-experiment-selector__row--focused': getFlatIndex(exp) === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = getFlatIndex(exp)\"\n >\n <div class=\"mld-experiment-selector__row-content\">\n <div class=\"mld-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mld-experiment-selector__meta\">\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"EXPERIMENT_STATUS_VARIANT_MAP[exp.status]\" size=\"sm\">\n {{ exp.status }}\n </BasePill>\n </div>\n </template>\n </template>\n </div>\n\n <!-- Experiment list: flat mode -->\n <div v-else ref=\"listRef\" class=\"mld-experiment-selector__list\">\n <div\n v-for=\"(exp, idx) in experiments\"\n :key=\"exp.id\"\n class=\"mld-experiment-selector__row\"\n :class=\"{\n 'mld-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mld-experiment-selector__row--focused': idx === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <div class=\"mld-experiment-selector__row-content\">\n <div class=\"mld-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mld-experiment-selector__meta\">\n <span v-if=\"exp.project_name || exp.project\">{{ exp.project_name || exp.project }}</span>\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"EXPERIMENT_STATUS_VARIANT_MAP[exp.status]\" size=\"sm\">\n {{ exp.status }}\n </BasePill>\n </div>\n </div>\n\n <!-- Footer: clear selection -->\n <div v-if=\"currentExperimentId != null\" class=\"mld-experiment-selector__footer\">\n <button\n type=\"button\"\n class=\"mld-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/experiment-selector-modal.css';\n</style>\n"],"names":["_createBlock","BaseModal","_createElementVNode","_createVNode","BaseInput","_unref","BaseSelect","_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","Skeleton","_toDisplayString","EmptyState","ExperimentCodeBadge","BasePill","_createTextVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExperimentSelectorModal.vue.js","sources":["../../src/components/ExperimentSelectorModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, reactive, computed, watch, nextTick } from 'vue'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport {\n formatExperimentDate,\n EXPERIMENT_STATUS_OPTIONS,\n EXPERIMENT_STATUS_VARIANT_MAP,\n DATE_PRESET_OPTIONS,\n SORT_OPTIONS,\n} from '../composables/experiment-utils'\nimport BaseModal from './BaseModal.vue'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport BasePill from './BasePill.vue'\nimport Skeleton from './Skeleton.vue'\nimport EmptyState from './EmptyState.vue'\nimport ExperimentCodeBadge from './ExperimentCodeBadge.vue'\n\ninterface Props {\n modelValue: boolean\n experimentType?: string\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n groupByProject?: boolean\n showFilters?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'full',\n groupByProject: false,\n showFilters: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n select: [experiment: ExperimentSummary]\n deselect: []\n}>()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n fetchFilterOptions,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n})\n\nconst activeIndex = ref(-1)\nconst listRef = ref<HTMLElement | null>(null)\nconst showAdvanced = ref(props.showFilters)\nconst groupToggle = ref(props.groupByProject)\n\n// Track whether any advanced filter is active (for badge dot)\nconst hasActiveAdvancedFilters = computed(() =>\n !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== 'created_at:desc'),\n)\n\n// Build type filter options from fetched experiment types\nconst typeFilterOptions = computed(() => [\n { value: '', label: 'All types' },\n ...experimentTypes.value.map(t => ({ value: t.value, label: t.label })),\n])\n\n// Build project filter options from fetched projects\nconst projectFilterOptions = computed(() => [\n { value: '', label: 'All projects' },\n ...projects.value,\n])\n\n// Flat list of experiments for keyboard navigation (works across groups too)\nconst flatExperiments = computed(() => {\n if (!groupToggle.value) return experiments.value\n return groupedByProject.value.flatMap(([, exps]) => exps)\n})\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSortChange(value: string | number) {\n sortKey.value = String(value) || 'created_at:desc'\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n emit('select', experiment)\n emit('update:modelValue', false)\n}\n\nfunction handleDeselect() {\n emit('deselect')\n emit('update:modelValue', false)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const list = flatExperiments.value\n if (!list.length) return\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n activeIndex.value = Math.min(activeIndex.value + 1, list.length - 1)\n scrollActiveIntoView()\n break\n case 'ArrowUp':\n event.preventDefault()\n activeIndex.value = Math.max(activeIndex.value - 1, 0)\n scrollActiveIntoView()\n break\n case 'Enter':\n event.preventDefault()\n if (activeIndex.value >= 0 && activeIndex.value < list.length) {\n handleSelect(list[activeIndex.value])\n }\n break\n }\n}\n\nfunction scrollActiveIntoView() {\n nextTick(() => {\n const row = listRef.value?.querySelector('.mld-experiment-selector__row--focused')\n row?.scrollIntoView({ block: 'nearest' })\n })\n}\n\n// Precomputed id → flat index for O(1) lookup in grouped mode\nconst flatIndexMap = computed(() => {\n const map = new Map<number, number>()\n flatExperiments.value.forEach((exp, i) => map.set(exp.id, i))\n return map\n})\n\nfunction getFlatIndex(experiment: ExperimentSummary): number {\n return flatIndexMap.value.get(experiment.id) ?? -1\n}\n\n// Track collapsed groups (reactive Set tracks .add/.delete/.has automatically)\nconst collapsedGroups = reactive(new Set<string>())\n\nfunction toggleGroup(groupName: string) {\n if (collapsedGroups.has(groupName)) {\n collapsedGroups.delete(groupName)\n } else {\n collapsedGroups.add(groupName)\n }\n}\n\n// Reset active index when experiments change\nwatch(experiments, () => { activeIndex.value = -1 })\n\n// Fetch on open\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (isOpen) {\n activeIndex.value = -1\n collapsedGroups.clear()\n fetchFilterOptions()\n fetchExperiments()\n }\n },\n)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mld-experiment-selector\" @keydown=\"handleKeydown\">\n <!-- Filter bar row 1 -->\n <div class=\"mld-experiment-selector__filters-row\">\n <div class=\"mld-experiment-selector__search\">\n <BaseInput\n v-model=\"filters.search\"\n placeholder=\"Search experiments...\"\n size=\"sm\"\n type=\"search\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.status ?? ''\"\n :options=\"EXPERIMENT_STATUS_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('status', v)\"\n />\n </div>\n <div v-if=\"typeFilterOptions.length > 1\" class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.experimentType ?? ''\"\n :options=\"typeFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('experimentType', v)\"\n />\n </div>\n <button\n class=\"mld-experiment-selector__filters-toggle\"\n :class=\"{ 'mld-experiment-selector__filters-toggle--active': hasActiveAdvancedFilters }\"\n type=\"button\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" /><line x1=\"8\" y1=\"12\" x2=\"20\" y2=\"12\" /><line x1=\"12\" y1=\"18\" x2=\"20\" y2=\"18\" />\n <circle cx=\"6\" cy=\"12\" r=\"2\" /><circle cx=\"10\" cy=\"18\" r=\"2\" /><circle cx=\"6\" cy=\"6\" r=\"2\" />\n </svg>\n Filters\n <span v-if=\"hasActiveAdvancedFilters\" class=\"mld-experiment-selector__filters-dot\" />\n </button>\n </div>\n\n <!-- Filter bar row 2 (advanced, collapsible) -->\n <div v-if=\"showAdvanced\" class=\"mld-experiment-selector__filters-advanced\">\n <div v-if=\"projectFilterOptions.length > 1\" class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.project ?? ''\"\n :options=\"projectFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('project', v)\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.datePreset ?? ''\"\n :options=\"DATE_PRESET_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('datePreset', v)\"\n />\n </div>\n <div class=\"mld-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"sortKey\"\n :options=\"SORT_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"handleSortChange\"\n />\n </div>\n <label class=\"mld-experiment-selector__group-toggle\">\n <input\n v-model=\"groupToggle\"\n type=\"checkbox\"\n class=\"mld-experiment-selector__group-checkbox\"\n />\n Group by project\n </label>\n </div>\n\n <!-- Loading skeleton -->\n <div v-if=\"isLoading\" class=\"mld-experiment-selector__skeleton\">\n <div v-for=\"n in 4\" :key=\"n\" class=\"mld-experiment-selector__skeleton-row\">\n <div class=\"mld-experiment-selector__skeleton-content\">\n <Skeleton :width=\"120 + n * 20\" height=\"14px\" />\n <Skeleton width=\"80px\" height=\"10px\" />\n </div>\n <Skeleton width=\"60px\" height=\"20px\" variant=\"rounded\" />\n </div>\n </div>\n\n <!-- Error -->\n <div v-else-if=\"error\" class=\"mld-experiment-selector__error\">\n {{ error }}\n </div>\n\n <!-- Empty -->\n <EmptyState\n v-else-if=\"experiments.length === 0\"\n title=\"No experiments found\"\n description=\"Try adjusting your search or filters.\"\n size=\"sm\"\n />\n\n <!-- Experiment list: grouped mode -->\n <div v-else-if=\"groupToggle\" ref=\"listRef\" class=\"mld-experiment-selector__list\">\n <template v-for=\"([groupName, groupExps]) in groupedByProject\" :key=\"groupName\">\n <button\n type=\"button\"\n class=\"mld-experiment-selector__group-header\"\n @click=\"toggleGroup(groupName)\"\n >\n <svg\n class=\"mld-experiment-selector__group-chevron\"\n :class=\"{ 'mld-experiment-selector__group-chevron--collapsed': collapsedGroups.has(groupName) }\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n <span class=\"mld-experiment-selector__group-name\">{{ groupName }}</span>\n <span class=\"mld-experiment-selector__group-count\">{{ groupExps.length }}</span>\n </button>\n <template v-if=\"!collapsedGroups.has(groupName)\">\n <div\n v-for=\"exp in groupExps\"\n :key=\"exp.id\"\n class=\"mld-experiment-selector__row\"\n :class=\"{\n 'mld-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mld-experiment-selector__row--focused': getFlatIndex(exp) === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = getFlatIndex(exp)\"\n >\n <div class=\"mld-experiment-selector__row-content\">\n <div class=\"mld-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mld-experiment-selector__meta\">\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"EXPERIMENT_STATUS_VARIANT_MAP[exp.status]\" size=\"sm\">\n {{ exp.status }}\n </BasePill>\n </div>\n </template>\n </template>\n </div>\n\n <!-- Experiment list: flat mode -->\n <div v-else ref=\"listRef\" class=\"mld-experiment-selector__list\">\n <div\n v-for=\"(exp, idx) in experiments\"\n :key=\"exp.id\"\n class=\"mld-experiment-selector__row\"\n :class=\"{\n 'mld-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mld-experiment-selector__row--focused': idx === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <div class=\"mld-experiment-selector__row-content\">\n <div class=\"mld-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mld-experiment-selector__meta\">\n <span v-if=\"exp.project_name || exp.project\">{{ exp.project_name || exp.project }}</span>\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"EXPERIMENT_STATUS_VARIANT_MAP[exp.status]\" size=\"sm\">\n {{ exp.status }}\n </BasePill>\n </div>\n </div>\n\n <!-- Footer: clear selection -->\n <div v-if=\"currentExperimentId != null\" class=\"mld-experiment-selector__footer\">\n <button\n type=\"button\"\n class=\"mld-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/experiment-selector-modal.css';\n</style>\n"],"names":["_createBlock","BaseModal","_createElementVNode","_createVNode","BaseInput","_unref","BaseSelect","_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","Skeleton","_toDisplayString","EmptyState","ExperimentCodeBadge","BasePill","_createTextVNode"],"mappings":"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"}
|
|
@@ -172,17 +172,20 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
172
172
|
}
|
|
173
173
|
async function readDirectory(directory, files) {
|
|
174
174
|
const reader = directory.createReader();
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
let batch;
|
|
176
|
+
do {
|
|
177
|
+
batch = await new Promise((resolve, reject) => {
|
|
178
|
+
reader.readEntries(resolve, reject);
|
|
179
|
+
});
|
|
180
|
+
for (const entry of batch) {
|
|
181
|
+
if (entry.isFile) {
|
|
182
|
+
const file = await getFile(entry);
|
|
183
|
+
if (file) files.push(file);
|
|
184
|
+
} else if (entry.isDirectory) {
|
|
185
|
+
await readDirectory(entry, files);
|
|
186
|
+
}
|
|
184
187
|
}
|
|
185
|
-
}
|
|
188
|
+
} while (batch.length > 0);
|
|
186
189
|
}
|
|
187
190
|
function getFile(entry) {
|
|
188
191
|
return new Promise((resolve) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploader.vue.js","sources":["../../src/components/FileUploader.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\n\ninterface Props {\n accept?: string\n multiple?: boolean\n maxSize?: number\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n mode?: 'file' | 'folder'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n multiple: false,\n disabled: false,\n size: 'md',\n mode: 'file',\n})\n\nconst emit = defineEmits<{\n upload: [files: File[]]\n error: [message: string]\n}>()\n\nconst isDragOver = ref(false)\nconst inputRef = ref<HTMLInputElement>()\nconst selectedFiles = ref<File[]>([])\nconst folderName = ref<string | null>(null)\n\nconst isFolder = computed(() => props.mode === 'folder')\n\nconst acceptLabel = computed(() => {\n if (!props.accept) return 'any file'\n return props.accept.split(',').map(a => a.trim().replace('.', '').toUpperCase()).join(', ')\n})\n\nconst maxSizeLabel = computed(() => {\n if (!props.maxSize) return null\n if (props.maxSize >= 1024 * 1024 * 1024) {\n return `${(props.maxSize / (1024 * 1024 * 1024)).toFixed(1)} GB`\n }\n if (props.maxSize >= 1024 * 1024) {\n return `${(props.maxSize / (1024 * 1024)).toFixed(1)} MB`\n }\n if (props.maxSize >= 1024) {\n return `${(props.maxSize / 1024).toFixed(1)} KB`\n }\n return `${props.maxSize} bytes`\n})\n\nconst fileTypeMap: Record<string, { color: string; label: string }> = {\n csv: { color: '#10B981', label: 'CSV' },\n tsv: { color: '#10B981', label: 'TSV' },\n xlsx: { color: '#10B981', label: 'XLSX' },\n xls: { color: '#10B981', label: 'XLS' },\n pdf: { color: '#EF4444', label: 'PDF' },\n png: { color: '#8B5CF6', label: 'PNG' },\n jpg: { color: '#8B5CF6', label: 'JPG' },\n jpeg: { color: '#8B5CF6', label: 'JPEG' },\n gif: { color: '#8B5CF6', label: 'GIF' },\n svg: { color: '#8B5CF6', label: 'SVG' },\n webp: { color: '#8B5CF6', label: 'WEBP' },\n json: { color: '#F59E0B', label: 'JSON' },\n xml: { color: '#F59E0B', label: 'XML' },\n txt: { color: '#64748B', label: 'TXT' },\n zip: { color: '#6366F1', label: 'ZIP' },\n gz: { color: '#6366F1', label: 'GZ' },\n py: { color: '#3B82F6', label: 'PY' },\n js: { color: '#EAB308', label: 'JS' },\n ts: { color: '#3B82F6', label: 'TS' },\n fasta: { color: '#EC4899', label: 'FASTA' },\n fastq: { color: '#EC4899', label: 'FASTQ' },\n bam: { color: '#EC4899', label: 'BAM' },\n vcf: { color: '#EC4899', label: 'VCF' },\n}\n\nfunction getFileType(filename: string): { color: string; label: string } {\n const ext = filename.split('.').pop()?.toLowerCase() || ''\n return fileTypeMap[ext] || { color: '#94A3B8', label: ext.toUpperCase() || 'FILE' }\n}\n\nfunction validateFiles(files: FileList | File[]): File[] {\n const validFiles: File[] = []\n\n for (const file of Array.from(files)) {\n if (props.maxSize && file.size > props.maxSize) {\n emit('error', `File \"${file.name}\" exceeds maximum size of ${maxSizeLabel.value}`)\n continue\n }\n\n if (props.accept) {\n const acceptedTypes = props.accept.split(',').map(t => t.trim().toLowerCase())\n const fileExt = '.' + file.name.split('.').pop()?.toLowerCase()\n const fileType = file.type.toLowerCase()\n\n const isAccepted = acceptedTypes.some(type => {\n if (type.startsWith('.')) {\n return fileExt === type\n }\n if (type.endsWith('/*')) {\n return fileType.startsWith(type.replace('/*', '/'))\n }\n return fileType === type\n })\n\n if (!isAccepted) {\n emit('error', `File \"${file.name}\" is not an accepted file type`)\n continue\n }\n }\n\n validFiles.push(file)\n }\n\n return validFiles\n}\n\nfunction handleFiles(files: FileList | File[], folder?: string) {\n const validFiles = validateFiles(files)\n if (validFiles.length === 0) return\n\n if (isFolder.value) {\n folderName.value = folder || extractFolderName(validFiles)\n selectedFiles.value = validFiles\n emit('upload', validFiles)\n } else if (!props.multiple) {\n selectedFiles.value = [validFiles[0]]\n emit('upload', [validFiles[0]])\n } else {\n selectedFiles.value = [...selectedFiles.value, ...validFiles]\n emit('upload', validFiles)\n }\n}\n\nfunction extractFolderName(files: File[]): string | null {\n if (files.length === 0) return null\n const firstPath = files[0].webkitRelativePath\n if (firstPath) {\n return firstPath.split('/')[0]\n }\n return null\n}\n\nfunction handleDrop(event: DragEvent) {\n event.preventDefault()\n isDragOver.value = false\n if (props.disabled) return\n\n if (isFolder.value && event.dataTransfer?.items) {\n handleFolderDrop(event.dataTransfer.items)\n } else if (event.dataTransfer?.files) {\n handleFiles(event.dataTransfer.files)\n }\n}\n\nasync function handleFolderDrop(items: DataTransferItemList) {\n const files: File[] = []\n let folderNameFromDrop: string | undefined\n\n for (const item of Array.from(items)) {\n const entry = item.webkitGetAsEntry?.()\n if (entry?.isDirectory) {\n folderNameFromDrop = entry.name\n await readDirectory(entry as FileSystemDirectoryEntry, files)\n } else if (entry?.isFile) {\n const file = await getFile(entry as FileSystemFileEntry)\n if (file) files.push(file)\n }\n }\n\n if (files.length > 0) {\n handleFiles(files, folderNameFromDrop)\n }\n}\n\nasync function readDirectory(directory: FileSystemDirectoryEntry, files: File[]): Promise<void> {\n const reader = directory.createReader()\n const entries = await new Promise<FileSystemEntry[]>((resolve) => {\n reader.readEntries(resolve)\n })\n\n for (const entry of entries) {\n if (entry.isFile) {\n const file = await getFile(entry as FileSystemFileEntry)\n if (file) files.push(file)\n } else if (entry.isDirectory) {\n await readDirectory(entry as FileSystemDirectoryEntry, files)\n }\n }\n}\n\nfunction getFile(entry: FileSystemFileEntry): Promise<File | null> {\n return new Promise((resolve) => {\n entry.file(resolve, () => resolve(null))\n })\n}\n\nfunction handleDragOver(event: DragEvent) {\n event.preventDefault()\n if (!props.disabled) {\n isDragOver.value = true\n }\n}\n\nfunction handleDragLeave() {\n isDragOver.value = false\n}\n\nfunction handleInputChange(event: Event) {\n const target = event.target as HTMLInputElement\n if (target.files) {\n handleFiles(target.files)\n }\n target.value = ''\n}\n\nfunction openFilePicker() {\n if (!props.disabled) {\n inputRef.value?.click()\n }\n}\n\nfunction removeFile(index: number) {\n selectedFiles.value = selectedFiles.value.filter((_, i) => i !== index)\n if (isFolder.value && selectedFiles.value.length === 0) {\n folderName.value = null\n }\n}\n\nfunction clearAll() {\n selectedFiles.value = []\n folderName.value = null\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes >= 1024 * 1024) {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n }\n if (bytes >= 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`\n }\n return `${bytes} bytes`\n}\n</script>\n\n<template>\n <div class=\"mld-file-uploader\">\n <!-- Dropzone -->\n <div\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"disabled ? 'File upload disabled' : 'Click or drag files to upload'\"\n :aria-disabled=\"disabled\"\n :class=\"[\n 'mld-file-uploader__dropzone',\n `mld-file-uploader__dropzone--${size}`,\n isDragOver ? 'mld-file-uploader__dropzone--dragover' : '',\n disabled ? 'mld-file-uploader__dropzone--disabled' : '',\n selectedFiles.length > 0 ? 'mld-file-uploader__dropzone--has-files' : '',\n ]\"\n @click=\"openFilePicker\"\n @keydown.enter=\"openFilePicker\"\n @keydown.space.prevent=\"openFilePicker\"\n @drop=\"handleDrop\"\n @dragover=\"handleDragOver\"\n @dragleave=\"handleDragLeave\"\n >\n <!-- Animated border overlay -->\n <div class=\"mld-file-uploader__border\" aria-hidden=\"true\" />\n\n <!-- Glow effect on drag -->\n <div v-if=\"isDragOver\" class=\"mld-file-uploader__glow\" aria-hidden=\"true\" />\n\n <input\n ref=\"inputRef\"\n type=\"file\"\n :accept=\"accept\"\n :multiple=\"isFolder || multiple\"\n :disabled=\"disabled\"\n :webkitdirectory=\"isFolder || undefined\"\n class=\"mld-file-uploader__input\"\n @change=\"handleInputChange\"\n />\n\n <div class=\"mld-file-uploader__content\">\n <!-- Animated icon -->\n <div :class=\"['mld-file-uploader__icon-wrapper', `mld-file-uploader__icon-wrapper--${size}`]\">\n <div class=\"mld-file-uploader__icon-bg\" aria-hidden=\"true\" />\n <svg\n v-if=\"isFolder\"\n :class=\"['mld-file-uploader__icon', `mld-file-uploader__icon--${size}`]\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\" />\n </svg>\n <svg\n v-else\n :class=\"['mld-file-uploader__icon', `mld-file-uploader__icon--${size}`]\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242\" /><path d=\"M12 13v8\" /><path d=\"m8 17 4-4 4 4\" />\n </svg>\n </div>\n\n <p :class=\"['mld-file-uploader__text', `mld-file-uploader__text--${size}`]\">\n <span class=\"mld-file-uploader__highlight\">{{ isFolder ? 'Click to select folder' : 'Click to upload' }}</span>\n <span class=\"mld-file-uploader__text-secondary\"> or drag and drop</span>\n </p>\n\n <p :class=\"['mld-file-uploader__hint', `mld-file-uploader__hint--${size}`]\">\n <template v-if=\"isFolder\">\n Select a folder to upload all files within\n </template>\n <template v-else>\n {{ acceptLabel }}{{ maxSizeLabel ? ` (max ${maxSizeLabel})` : '' }}\n </template>\n </p>\n </div>\n </div>\n\n <!-- Folder summary display -->\n <div v-if=\"isFolder && folderName && selectedFiles.length > 0\" class=\"mld-file-uploader__folder-summary\">\n <div class=\"mld-file-uploader__folder-info\">\n <div class=\"mld-file-uploader__folder-icon-wrapper\">\n <svg class=\"mld-file-uploader__folder-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2\" />\n </svg>\n </div>\n <div class=\"mld-file-uploader__folder-details\">\n <span class=\"mld-file-uploader__folder-name\">{{ folderName }}</span>\n <span class=\"mld-file-uploader__folder-count\">{{ selectedFiles.length }} file{{ selectedFiles.length !== 1 ? 's' : '' }}</span>\n </div>\n </div>\n <button\n type=\"button\"\n aria-label=\"Clear folder\"\n class=\"mld-file-uploader__remove-btn\"\n @click.stop=\"clearAll\"\n >\n <svg class=\"mld-file-uploader__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- File list display -->\n <TransitionGroup\n v-if=\"!isFolder && selectedFiles.length > 0\"\n tag=\"ul\"\n name=\"mld-file-uploader-item\"\n class=\"mld-file-uploader__list\"\n >\n <li\n v-for=\"(file, index) in selectedFiles\"\n :key=\"`${file.name}-${file.size}-${index}`\"\n class=\"mld-file-uploader__file\"\n >\n <div class=\"mld-file-uploader__file-info\">\n <span\n class=\"mld-file-uploader__file-badge\"\n :style=\"{ '--badge-color': getFileType(file.name).color }\"\n >\n {{ getFileType(file.name).label }}\n </span>\n <div class=\"mld-file-uploader__file-meta\">\n <span class=\"mld-file-uploader__file-name\">{{ file.name }}</span>\n <span class=\"mld-file-uploader__file-size\">{{ formatFileSize(file.size) }}</span>\n </div>\n </div>\n <button\n type=\"button\"\n :aria-label=\"`Remove ${file.name}`\"\n class=\"mld-file-uploader__remove-btn\"\n @click.stop=\"removeFile(index)\"\n >\n <svg class=\"mld-file-uploader__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </li>\n </TransitionGroup>\n </div>\n</template>\n\n<style>\n@import '../styles/components/file-uploader.css';\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_normalizeClass","_toDisplayString","_Fragment","_createBlock","_TransitionGroup","_renderList","_withModifiers"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FileUploader.vue.js","sources":["../../src/components/FileUploader.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\n\ninterface Props {\n accept?: string\n multiple?: boolean\n maxSize?: number\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n mode?: 'file' | 'folder'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n multiple: false,\n disabled: false,\n size: 'md',\n mode: 'file',\n})\n\nconst emit = defineEmits<{\n upload: [files: File[]]\n error: [message: string]\n}>()\n\nconst isDragOver = ref(false)\nconst inputRef = ref<HTMLInputElement>()\nconst selectedFiles = ref<File[]>([])\nconst folderName = ref<string | null>(null)\n\nconst isFolder = computed(() => props.mode === 'folder')\n\nconst acceptLabel = computed(() => {\n if (!props.accept) return 'any file'\n return props.accept.split(',').map(a => a.trim().replace('.', '').toUpperCase()).join(', ')\n})\n\nconst maxSizeLabel = computed(() => {\n if (!props.maxSize) return null\n if (props.maxSize >= 1024 * 1024 * 1024) {\n return `${(props.maxSize / (1024 * 1024 * 1024)).toFixed(1)} GB`\n }\n if (props.maxSize >= 1024 * 1024) {\n return `${(props.maxSize / (1024 * 1024)).toFixed(1)} MB`\n }\n if (props.maxSize >= 1024) {\n return `${(props.maxSize / 1024).toFixed(1)} KB`\n }\n return `${props.maxSize} bytes`\n})\n\nconst fileTypeMap: Record<string, { color: string; label: string }> = {\n csv: { color: '#10B981', label: 'CSV' },\n tsv: { color: '#10B981', label: 'TSV' },\n xlsx: { color: '#10B981', label: 'XLSX' },\n xls: { color: '#10B981', label: 'XLS' },\n pdf: { color: '#EF4444', label: 'PDF' },\n png: { color: '#8B5CF6', label: 'PNG' },\n jpg: { color: '#8B5CF6', label: 'JPG' },\n jpeg: { color: '#8B5CF6', label: 'JPEG' },\n gif: { color: '#8B5CF6', label: 'GIF' },\n svg: { color: '#8B5CF6', label: 'SVG' },\n webp: { color: '#8B5CF6', label: 'WEBP' },\n json: { color: '#F59E0B', label: 'JSON' },\n xml: { color: '#F59E0B', label: 'XML' },\n txt: { color: '#64748B', label: 'TXT' },\n zip: { color: '#6366F1', label: 'ZIP' },\n gz: { color: '#6366F1', label: 'GZ' },\n py: { color: '#3B82F6', label: 'PY' },\n js: { color: '#EAB308', label: 'JS' },\n ts: { color: '#3B82F6', label: 'TS' },\n fasta: { color: '#EC4899', label: 'FASTA' },\n fastq: { color: '#EC4899', label: 'FASTQ' },\n bam: { color: '#EC4899', label: 'BAM' },\n vcf: { color: '#EC4899', label: 'VCF' },\n}\n\nfunction getFileType(filename: string): { color: string; label: string } {\n const ext = filename.split('.').pop()?.toLowerCase() || ''\n return fileTypeMap[ext] || { color: '#94A3B8', label: ext.toUpperCase() || 'FILE' }\n}\n\nfunction validateFiles(files: FileList | File[]): File[] {\n const validFiles: File[] = []\n\n for (const file of Array.from(files)) {\n if (props.maxSize && file.size > props.maxSize) {\n emit('error', `File \"${file.name}\" exceeds maximum size of ${maxSizeLabel.value}`)\n continue\n }\n\n if (props.accept) {\n const acceptedTypes = props.accept.split(',').map(t => t.trim().toLowerCase())\n const fileExt = '.' + file.name.split('.').pop()?.toLowerCase()\n const fileType = file.type.toLowerCase()\n\n const isAccepted = acceptedTypes.some(type => {\n if (type.startsWith('.')) {\n return fileExt === type\n }\n if (type.endsWith('/*')) {\n return fileType.startsWith(type.replace('/*', '/'))\n }\n return fileType === type\n })\n\n if (!isAccepted) {\n emit('error', `File \"${file.name}\" is not an accepted file type`)\n continue\n }\n }\n\n validFiles.push(file)\n }\n\n return validFiles\n}\n\nfunction handleFiles(files: FileList | File[], folder?: string) {\n const validFiles = validateFiles(files)\n if (validFiles.length === 0) return\n\n if (isFolder.value) {\n folderName.value = folder || extractFolderName(validFiles)\n selectedFiles.value = validFiles\n emit('upload', validFiles)\n } else if (!props.multiple) {\n selectedFiles.value = [validFiles[0]]\n emit('upload', [validFiles[0]])\n } else {\n selectedFiles.value = [...selectedFiles.value, ...validFiles]\n emit('upload', validFiles)\n }\n}\n\nfunction extractFolderName(files: File[]): string | null {\n if (files.length === 0) return null\n const firstPath = files[0].webkitRelativePath\n if (firstPath) {\n return firstPath.split('/')[0]\n }\n return null\n}\n\nfunction handleDrop(event: DragEvent) {\n event.preventDefault()\n isDragOver.value = false\n if (props.disabled) return\n\n if (isFolder.value && event.dataTransfer?.items) {\n handleFolderDrop(event.dataTransfer.items)\n } else if (event.dataTransfer?.files) {\n handleFiles(event.dataTransfer.files)\n }\n}\n\nasync function handleFolderDrop(items: DataTransferItemList) {\n const files: File[] = []\n let folderNameFromDrop: string | undefined\n\n for (const item of Array.from(items)) {\n const entry = item.webkitGetAsEntry?.()\n if (entry?.isDirectory) {\n folderNameFromDrop = entry.name\n await readDirectory(entry as FileSystemDirectoryEntry, files)\n } else if (entry?.isFile) {\n const file = await getFile(entry as FileSystemFileEntry)\n if (file) files.push(file)\n }\n }\n\n if (files.length > 0) {\n handleFiles(files, folderNameFromDrop)\n }\n}\n\nasync function readDirectory(directory: FileSystemDirectoryEntry, files: File[]): Promise<void> {\n const reader = directory.createReader()\n // readEntries returns at most 100 entries per call per spec — must loop\n let batch: FileSystemEntry[]\n do {\n batch = await new Promise<FileSystemEntry[]>((resolve, reject) => {\n reader.readEntries(resolve, reject)\n })\n for (const entry of batch) {\n if (entry.isFile) {\n const file = await getFile(entry as FileSystemFileEntry)\n if (file) files.push(file)\n } else if (entry.isDirectory) {\n await readDirectory(entry as FileSystemDirectoryEntry, files)\n }\n }\n } while (batch.length > 0)\n}\n\nfunction getFile(entry: FileSystemFileEntry): Promise<File | null> {\n return new Promise((resolve) => {\n entry.file(resolve, () => resolve(null))\n })\n}\n\nfunction handleDragOver(event: DragEvent) {\n event.preventDefault()\n if (!props.disabled) {\n isDragOver.value = true\n }\n}\n\nfunction handleDragLeave() {\n isDragOver.value = false\n}\n\nfunction handleInputChange(event: Event) {\n const target = event.target as HTMLInputElement\n if (target.files) {\n handleFiles(target.files)\n }\n target.value = ''\n}\n\nfunction openFilePicker() {\n if (!props.disabled) {\n inputRef.value?.click()\n }\n}\n\nfunction removeFile(index: number) {\n selectedFiles.value = selectedFiles.value.filter((_, i) => i !== index)\n if (isFolder.value && selectedFiles.value.length === 0) {\n folderName.value = null\n }\n}\n\nfunction clearAll() {\n selectedFiles.value = []\n folderName.value = null\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes >= 1024 * 1024) {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n }\n if (bytes >= 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`\n }\n return `${bytes} bytes`\n}\n</script>\n\n<template>\n <div class=\"mld-file-uploader\">\n <!-- Dropzone -->\n <div\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"disabled ? 'File upload disabled' : 'Click or drag files to upload'\"\n :aria-disabled=\"disabled\"\n :class=\"[\n 'mld-file-uploader__dropzone',\n `mld-file-uploader__dropzone--${size}`,\n isDragOver ? 'mld-file-uploader__dropzone--dragover' : '',\n disabled ? 'mld-file-uploader__dropzone--disabled' : '',\n selectedFiles.length > 0 ? 'mld-file-uploader__dropzone--has-files' : '',\n ]\"\n @click=\"openFilePicker\"\n @keydown.enter=\"openFilePicker\"\n @keydown.space.prevent=\"openFilePicker\"\n @drop=\"handleDrop\"\n @dragover=\"handleDragOver\"\n @dragleave=\"handleDragLeave\"\n >\n <!-- Animated border overlay -->\n <div class=\"mld-file-uploader__border\" aria-hidden=\"true\" />\n\n <!-- Glow effect on drag -->\n <div v-if=\"isDragOver\" class=\"mld-file-uploader__glow\" aria-hidden=\"true\" />\n\n <input\n ref=\"inputRef\"\n type=\"file\"\n :accept=\"accept\"\n :multiple=\"isFolder || multiple\"\n :disabled=\"disabled\"\n :webkitdirectory=\"isFolder || undefined\"\n class=\"mld-file-uploader__input\"\n @change=\"handleInputChange\"\n />\n\n <div class=\"mld-file-uploader__content\">\n <!-- Animated icon -->\n <div :class=\"['mld-file-uploader__icon-wrapper', `mld-file-uploader__icon-wrapper--${size}`]\">\n <div class=\"mld-file-uploader__icon-bg\" aria-hidden=\"true\" />\n <svg\n v-if=\"isFolder\"\n :class=\"['mld-file-uploader__icon', `mld-file-uploader__icon--${size}`]\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\" />\n </svg>\n <svg\n v-else\n :class=\"['mld-file-uploader__icon', `mld-file-uploader__icon--${size}`]\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242\" /><path d=\"M12 13v8\" /><path d=\"m8 17 4-4 4 4\" />\n </svg>\n </div>\n\n <p :class=\"['mld-file-uploader__text', `mld-file-uploader__text--${size}`]\">\n <span class=\"mld-file-uploader__highlight\">{{ isFolder ? 'Click to select folder' : 'Click to upload' }}</span>\n <span class=\"mld-file-uploader__text-secondary\"> or drag and drop</span>\n </p>\n\n <p :class=\"['mld-file-uploader__hint', `mld-file-uploader__hint--${size}`]\">\n <template v-if=\"isFolder\">\n Select a folder to upload all files within\n </template>\n <template v-else>\n {{ acceptLabel }}{{ maxSizeLabel ? ` (max ${maxSizeLabel})` : '' }}\n </template>\n </p>\n </div>\n </div>\n\n <!-- Folder summary display -->\n <div v-if=\"isFolder && folderName && selectedFiles.length > 0\" class=\"mld-file-uploader__folder-summary\">\n <div class=\"mld-file-uploader__folder-info\">\n <div class=\"mld-file-uploader__folder-icon-wrapper\">\n <svg class=\"mld-file-uploader__folder-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2\" />\n </svg>\n </div>\n <div class=\"mld-file-uploader__folder-details\">\n <span class=\"mld-file-uploader__folder-name\">{{ folderName }}</span>\n <span class=\"mld-file-uploader__folder-count\">{{ selectedFiles.length }} file{{ selectedFiles.length !== 1 ? 's' : '' }}</span>\n </div>\n </div>\n <button\n type=\"button\"\n aria-label=\"Clear folder\"\n class=\"mld-file-uploader__remove-btn\"\n @click.stop=\"clearAll\"\n >\n <svg class=\"mld-file-uploader__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- File list display -->\n <TransitionGroup\n v-if=\"!isFolder && selectedFiles.length > 0\"\n tag=\"ul\"\n name=\"mld-file-uploader-item\"\n class=\"mld-file-uploader__list\"\n >\n <li\n v-for=\"(file, index) in selectedFiles\"\n :key=\"`${file.name}-${file.size}-${index}`\"\n class=\"mld-file-uploader__file\"\n >\n <div class=\"mld-file-uploader__file-info\">\n <span\n class=\"mld-file-uploader__file-badge\"\n :style=\"{ '--badge-color': getFileType(file.name).color }\"\n >\n {{ getFileType(file.name).label }}\n </span>\n <div class=\"mld-file-uploader__file-meta\">\n <span class=\"mld-file-uploader__file-name\">{{ file.name }}</span>\n <span class=\"mld-file-uploader__file-size\">{{ formatFileSize(file.size) }}</span>\n </div>\n </div>\n <button\n type=\"button\"\n :aria-label=\"`Remove ${file.name}`\"\n class=\"mld-file-uploader__remove-btn\"\n @click.stop=\"removeFile(index)\"\n >\n <svg class=\"mld-file-uploader__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </li>\n </TransitionGroup>\n </div>\n</template>\n\n<style>\n@import '../styles/components/file-uploader.css';\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_normalizeClass","_toDisplayString","_Fragment","_createBlock","_TransitionGroup","_renderList","_withModifiers"],"mappings":"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"}
|