@templatical/media-library 0.44.2 → 0.45.0
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/{MediaEditModal-CO_AVnsB.js → MediaEditModal-Bo3nDMEl.js} +3 -11
- package/dist/cdn/chunks/{MediaEditModal-DTCjSFT_.js → MediaEditModal-Ct0DeGy4.js} +6 -14
- package/dist/cdn/chunks/{MediaEditModal-DTCjSFT_.js.map → MediaEditModal-Ct0DeGy4.js.map} +1 -1
- package/dist/cdn/chunks/{icons-6EkTPJx8.js → icons-CtGTlRRa.js} +14 -36
- package/dist/cdn/chunks/{icons-6EkTPJx8.js.map → icons-CtGTlRRa.js.map} +1 -1
- package/dist/cdn/chunks/{useFocusTrap-Cd2j4q7F.js → useFocusTrap-CHlDIBd_.js} +3 -9
- package/dist/cdn/chunks/{useFocusTrap-Cd2j4q7F.js.map → useFocusTrap-CHlDIBd_.js.map} +1 -1
- package/dist/cdn/media-library.js +9 -23
- package/dist/cdn/media-library.js.map +1 -1
- package/dist/templatical-media-library.js +8 -28
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-library.js","names":["_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_unref","_Fragment","_renderList","_createVNode","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_resolveComponent","_openBlock","_createElementBlock","_createElementVNode","_normalizeStyle","_unref","_withModifiers","_createVNode","_normalizeClass","_toDisplayString","_createCommentVNode","_withDirectives","_withKeys","_vModelText","Trash2","_Fragment","_renderList","_createBlock","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_createElementVNode","_normalizeStyle","_createVNode","_unref","_createTextVNode","_toDisplayString","_Fragment","_renderList","_createBlock","MediaFolderTreeNode","_withDirectives","_withKeys","_withModifiers","_vModelText","_createCommentVNode","_sfc_main","_openBlock","_createElementBlock","_normalizeStyle","_createElementVNode","_toDisplayString","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_openBlock","_createElementBlock","_unref","_normalizeClass","_Fragment","_renderList","_createElementVNode","_createVNode","_toDisplayString","_normalizeStyle","_createBlock","MediaFileIcon","_createCommentVNode","_withModifiers","_createTextVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_openBlock","_createBlock","_Teleport","_unref","_createVNode","_Transition","_withCtx","_createElementBlock","_normalizeClass","_withModifiers","_createElementVNode","_toDisplayString","_withDirectives","_vModelText","_createCommentVNode","_createTextVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_unref","_createTextVNode","_toDisplayString","_createCommentVNode","_Fragment","_renderList","_normalizeStyle","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_createVNode","MediaFileIcon","_createElementVNode","_toDisplayString","_Fragment","_createTextVNode","_unref","_createCommentVNode","_normalizeClass","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_openBlock","_createBlock","_Teleport","_unref","_createVNode","_Transition","_withCtx","_createElementBlock","_normalizeClass","_withModifiers","_createElementVNode","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_Fragment","_createElementVNode","_normalizeClass","_unref","_createVNode","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_Transition","_withCtx","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_unref","_createBlock","StorageProgressRing","_createCommentVNode","_createVNode","_Transition","_withCtx","MediaFolderTree","_normalizeStyle","MediaBreadcrumb","_Fragment","_renderList","MediaUploadZone","_createTextVNode","MediaGrid","MediaImportUrlModal","MediaReplaceModal","_withModifiers","_normalizeClass","MediaPreviewPanel","MediaMovePicker","_sfc_main","_openBlock","_createElementBlock","_createVNode","MediaLibraryChrome","_sfc_main","MediaLibrary","createCommentVNode","onBeforeUnmount","withKeys","computed","Teleport","defineComponent","openBlock","watch","resolveComponent","h","createTextVNode","createVNode","createBaseVNode","defineAsyncComponent","inject","Fragment","provide","renderList","withCtx","withDirectives","ChevronRight","Search","createApp","Pencil","unref","List","toDisplayString","Link","Copy","File","Star","createBlock","PanelLeft","normalizeClass","Folder","Upload","ref","RefreshCw","toRef","Grid2x2","Trash","Plus","toValue","X","LoaderCircle","normalizeStyle","Check","withModifiers","vModelText","Transition","useDropZone","useDebounceFn","MEDIA_LIMITS_KEY","POPOVER_TARGET_KEY","UI_THEME_KEY","useClipboard","useMediaCategories","UI_LOCALE_KEY","_plugin_vue_export_helper_default","useFileDialog","TRANSLATIONS_KEY","onClickOutside","useIntersectionObserver","useFocusTrap","useI18n","_hoisted_1$10","\"color\"","_hoisted_2$10","_hoisted_3$10","MediaBreadcrumb_default","__emit","props","__props","emit","breadcrumbPath","path","buildPath","folder","folderList","targetId","createElementBlock","_cache","t","index","_hoisted_1$9","_hoisted_2$9","_hoisted_3$9","_hoisted_4$8","_hoisted_5$7","_hoisted_6$6","_hoisted_7$4","_hoisted_8$4","_hoisted_9$4","MediaFolderTreeNode_default","isExpanded","isRenaming","renameValue","isCreatingSubfolder","subfolderName","confirmDelete","hasChildren","canCreateSubfolder","isActive","isDescendantActive","containsFolder","shouldExpand","folders","id","toggleExpand","startRename","confirmRename","trimmed","cancelRename","startCreateSubfolder","confirmCreateSubfolder","cancelCreateSubfolder","handleDeleteClick","onRowFocusOut","next","event","_component_MediaFolderTreeNode","$event","child","name","parentId","_hoisted_1$8","_hoisted_2$8","_hoisted_3$8","MediaFolderTree_default","isCreating","newFolderName","startCreateFolder","confirmCreateFolder","cancelCreate","MediaFileIcon_default","FILE_TYPE_MAP","DEFAULT_CONFIG","config","formatAbsoluteDate","parsed","iso","tag","locale","options","UNITS","formatFileSize","value","bytes","unit","_hoisted_1$7","_hoisted_2$7","_hoisted_3$7","_hoisted_4$7","_hoisted_5$6","_hoisted_6$5","_hoisted_7$3","_hoisted_8$3","_hoisted_9$3","_hoisted_10$3","_hoisted_11$2","_hoisted_12$2","_hoisted_13$2","_hoisted_14$1","_hoisted_15$1","_hoisted_16$1","_hoisted_17$1","_hoisted_18$1","_hoisted_19$1","_hoisted_20$1","_hoisted_21$1","_hoisted_22$1","_hoisted_23$1","_hoisted_24$1","_hoisted_25$1","_hoisted_26$1","_hoisted_27$1","_hoisted_28$1","MediaGrid_default","mimeOf","item","thumbOf","isSelectable","isAcceptedMimeType","showEdit","showReplace","handleItemClick","handleItemDblClick","handleItemKeydown","isSelected","actionChromeClass","uiLocale","sentinelRef","isIntersecting","formatDate","dateStr","i","isImageMimeType","_hoisted_1$6","_hoisted_2$6","_hoisted_3$6","_hoisted_4$6","_hoisted_5$5","_hoisted_6$4","_hoisted_7$2","_hoisted_8$2","_hoisted_9$2","_hoisted_10$2","MediaImportUrlModal_default","tplUiTheme","popoverTarget","urlValue","visible","handleImport","trimmedUrl","handleClose","dialogRef","trapActive","handleKeydown","_hoisted_1$5","_hoisted_2$5","_hoisted_3$5","_hoisted_4$5","MediaMovePicker_default","pickerRef","flattenFolders","result","depth","_hoisted_1$4","_hoisted_2$4","_hoisted_3$4","\"border\"","_hoisted_4$4","_hoisted_5$4","_hoisted_6$3","MediaPreviewPanel_default","buildFolderPath","newPath","currentPath","found","folderPath","mimeType","isImage","filename","_hoisted_1$3","_hoisted_2$3","_hoisted_3$3","_hoisted_4$3","_hoisted_5$3","_hoisted_6$2","_hoisted_7$1","_hoisted_8$1","_hoisted_9$1","_hoisted_10$1","_hoisted_11$1","_hoisted_12$1","_hoisted_13$1","MediaReplaceModal_default","fileInputRef","selectedFile","extension","parts","acceptPattern","hasUsage","handleFileChange","input","handleReplace","formatList","items","error","_hoisted_1$2","_hoisted_2$2","_hoisted_3$2","_hoisted_4$2","_hoisted_5$2","_hoisted_6$1","MediaUploadZone_default","CATEGORY_LABEL_KEYS","acceptedHint","labels","formats","restrictsMimeTypes","availableCategories","category","maxFileSize","format","rejectedNames","dropZoneRef","emitValidFiles","valid","rejected","file","fileList","isAcceptedFile","files","openFilePicker","allAcceptedInputString","onChange","isOverDropZone","_hoisted_1$1","_hoisted_2$1","_hoisted_3$1","_hoisted_4$1","_hoisted_5$1","StorageProgressRing_default","showTooltip","size","strokeWidth","radius","circumference","percentage","strokeDashoffset","progressColor","usedFormatted","limitFormatted","remainingBytes","remainingFormatted","tooltipText","\"stroke\"","treeFolders","nodes","roots","node","parent","useMediaLibraryUI","getTranslations","translations","layoutMode","showSidebar","searchInput","editingItem","showImportUrlModal","showMovePicker","selectedUrl","library","hasFrequentlyUsed","displayItems","hasUsedFiles","info","folderTree","categoryLabels","getCategoryLabel","show","debouncedSearch","handleSearchInput","handleUpload","handleSelect","handleCreateFolder","handleRenameFolder","folderId","handleDeleteFolder","handleEditItem","handleEditSave","cropData","mediaId","altText","handleImportFromUrl","url","handleMoveToFolder","handleReplaceItem","handleReplaceFile","resetUI","useMediaLibrary","currentFolderId","viewMode","searchQuery","categoryFilter","sortOption","isLoading","isUploading","hasMore","nextCursor","uploadProgress","selectedItems","previewItem","frequentlyUsedItems","deleteUsageInfo","showDeleteWarning","pendingDeleteIds","isImportingFromUrl","importFromUrlError","isReplacing","replaceError","showReplaceWarning","pendingReplaceItem","replaceUsageInfo","storageInfo","browseRequestId","notify","foldersProvider","provider","resolvedTemplateId","currentListParams","params","cursor","templateId","loadItems","requestId","page","loadMore","search","query","filterByCategory","sortBy","option","navigateToFolder","showFrequentlyUsed","loadFrequentlyUsed","uploadFile","create","media","uploadFiles","moveSelected","move","movedItems","targetFolderId","movedMap","updateFile","update","patch","updated","captureLocalAssets","captured","ids","dropLocalAssets","deleteSelected","providerDelete","asset","frequentlyUsed","checkUsageBeforeDelete","checkUsage","usage","cancelDelete","importFromUrl","importUrl","toggleSelection","clearSelection","selectItem","loadStorage","storage","loadFolders","foldersApi","createFolder","findFolderInTree","renameFolder","deleteFolder","deleteFn","checkUsageBeforeReplace","cancelReplace","applyReplaced","replaceFile","replace","replaceMediaDirectly","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_hoisted_29","_hoisted_30","_hoisted_31","_hoisted_32","_hoisted_33","_hoisted_34","_hoisted_35","_hoisted_36","_hoisted_37","MediaLibraryChrome_default","__expose","MediaEditModal","resolvedConfirmTestId","mediaLimits","ui","canCreate","canFolders","canImport","canDelete","canUpdate","canReplace","canFrequentlyUsed","canMove","canCreateFolder","canRenameFolder","canDeleteFolder","isInitialLoad","isConfirmable","selectedDeletableCount","count","entry","nestedDialogOpen","handleEscape","confirmSelection","handleCategoryChange","accept","args","MediaLibrary_default","popoverRoot","handleConfirm","\"width\"","\"height\"","localeModules","n","localesFromGlob","modules","supportedLocales","canonicalize","findSupportedLocale","canonical","s","getBaseLocale","resolveLocale","loadMediaTranslations","loader","appInstance","mediaLibraryRef","init","container","applyTheme","unmount","onReady","theme","TemplaticalMedia"],"sources":["../../src/components/media/MediaBreadcrumb.vue","../../src/components/media/MediaBreadcrumb.vue","../../src/components/media/MediaFolderTreeNode.vue","../../src/components/media/MediaFolderTreeNode.vue","../../src/components/media/MediaFolderTree.vue","../../src/components/media/MediaFolderTree.vue","../../src/components/media/MediaFileIcon.vue","../../src/components/media/MediaFileIcon.vue","../../src/utils/formatAbsoluteDate.ts","../../src/utils/formatFileSize.ts","../../src/components/media/MediaGrid.vue","../../src/components/media/MediaGrid.vue","../../src/components/media/MediaImportUrlModal.vue","../../src/components/media/MediaImportUrlModal.vue","../../src/components/media/MediaMovePicker.vue","../../src/components/media/MediaMovePicker.vue","../../src/components/media/MediaPreviewPanel.vue","../../src/components/media/MediaPreviewPanel.vue","../../src/components/media/MediaReplaceModal.vue","../../src/components/media/MediaReplaceModal.vue","../../src/utils/formatList.ts","../../src/components/media/MediaUploadZone.vue","../../src/components/media/MediaUploadZone.vue","../../src/components/media/StorageProgressRing.vue","../../src/components/media/StorageProgressRing.vue","../../src/utils/treeFolders.ts","../../src/composables/useMediaLibraryUI.ts","../../src/composable.ts","../../src/components/MediaLibraryChrome.vue","../../src/components/MediaLibraryChrome.vue","../../src/standalone/MediaLibrary.vue","../../src/standalone/MediaLibrary.vue","../../src/i18n/index.ts","../../src/standalone/visual.ts"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n}>();\n\nconst { t } = useI18n();\n\nconst breadcrumbPath = computed(() => {\n if (!props.currentFolderId) return [];\n const path: MediaFolderNode[] = [];\n buildPath(props.folders, props.currentFolderId, path);\n return path;\n});\n\nfunction buildPath(\n folderList: MediaFolderNode[],\n targetId: string,\n path: MediaFolderNode[],\n): boolean {\n for (const folder of folderList) {\n if (folder.id === targetId) {\n path.push(folder);\n return true;\n }\n if (buildPath(folder.children, targetId, path)) {\n path.unshift(folder);\n return true;\n }\n }\n return false;\n}\n</script>\n\n<template>\n <div\n v-if=\"breadcrumbPath.length > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-1 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <button\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', null)\"\n >\n {{ t.mediaLibrary.allFiles }}\n </button>\n <template v-for=\"(folder, index) in breadcrumbPath\" :key=\"folder.id\">\n <ChevronRight :size=\"12\" :stroke-width=\"2\" />\n <button\n v-if=\"index < breadcrumbPath.length - 1\"\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', folder.id)\"\n >\n {{ folder.name }}\n </button>\n <span v-else style=\"color: var(--tpl-text)\">{{ folder.name }}</span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n}>();\n\nconst { t } = useI18n();\n\nconst breadcrumbPath = computed(() => {\n if (!props.currentFolderId) return [];\n const path: MediaFolderNode[] = [];\n buildPath(props.folders, props.currentFolderId, path);\n return path;\n});\n\nfunction buildPath(\n folderList: MediaFolderNode[],\n targetId: string,\n path: MediaFolderNode[],\n): boolean {\n for (const folder of folderList) {\n if (folder.id === targetId) {\n path.push(folder);\n return true;\n }\n if (buildPath(folder.children, targetId, path)) {\n path.unshift(folder);\n return true;\n }\n }\n return false;\n}\n</script>\n\n<template>\n <div\n v-if=\"breadcrumbPath.length > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-1 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <button\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', null)\"\n >\n {{ t.mediaLibrary.allFiles }}\n </button>\n <template v-for=\"(folder, index) in breadcrumbPath\" :key=\"folder.id\">\n <ChevronRight :size=\"12\" :stroke-width=\"2\" />\n <button\n v-if=\"index < breadcrumbPath.length - 1\"\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', folder.id)\"\n >\n {{ folder.name }}\n </button>\n <span v-else style=\"color: var(--tpl-text)\">{{ folder.name }}</span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight, Folder, Pencil, Plus, Trash2 } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n folder: MediaFolderNode;\n currentFolderId: string | null;\n depth: number;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isExpanded = ref(false);\nconst isRenaming = ref(false);\nconst renameValue = ref(\"\");\nconst isCreatingSubfolder = ref(false);\nconst subfolderName = ref(\"\");\nconst confirmDelete = ref(false);\n\nconst MAX_DEPTH = 5;\n\nconst hasChildren = computed(() => props.folder.children.length > 0);\n\nconst canCreateSubfolder = computed(() => props.depth < MAX_DEPTH - 1);\n\nconst isActive = computed(() => props.currentFolderId === props.folder.id);\n\nconst isDescendantActive = computed(() => {\n if (!props.currentFolderId) return false;\n return containsFolder(props.folder.children, props.currentFolderId);\n});\n\nconst shouldExpand = computed(\n () => isExpanded.value || isDescendantActive.value,\n);\n\nfunction containsFolder(folders: MediaFolderNode[], id: string): boolean {\n for (const folder of folders) {\n if (folder.id === id) return true;\n if (containsFolder(folder.children, id)) return true;\n }\n return false;\n}\n\nfunction toggleExpand(): void {\n isExpanded.value = !isExpanded.value;\n}\n\nfunction startRename(): void {\n isRenaming.value = true;\n renameValue.value = props.folder.name;\n}\n\nfunction confirmRename(): void {\n const trimmed = renameValue.value.trim();\n if (trimmed && trimmed !== props.folder.name) {\n emit(\"renameFolder\", props.folder.id, trimmed);\n }\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction cancelRename(): void {\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction startCreateSubfolder(): void {\n isCreatingSubfolder.value = true;\n subfolderName.value = \"\";\n isExpanded.value = true;\n}\n\nfunction confirmCreateSubfolder(): void {\n if (subfolderName.value.trim()) {\n emit(\"createFolder\", subfolderName.value.trim(), props.folder.id);\n }\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction cancelCreateSubfolder(): void {\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction handleDeleteClick(): void {\n if (confirmDelete.value) {\n emit(\"deleteFolder\", props.folder.id);\n confirmDelete.value = false;\n return;\n }\n confirmDelete.value = true;\n}\n\nfunction onRowFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (next && (event.currentTarget as HTMLElement).contains(next)) {\n return;\n }\n confirmDelete.value = false;\n}\n</script>\n\n<template>\n <div>\n <!-- Folder row -->\n <div\n class=\"tpl:group tpl:flex tpl:w-full tpl:items-center tpl:gap-1 tpl:py-1.5 tpl:pr-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n :style=\"{\n paddingLeft: `${depth * 16 + 8}px`,\n backgroundColor: isActive ? 'var(--tpl-bg-active)' : 'transparent',\n color: isActive ? 'var(--tpl-primary)' : 'var(--tpl-text)',\n }\"\n @focusout=\"onRowFocusOut\"\n >\n <!-- Expand/collapse chevron -->\n <button\n v-if=\"hasChildren || isCreatingSubfolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-4 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :aria-expanded=\"shouldExpand\"\n :aria-label=\"\n shouldExpand\n ? t.mediaLibrary.collapseFolder\n : t.mediaLibrary.expandFolder\n \"\n @click.stop=\"toggleExpand\"\n >\n <ChevronRight\n class=\"tpl:transition-transform tpl:duration-150\"\n :class=\"{ 'tpl:rotate-90': shouldExpand }\"\n :size=\"10\"\n :stroke-width=\"2\"\n />\n </button>\n <span v-else class=\"tpl:size-4 tpl:shrink-0\" />\n\n <!-- Folder icon + name (clickable) -->\n <button\n type=\"button\"\n class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-1.5\"\n @click=\"emit('navigate', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <template v-if=\"!isRenaming\">\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n </template>\n </button>\n\n <!-- Inline rename input -->\n <input\n v-if=\"isRenaming\"\n v-model=\"renameValue\"\n type=\"text\"\n class=\"tpl:min-w-0 tpl:flex-1 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n autofocus\n @keydown.enter=\"confirmRename\"\n @keydown.escape.stop=\"cancelRename\"\n @blur=\"confirmRename\"\n @click.stop\n />\n\n <span\n v-if=\"!isRenaming\"\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-0.5 tpl:transition-opacity\"\n :class=\"\n isActive || confirmDelete\n ? 'tpl:opacity-100'\n : 'tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100'\n \"\n >\n <button\n v-if=\"canCreateSubfolder && canCreateFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.addSubfolder\"\n :aria-label=\"t.mediaLibrary.addSubfolder\"\n @click.stop=\"startCreateSubfolder\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canRenameFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.renameFolder\"\n :aria-label=\"t.mediaLibrary.renameFolder\"\n @click.stop=\"startRename\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canDeleteFolder\"\n type=\"button\"\n data-testid=\"media-folder-delete\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n :aria-label=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n @click.stop=\"handleDeleteClick\"\n >\n <Trash2\n :size=\"12\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-danger)\"\n />\n </button>\n </span>\n </div>\n\n <!-- Children (expanded) -->\n <div v-if=\"shouldExpand\">\n <MediaFolderTreeNode\n v-for=\"child in folder.children\"\n :key=\"child.id\"\n :folder=\"child\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"depth + 1\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"\n (name, parentId) => emit('createFolder', name, parentId)\n \"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- Subfolder creation input -->\n <div\n v-if=\"isCreatingSubfolder\"\n :style=\"{ paddingLeft: `${(depth + 1) * 16 + 8}px` }\"\n class=\"tpl:py-1.5 tpl:pr-2\"\n >\n <input\n v-model=\"subfolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.subfolderName\"\n autofocus\n @keydown.enter=\"confirmCreateSubfolder\"\n @keydown.escape.stop=\"cancelCreateSubfolder\"\n @blur=\"confirmCreateSubfolder\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight, Folder, Pencil, Plus, Trash2 } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n folder: MediaFolderNode;\n currentFolderId: string | null;\n depth: number;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isExpanded = ref(false);\nconst isRenaming = ref(false);\nconst renameValue = ref(\"\");\nconst isCreatingSubfolder = ref(false);\nconst subfolderName = ref(\"\");\nconst confirmDelete = ref(false);\n\nconst MAX_DEPTH = 5;\n\nconst hasChildren = computed(() => props.folder.children.length > 0);\n\nconst canCreateSubfolder = computed(() => props.depth < MAX_DEPTH - 1);\n\nconst isActive = computed(() => props.currentFolderId === props.folder.id);\n\nconst isDescendantActive = computed(() => {\n if (!props.currentFolderId) return false;\n return containsFolder(props.folder.children, props.currentFolderId);\n});\n\nconst shouldExpand = computed(\n () => isExpanded.value || isDescendantActive.value,\n);\n\nfunction containsFolder(folders: MediaFolderNode[], id: string): boolean {\n for (const folder of folders) {\n if (folder.id === id) return true;\n if (containsFolder(folder.children, id)) return true;\n }\n return false;\n}\n\nfunction toggleExpand(): void {\n isExpanded.value = !isExpanded.value;\n}\n\nfunction startRename(): void {\n isRenaming.value = true;\n renameValue.value = props.folder.name;\n}\n\nfunction confirmRename(): void {\n const trimmed = renameValue.value.trim();\n if (trimmed && trimmed !== props.folder.name) {\n emit(\"renameFolder\", props.folder.id, trimmed);\n }\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction cancelRename(): void {\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction startCreateSubfolder(): void {\n isCreatingSubfolder.value = true;\n subfolderName.value = \"\";\n isExpanded.value = true;\n}\n\nfunction confirmCreateSubfolder(): void {\n if (subfolderName.value.trim()) {\n emit(\"createFolder\", subfolderName.value.trim(), props.folder.id);\n }\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction cancelCreateSubfolder(): void {\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction handleDeleteClick(): void {\n if (confirmDelete.value) {\n emit(\"deleteFolder\", props.folder.id);\n confirmDelete.value = false;\n return;\n }\n confirmDelete.value = true;\n}\n\nfunction onRowFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (next && (event.currentTarget as HTMLElement).contains(next)) {\n return;\n }\n confirmDelete.value = false;\n}\n</script>\n\n<template>\n <div>\n <!-- Folder row -->\n <div\n class=\"tpl:group tpl:flex tpl:w-full tpl:items-center tpl:gap-1 tpl:py-1.5 tpl:pr-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n :style=\"{\n paddingLeft: `${depth * 16 + 8}px`,\n backgroundColor: isActive ? 'var(--tpl-bg-active)' : 'transparent',\n color: isActive ? 'var(--tpl-primary)' : 'var(--tpl-text)',\n }\"\n @focusout=\"onRowFocusOut\"\n >\n <!-- Expand/collapse chevron -->\n <button\n v-if=\"hasChildren || isCreatingSubfolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-4 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :aria-expanded=\"shouldExpand\"\n :aria-label=\"\n shouldExpand\n ? t.mediaLibrary.collapseFolder\n : t.mediaLibrary.expandFolder\n \"\n @click.stop=\"toggleExpand\"\n >\n <ChevronRight\n class=\"tpl:transition-transform tpl:duration-150\"\n :class=\"{ 'tpl:rotate-90': shouldExpand }\"\n :size=\"10\"\n :stroke-width=\"2\"\n />\n </button>\n <span v-else class=\"tpl:size-4 tpl:shrink-0\" />\n\n <!-- Folder icon + name (clickable) -->\n <button\n type=\"button\"\n class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-1.5\"\n @click=\"emit('navigate', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <template v-if=\"!isRenaming\">\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n </template>\n </button>\n\n <!-- Inline rename input -->\n <input\n v-if=\"isRenaming\"\n v-model=\"renameValue\"\n type=\"text\"\n class=\"tpl:min-w-0 tpl:flex-1 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n autofocus\n @keydown.enter=\"confirmRename\"\n @keydown.escape.stop=\"cancelRename\"\n @blur=\"confirmRename\"\n @click.stop\n />\n\n <span\n v-if=\"!isRenaming\"\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-0.5 tpl:transition-opacity\"\n :class=\"\n isActive || confirmDelete\n ? 'tpl:opacity-100'\n : 'tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100'\n \"\n >\n <button\n v-if=\"canCreateSubfolder && canCreateFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.addSubfolder\"\n :aria-label=\"t.mediaLibrary.addSubfolder\"\n @click.stop=\"startCreateSubfolder\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canRenameFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.renameFolder\"\n :aria-label=\"t.mediaLibrary.renameFolder\"\n @click.stop=\"startRename\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canDeleteFolder\"\n type=\"button\"\n data-testid=\"media-folder-delete\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n :aria-label=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n @click.stop=\"handleDeleteClick\"\n >\n <Trash2\n :size=\"12\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-danger)\"\n />\n </button>\n </span>\n </div>\n\n <!-- Children (expanded) -->\n <div v-if=\"shouldExpand\">\n <MediaFolderTreeNode\n v-for=\"child in folder.children\"\n :key=\"child.id\"\n :folder=\"child\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"depth + 1\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"\n (name, parentId) => emit('createFolder', name, parentId)\n \"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- Subfolder creation input -->\n <div\n v-if=\"isCreatingSubfolder\"\n :style=\"{ paddingLeft: `${(depth + 1) * 16 + 8}px` }\"\n class=\"tpl:py-1.5 tpl:pr-2\"\n >\n <input\n v-model=\"subfolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.subfolderName\"\n autofocus\n @keydown.enter=\"confirmCreateSubfolder\"\n @keydown.escape.stop=\"cancelCreateSubfolder\"\n @blur=\"confirmCreateSubfolder\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFolderTreeNode from \"./MediaFolderTreeNode.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaViewMode } from \"../../composable\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { File, Plus, Star } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n viewMode: MediaViewMode;\n hasFrequentlyUsed: boolean;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n (e: \"showFrequentlyUsed\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isCreating = ref(false);\nconst newFolderName = ref(\"\");\n\nfunction startCreateFolder(): void {\n isCreating.value = true;\n newFolderName.value = \"\";\n}\n\nfunction confirmCreateFolder(): void {\n if (newFolderName.value.trim()) {\n emit(\"createFolder\", newFolderName.value.trim());\n }\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n\nfunction cancelCreate(): void {\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n</script>\n\n<template>\n <div\n data-testid=\"media-folder-tree\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:overflow-y-auto\"\n >\n <!-- All Images root -->\n <button\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n backgroundColor:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('navigate', null)\"\n >\n <File :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.allFiles }}\n </button>\n\n <!-- Recursive folder tree -->\n <MediaFolderTreeNode\n v-for=\"folder in folders\"\n :key=\"folder.id\"\n :folder=\"folder\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"0\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"(name, parentId) => emit('createFolder', name, parentId)\"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- New folder input -->\n <div v-if=\"isCreating\" class=\"tpl:px-3 tpl:py-2\">\n <input\n v-model=\"newFolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.folderName\"\n autofocus\n @keydown.enter=\"confirmCreateFolder\"\n @keydown.escape.stop=\"cancelCreate\"\n @blur=\"confirmCreateFolder\"\n />\n </div>\n\n <!-- New folder button -->\n <button\n v-if=\"!isCreating && canCreateFolder\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"startCreateFolder\"\n >\n <Plus :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.newFolder }}\n </button>\n\n <!-- Spacer to push frequently used to bottom -->\n <div class=\"tpl:flex-1\" />\n\n <!-- Frequently Used -->\n <button\n v-if=\"hasFrequentlyUsed\"\n data-testid=\"media-frequently-used\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:border-t tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: 'var(--tpl-border)',\n backgroundColor:\n viewMode === 'frequently-used'\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'frequently-used'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('showFrequentlyUsed')\"\n >\n <Star :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.frequentlyUsed }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFolderTreeNode from \"./MediaFolderTreeNode.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaViewMode } from \"../../composable\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { File, Plus, Star } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n viewMode: MediaViewMode;\n hasFrequentlyUsed: boolean;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n (e: \"showFrequentlyUsed\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isCreating = ref(false);\nconst newFolderName = ref(\"\");\n\nfunction startCreateFolder(): void {\n isCreating.value = true;\n newFolderName.value = \"\";\n}\n\nfunction confirmCreateFolder(): void {\n if (newFolderName.value.trim()) {\n emit(\"createFolder\", newFolderName.value.trim());\n }\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n\nfunction cancelCreate(): void {\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n</script>\n\n<template>\n <div\n data-testid=\"media-folder-tree\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:overflow-y-auto\"\n >\n <!-- All Images root -->\n <button\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n backgroundColor:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('navigate', null)\"\n >\n <File :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.allFiles }}\n </button>\n\n <!-- Recursive folder tree -->\n <MediaFolderTreeNode\n v-for=\"folder in folders\"\n :key=\"folder.id\"\n :folder=\"folder\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"0\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"(name, parentId) => emit('createFolder', name, parentId)\"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- New folder input -->\n <div v-if=\"isCreating\" class=\"tpl:px-3 tpl:py-2\">\n <input\n v-model=\"newFolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.folderName\"\n autofocus\n @keydown.enter=\"confirmCreateFolder\"\n @keydown.escape.stop=\"cancelCreate\"\n @blur=\"confirmCreateFolder\"\n />\n </div>\n\n <!-- New folder button -->\n <button\n v-if=\"!isCreating && canCreateFolder\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"startCreateFolder\"\n >\n <Plus :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.newFolder }}\n </button>\n\n <!-- Spacer to push frequently used to bottom -->\n <div class=\"tpl:flex-1\" />\n\n <!-- Frequently Used -->\n <button\n v-if=\"hasFrequentlyUsed\"\n data-testid=\"media-frequently-used\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:border-t tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: 'var(--tpl-border)',\n backgroundColor:\n viewMode === 'frequently-used'\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'frequently-used'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('showFrequentlyUsed')\"\n >\n <Star :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.frequentlyUsed }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n mimeType: string;\n}>();\n\ninterface FileTypeConfig {\n label: string;\n color: string;\n bgColor: string;\n icon: \"document\" | \"video\" | \"audio\";\n}\n\nconst FILE_TYPE_MAP: Record<string, FileTypeConfig> = {\n \"application/pdf\": {\n label: \"PDF\",\n color: \"var(--tpl-danger)\",\n bgColor: \"var(--tpl-danger-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/msword\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-excel\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-powerpoint\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"text/csv\": {\n label: \"CSV\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"text/plain\": {\n label: \"TXT\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n },\n \"video/mp4\": {\n label: \"MP4\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/quicktime\": {\n label: \"MOV\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/webm\": {\n label: \"WEBM\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"audio/mpeg\": {\n label: \"MP3\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/wav\": {\n label: \"WAV\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/ogg\": {\n label: \"OGG\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n};\n\nconst DEFAULT_CONFIG: FileTypeConfig = {\n label: \"FILE\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n};\n\nconst config = computed(\n (): FileTypeConfig => FILE_TYPE_MAP[props.mimeType] ?? DEFAULT_CONFIG,\n);\n</script>\n\n<template>\n <div\n class=\"tpl:flex tpl:aspect-square tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-2\"\n :style=\"{ backgroundColor: config.bgColor }\"\n >\n <svg\n v-if=\"config.icon === 'document'\"\n width=\"60\"\n height=\"60\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14 2 14 8 20 8\" />\n <line x1=\"8\" y1=\"13\" x2=\"16\" y2=\"13\" />\n <line x1=\"8\" y1=\"17\" x2=\"12\" y2=\"17\" />\n </svg>\n\n <svg\n v-else-if=\"config.icon === 'video'\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\" />\n <polygon points=\"10,8 16,12 10,16\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n\n <svg\n v-else\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M9 18V5l12-2v13\" />\n <circle cx=\"6\" cy=\"18\" r=\"3\" />\n <circle cx=\"18\" cy=\"16\" r=\"3\" />\n </svg>\n\n <span\n class=\"tpl:text-xs tpl:font-bold tpl:tracking-wider\"\n :style=\"{ color: config.color }\"\n >\n {{ config.label }}\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n mimeType: string;\n}>();\n\ninterface FileTypeConfig {\n label: string;\n color: string;\n bgColor: string;\n icon: \"document\" | \"video\" | \"audio\";\n}\n\nconst FILE_TYPE_MAP: Record<string, FileTypeConfig> = {\n \"application/pdf\": {\n label: \"PDF\",\n color: \"var(--tpl-danger)\",\n bgColor: \"var(--tpl-danger-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/msword\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-excel\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-powerpoint\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"text/csv\": {\n label: \"CSV\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"text/plain\": {\n label: \"TXT\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n },\n \"video/mp4\": {\n label: \"MP4\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/quicktime\": {\n label: \"MOV\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/webm\": {\n label: \"WEBM\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"audio/mpeg\": {\n label: \"MP3\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/wav\": {\n label: \"WAV\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/ogg\": {\n label: \"OGG\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n};\n\nconst DEFAULT_CONFIG: FileTypeConfig = {\n label: \"FILE\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n};\n\nconst config = computed(\n (): FileTypeConfig => FILE_TYPE_MAP[props.mimeType] ?? DEFAULT_CONFIG,\n);\n</script>\n\n<template>\n <div\n class=\"tpl:flex tpl:aspect-square tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-2\"\n :style=\"{ backgroundColor: config.bgColor }\"\n >\n <svg\n v-if=\"config.icon === 'document'\"\n width=\"60\"\n height=\"60\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14 2 14 8 20 8\" />\n <line x1=\"8\" y1=\"13\" x2=\"16\" y2=\"13\" />\n <line x1=\"8\" y1=\"17\" x2=\"12\" y2=\"17\" />\n </svg>\n\n <svg\n v-else-if=\"config.icon === 'video'\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\" />\n <polygon points=\"10,8 16,12 10,16\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n\n <svg\n v-else\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M9 18V5l12-2v13\" />\n <circle cx=\"6\" cy=\"18\" r=\"3\" />\n <circle cx=\"18\" cy=\"16\" r=\"3\" />\n </svg>\n\n <span\n class=\"tpl:text-xs tpl:font-bold tpl:tracking-wider\"\n :style=\"{ color: config.color }\"\n >\n {{ config.label }}\n </span>\n </div>\n</template>\n","/**\n * An absolute date label in the **host's** locale.\n *\n * `toLocaleDateString(undefined, …)` formats in the *runtime's* locale — the\n * browser's or the OS's — not the one that reached this package as\n * `MediaLibraryModal`'s `locale` prop. So the library's captions read in the\n * browser's language while every string around them was translated from the\n * editor's `init({ locale })`.\n *\n * Two guards, both reachable from consumer input:\n *\n * - **A malformed tag makes `Intl` throw `RangeError`.** The locale originates\n * in the host's free-text config, so a bad value would take down the grid\n * for a caption. Falls back to the runtime locale — the behaviour before a\n * locale was threaded through at all — rather than to a hardcoded language.\n * A well-formed but *unregistered* tag (`\"zz\"`) is not caught: `Intl`\n * resolves its own fallback.\n * - **An unparseable date yields `\"\"`**, never a literal \"Invalid Date\".\n *\n * A deliberate twin of `formatAbsoluteDateTime` in `@templatical/editor`. They\n * are copies, not a shared module: this package cannot import from the editor\n * (the dependency runs the other way) and `@templatical/types` is the\n * dependency-free leaf rather than a home for display helpers. Behavioural\n * drift between them fails `tests/formatAbsoluteDate.test.ts`, which carries\n * the editor suite's own cases.\n */\nexport function formatAbsoluteDate(\n iso: string,\n locale: string | undefined,\n options?: Intl.DateTimeFormatOptions,\n): string {\n const parsed = new Date(iso);\n if (Number.isNaN(parsed.getTime())) return \"\";\n\n const tag = locale?.trim().replace(/_/g, \"-\") || undefined;\n\n try {\n return parsed.toLocaleDateString(tag, options);\n } catch {\n return parsed.toLocaleDateString(undefined, options);\n }\n}\n","const UNITS = [\"B\", \"KB\", \"MB\", \"GB\"];\n\n/** `10485760` → `\"10 MB\"`, `1572864` → `\"1.5 MB\"`: whole units drop the `.0`. */\nexport function formatFileSize(bytes: number): string {\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < UNITS.length - 1) {\n value /= 1024;\n unit += 1;\n }\n const rounded = Math.round(value * 10) / 10;\n return `${rounded} ${UNITS[unit]}`;\n}\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset, MediaCategory } from \"@templatical/types\";\nimport { useIntersectionObserver } from \"@vueuse/core\";\nimport { Check, File, LoaderCircle, Pencil, RefreshCw } from \"@lucide/vue\";\nimport { inject, ref } from \"vue\";\n\nconst props = defineProps<{\n items: MediaAsset[];\n selectedIds: Set<string>;\n isLoading: boolean;\n hasMore: boolean;\n accept?: MediaCategory[];\n layout?: \"grid\" | \"list\";\n searchQuery?: string;\n canUpdate?: boolean;\n canReplace?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", item: MediaAsset): void;\n (e: \"toggle\", id: string): void;\n (e: \"loadMore\"): void;\n (e: \"edit\", item: MediaAsset): void;\n (e: \"replace\", item: MediaAsset): void;\n (e: \"confirm\", item: MediaAsset): void;\n}>();\n\nconst { isAcceptedMimeType, isImageMimeType } = useMediaCategories();\n\nfunction mimeOf(item: MediaAsset): string {\n return item.mimeType ?? \"\";\n}\n\nfunction thumbOf(item: MediaAsset): string {\n return item.thumbnailUrl ?? item.url;\n}\n\nfunction isSelectable(item: MediaAsset): boolean {\n if (!props.accept || props.accept.length === 0) {\n return true;\n }\n\n return isAcceptedMimeType(mimeOf(item), props.accept);\n}\n\nfunction showEdit(item: MediaAsset): boolean {\n return props.canUpdate === true && item.canUpdate !== false;\n}\n\nfunction showReplace(item: MediaAsset): boolean {\n return props.canReplace === true && item.canUpdate !== false;\n}\n\nfunction handleItemClick(item: MediaAsset, event: MouseEvent): void {\n if (event.metaKey || event.ctrlKey) {\n emit(\"toggle\", item.id);\n return;\n }\n emit(\"select\", item);\n}\n\nfunction handleItemDblClick(item: MediaAsset): void {\n if (isSelectable(item)) {\n emit(\"confirm\", item);\n }\n}\n\nfunction handleItemKeydown(item: MediaAsset, event: KeyboardEvent): void {\n if (event.key !== \"Enter\" && event.key !== \" \") {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n emit(\"select\", item);\n}\n\nfunction isSelected(item: MediaAsset): boolean {\n return props.selectedIds.has(item.id);\n}\n\nfunction actionChromeClass(item: MediaAsset): string {\n return isSelected(item)\n ? \"tpl:opacity-100\"\n : \"tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100\";\n}\n\nconst { t } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst sentinelRef = ref<HTMLElement | null>(null);\n\nuseIntersectionObserver(\n sentinelRef,\n ([{ isIntersecting }]) => {\n if (isIntersecting && props.hasMore && !props.isLoading) {\n emit(\"loadMore\");\n }\n },\n { threshold: 0.1 },\n);\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n</script>\n\n<template>\n <div class=\"tpl:p-4\">\n <div\n v-if=\"isLoading && items.length === 0\"\n role=\"status\"\n aria-busy=\"true\"\n :aria-label=\"t.mediaLibrary.loading\"\n :class=\"\n layout === 'list'\n ? 'tpl:flex tpl:flex-col tpl:gap-1'\n : 'tpl:grid tpl:grid-cols-4 tpl:gap-3'\n \"\n >\n <div\n v-for=\"i in 8\"\n :key=\"i\"\n class=\"tpl-pulse tpl:rounded-lg\"\n :class=\"layout === 'list' ? 'tpl:h-12' : 'tpl:aspect-square'\"\n style=\"background-color: var(--tpl-bg-hover)\"\n aria-hidden=\"true\"\n />\n </div>\n\n <div\n v-else-if=\"items.length === 0\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:py-16\"\n >\n <File\n class=\"tpl:mb-3\"\n :size=\"40\"\n :stroke-width=\"1\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n searchQuery ? t.mediaLibrary.noSearchResults : t.mediaLibrary.noFiles\n }}\n </p>\n </div>\n\n <div\n v-else-if=\"layout !== 'list'\"\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:grid tpl:grid-cols-4 tpl:gap-3\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-item tpl:group tpl:relative tpl:overflow-hidden tpl:rounded-lg tpl:border-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n 'tpl:cursor-pointer',\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n isSelected(item) ? 'tpl-media-item--selected' : '',\n ]\"\n :style=\"{\n borderColor: isSelected(item) ? 'var(--tpl-primary)' : 'transparent',\n backgroundColor:\n !isSelectable(item) && !isSelected(item)\n ? 'var(--tpl-bg)'\n : 'var(--tpl-bg-hover)',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div class=\"tpl:aspect-square\">\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <MediaFileIcon v-else :mime-type=\"mimeOf(item)\" />\n </div>\n <div class=\"tpl:px-2 tpl:py-1.5\">\n <p\n class=\"tpl:truncate tpl:text-[10px] tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p\n class=\"tpl:flex tpl:justify-between tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <span v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</span>\n <span\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n {{ item.width }}×{{ item.height }}\n </span>\n </p>\n </div>\n <div\n class=\"tpl:absolute tpl:top-1.5 tpl:left-1.5 tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:absolute tpl:top-1.5 tpl:right-1.5 tpl:flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div\n v-else\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-list-item tpl:group tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-3 tpl:rounded-lg tpl:px-3 tpl:py-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n ]\"\n :style=\"{\n backgroundColor: isSelected(item)\n ? 'var(--tpl-bg-hover)'\n : 'transparent',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div\n class=\"tpl:size-10 tpl:shrink-0 tpl:overflow-hidden tpl:rounded\"\n style=\"background-color: var(--tpl-bg-hover)\"\n >\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <div v-else class=\"tpl-list-icon tpl:size-full\">\n <MediaFileIcon :mime-type=\"mimeOf(item)\" />\n </div>\n </div>\n\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p class=\"tpl:text-[10px]\" style=\"color: var(--tpl-text-muted)\">\n <template v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> · </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n ·\n {{ item.width }}×{{ item.height }}\n </template>\n </p>\n </div>\n\n <div\n class=\"tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:flex tpl:size-5 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div ref=\"sentinelRef\" class=\"tpl:h-4\" />\n\n <div\n v-if=\"isLoading && items.length > 0\"\n class=\"tpl:flex tpl:justify-center tpl:py-4\"\n >\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-media-item:not(.tpl-media-item--selected):hover {\n border-color: var(--tpl-border) !important;\n}\n\n.tpl-media-list-item:hover {\n background-color: var(--tpl-bg-hover) !important;\n}\n\n.tpl-list-icon {\n transform: scale(0.35);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset, MediaCategory } from \"@templatical/types\";\nimport { useIntersectionObserver } from \"@vueuse/core\";\nimport { Check, File, LoaderCircle, Pencil, RefreshCw } from \"@lucide/vue\";\nimport { inject, ref } from \"vue\";\n\nconst props = defineProps<{\n items: MediaAsset[];\n selectedIds: Set<string>;\n isLoading: boolean;\n hasMore: boolean;\n accept?: MediaCategory[];\n layout?: \"grid\" | \"list\";\n searchQuery?: string;\n canUpdate?: boolean;\n canReplace?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", item: MediaAsset): void;\n (e: \"toggle\", id: string): void;\n (e: \"loadMore\"): void;\n (e: \"edit\", item: MediaAsset): void;\n (e: \"replace\", item: MediaAsset): void;\n (e: \"confirm\", item: MediaAsset): void;\n}>();\n\nconst { isAcceptedMimeType, isImageMimeType } = useMediaCategories();\n\nfunction mimeOf(item: MediaAsset): string {\n return item.mimeType ?? \"\";\n}\n\nfunction thumbOf(item: MediaAsset): string {\n return item.thumbnailUrl ?? item.url;\n}\n\nfunction isSelectable(item: MediaAsset): boolean {\n if (!props.accept || props.accept.length === 0) {\n return true;\n }\n\n return isAcceptedMimeType(mimeOf(item), props.accept);\n}\n\nfunction showEdit(item: MediaAsset): boolean {\n return props.canUpdate === true && item.canUpdate !== false;\n}\n\nfunction showReplace(item: MediaAsset): boolean {\n return props.canReplace === true && item.canUpdate !== false;\n}\n\nfunction handleItemClick(item: MediaAsset, event: MouseEvent): void {\n if (event.metaKey || event.ctrlKey) {\n emit(\"toggle\", item.id);\n return;\n }\n emit(\"select\", item);\n}\n\nfunction handleItemDblClick(item: MediaAsset): void {\n if (isSelectable(item)) {\n emit(\"confirm\", item);\n }\n}\n\nfunction handleItemKeydown(item: MediaAsset, event: KeyboardEvent): void {\n if (event.key !== \"Enter\" && event.key !== \" \") {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n emit(\"select\", item);\n}\n\nfunction isSelected(item: MediaAsset): boolean {\n return props.selectedIds.has(item.id);\n}\n\nfunction actionChromeClass(item: MediaAsset): string {\n return isSelected(item)\n ? \"tpl:opacity-100\"\n : \"tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100\";\n}\n\nconst { t } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst sentinelRef = ref<HTMLElement | null>(null);\n\nuseIntersectionObserver(\n sentinelRef,\n ([{ isIntersecting }]) => {\n if (isIntersecting && props.hasMore && !props.isLoading) {\n emit(\"loadMore\");\n }\n },\n { threshold: 0.1 },\n);\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n</script>\n\n<template>\n <div class=\"tpl:p-4\">\n <div\n v-if=\"isLoading && items.length === 0\"\n role=\"status\"\n aria-busy=\"true\"\n :aria-label=\"t.mediaLibrary.loading\"\n :class=\"\n layout === 'list'\n ? 'tpl:flex tpl:flex-col tpl:gap-1'\n : 'tpl:grid tpl:grid-cols-4 tpl:gap-3'\n \"\n >\n <div\n v-for=\"i in 8\"\n :key=\"i\"\n class=\"tpl-pulse tpl:rounded-lg\"\n :class=\"layout === 'list' ? 'tpl:h-12' : 'tpl:aspect-square'\"\n style=\"background-color: var(--tpl-bg-hover)\"\n aria-hidden=\"true\"\n />\n </div>\n\n <div\n v-else-if=\"items.length === 0\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:py-16\"\n >\n <File\n class=\"tpl:mb-3\"\n :size=\"40\"\n :stroke-width=\"1\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n searchQuery ? t.mediaLibrary.noSearchResults : t.mediaLibrary.noFiles\n }}\n </p>\n </div>\n\n <div\n v-else-if=\"layout !== 'list'\"\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:grid tpl:grid-cols-4 tpl:gap-3\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-item tpl:group tpl:relative tpl:overflow-hidden tpl:rounded-lg tpl:border-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n 'tpl:cursor-pointer',\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n isSelected(item) ? 'tpl-media-item--selected' : '',\n ]\"\n :style=\"{\n borderColor: isSelected(item) ? 'var(--tpl-primary)' : 'transparent',\n backgroundColor:\n !isSelectable(item) && !isSelected(item)\n ? 'var(--tpl-bg)'\n : 'var(--tpl-bg-hover)',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div class=\"tpl:aspect-square\">\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <MediaFileIcon v-else :mime-type=\"mimeOf(item)\" />\n </div>\n <div class=\"tpl:px-2 tpl:py-1.5\">\n <p\n class=\"tpl:truncate tpl:text-[10px] tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p\n class=\"tpl:flex tpl:justify-between tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <span v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</span>\n <span\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n {{ item.width }}×{{ item.height }}\n </span>\n </p>\n </div>\n <div\n class=\"tpl:absolute tpl:top-1.5 tpl:left-1.5 tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:absolute tpl:top-1.5 tpl:right-1.5 tpl:flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div\n v-else\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-list-item tpl:group tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-3 tpl:rounded-lg tpl:px-3 tpl:py-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n ]\"\n :style=\"{\n backgroundColor: isSelected(item)\n ? 'var(--tpl-bg-hover)'\n : 'transparent',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div\n class=\"tpl:size-10 tpl:shrink-0 tpl:overflow-hidden tpl:rounded\"\n style=\"background-color: var(--tpl-bg-hover)\"\n >\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <div v-else class=\"tpl-list-icon tpl:size-full\">\n <MediaFileIcon :mime-type=\"mimeOf(item)\" />\n </div>\n </div>\n\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p class=\"tpl:text-[10px]\" style=\"color: var(--tpl-text-muted)\">\n <template v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> · </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n ·\n {{ item.width }}×{{ item.height }}\n </template>\n </p>\n </div>\n\n <div\n class=\"tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:flex tpl:size-5 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div ref=\"sentinelRef\" class=\"tpl:h-4\" />\n\n <div\n v-if=\"isLoading && items.length > 0\"\n class=\"tpl:flex tpl:justify-center tpl:py-4\"\n >\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-media-item:not(.tpl-media-item--selected):hover {\n border-color: var(--tpl-border) !important;\n}\n\n.tpl-media-list-item:hover {\n background-color: var(--tpl-bg-hover) !important;\n}\n\n.tpl-list-icon {\n transform: scale(0.35);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport { LoaderCircle } from \"@lucide/vue\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n isImporting: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"import\", url: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst urlValue = ref(\"\");\n\nwatch(\n () => props.visible,\n (visible) => {\n if (visible) {\n urlValue.value = \"\";\n }\n },\n);\n\nfunction handleImport(): void {\n const trimmedUrl = urlValue.value.trim();\n if (!trimmedUrl || props.isImporting) {\n return;\n }\n\n emit(\"import\", trimmedUrl);\n}\n\nfunction handleClose(): void {\n if (!props.isImporting) {\n emit(\"close\");\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleImport();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"handleClose\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-import-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-import-title\"\n class=\"tpl:mb-4 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.importFromUrl }}\n </h3>\n\n <!-- URL Input -->\n <div class=\"tpl:mb-3\">\n <input\n v-model=\"urlValue\"\n type=\"url\"\n :aria-label=\"t.mediaLibrary.importFromUrl\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-import-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.importUrlPlaceholder\"\n :disabled=\"isImporting\"\n autofocus\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-import-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isImporting\"\n :class=\"{\n 'tpl:cursor-not-allowed tpl:opacity-50': isImporting,\n }\"\n @click=\"handleClose\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!urlValue.trim() || isImporting\"\n @click=\"handleImport\"\n >\n <span\n v-if=\"isImporting\"\n class=\"tpl:flex tpl:items-center tpl:gap-1.5\"\n >\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.mediaLibrary.importing }}\n </span>\n <span v-else>\n {{ t.mediaLibrary.import }}\n </span>\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport { LoaderCircle } from \"@lucide/vue\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n isImporting: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"import\", url: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst urlValue = ref(\"\");\n\nwatch(\n () => props.visible,\n (visible) => {\n if (visible) {\n urlValue.value = \"\";\n }\n },\n);\n\nfunction handleImport(): void {\n const trimmedUrl = urlValue.value.trim();\n if (!trimmedUrl || props.isImporting) {\n return;\n }\n\n emit(\"import\", trimmedUrl);\n}\n\nfunction handleClose(): void {\n if (!props.isImporting) {\n emit(\"close\");\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleImport();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"handleClose\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-import-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-import-title\"\n class=\"tpl:mb-4 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.importFromUrl }}\n </h3>\n\n <!-- URL Input -->\n <div class=\"tpl:mb-3\">\n <input\n v-model=\"urlValue\"\n type=\"url\"\n :aria-label=\"t.mediaLibrary.importFromUrl\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-import-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.importUrlPlaceholder\"\n :disabled=\"isImporting\"\n autofocus\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-import-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isImporting\"\n :class=\"{\n 'tpl:cursor-not-allowed tpl:opacity-50': isImporting,\n }\"\n @click=\"handleClose\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!urlValue.trim() || isImporting\"\n @click=\"handleImport\"\n >\n <span\n v-if=\"isImporting\"\n class=\"tpl:flex tpl:items-center tpl:gap-1.5\"\n >\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.mediaLibrary.importing }}\n </span>\n <span v-else>\n {{ t.mediaLibrary.import }}\n </span>\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { File, Folder } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", folderId: string | null): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst pickerRef = ref<HTMLDivElement | null>(null);\n\ninterface FlatFolder {\n id: string;\n name: string;\n depth: number;\n}\n\nfunction flattenFolders(\n folders: MediaFolderNode[],\n depth: number = 0,\n): FlatFolder[] {\n const result: FlatFolder[] = [];\n for (const folder of folders) {\n result.push({ id: folder.id, name: folder.name, depth });\n if (folder.children.length) {\n result.push(...flattenFolders(folder.children, depth + 1));\n }\n }\n return result;\n}\n\nonClickOutside(pickerRef, () => {\n emit(\"close\");\n});\n</script>\n\n<template>\n <div\n ref=\"pickerRef\"\n data-testid=\"media-move-picker\"\n class=\"tpl:absolute tpl:bottom-full tpl:left-0 tpl:z-10 tpl:mb-2 tpl:w-56 tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:shadow-lg\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg-elevated);\n \"\n >\n <div class=\"tpl:max-h-56 tpl:overflow-y-auto tpl:py-1\">\n <button\n v-if=\"currentFolderId !== null\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n style=\"color: var(--tpl-text)\"\n @click=\"emit('select', null)\"\n >\n <File class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.moveToRoot }}\n </button>\n <button\n v-for=\"folder in flattenFolders(folders)\"\n :key=\"folder.id\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:py-1.5 tpl:pr-3 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n :style=\"{\n paddingLeft: `${folder.depth * 16 + 12}px`,\n color:\n folder.id === currentFolderId\n ? 'var(--tpl-text-dim)'\n : 'var(--tpl-text)',\n opacity: folder.id === currentFolderId ? 0.5 : 1,\n }\"\n :disabled=\"folder.id === currentFolderId\"\n @click=\"emit('select', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n <span\n v-if=\"folder.id === currentFolderId\"\n class=\"tpl:shrink-0\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ t.mediaLibrary.currentFolder }}\n </span>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { File, Folder } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", folderId: string | null): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst pickerRef = ref<HTMLDivElement | null>(null);\n\ninterface FlatFolder {\n id: string;\n name: string;\n depth: number;\n}\n\nfunction flattenFolders(\n folders: MediaFolderNode[],\n depth: number = 0,\n): FlatFolder[] {\n const result: FlatFolder[] = [];\n for (const folder of folders) {\n result.push({ id: folder.id, name: folder.name, depth });\n if (folder.children.length) {\n result.push(...flattenFolders(folder.children, depth + 1));\n }\n }\n return result;\n}\n\nonClickOutside(pickerRef, () => {\n emit(\"close\");\n});\n</script>\n\n<template>\n <div\n ref=\"pickerRef\"\n data-testid=\"media-move-picker\"\n class=\"tpl:absolute tpl:bottom-full tpl:left-0 tpl:z-10 tpl:mb-2 tpl:w-56 tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:shadow-lg\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg-elevated);\n \"\n >\n <div class=\"tpl:max-h-56 tpl:overflow-y-auto tpl:py-1\">\n <button\n v-if=\"currentFolderId !== null\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n style=\"color: var(--tpl-text)\"\n @click=\"emit('select', null)\"\n >\n <File class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.moveToRoot }}\n </button>\n <button\n v-for=\"folder in flattenFolders(folders)\"\n :key=\"folder.id\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:py-1.5 tpl:pr-3 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n :style=\"{\n paddingLeft: `${folder.depth * 16 + 12}px`,\n color:\n folder.id === currentFolderId\n ? 'var(--tpl-text-dim)'\n : 'var(--tpl-text)',\n opacity: folder.id === currentFolderId ? 0.5 : 1,\n }\"\n :disabled=\"folder.id === currentFolderId\"\n @click=\"emit('select', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n <span\n v-if=\"folder.id === currentFolderId\"\n class=\"tpl:shrink-0\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ t.mediaLibrary.currentFolder }}\n </span>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { Folder } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\n\nconst props = defineProps<{\n item: MediaAsset;\n folders?: MediaFolderNode[];\n}>();\n\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst { isImageMimeType } = useMediaCategories();\n\nfunction buildFolderPath(\n folderList: MediaFolderNode[],\n targetId: string,\n currentPath: string[] = [],\n): string[] | null {\n for (const folder of folderList) {\n const newPath = [...currentPath, folder.name];\n if (folder.id === targetId) {\n return newPath;\n }\n if (folder.children.length) {\n const found = buildFolderPath(folder.children, targetId, newPath);\n if (found) return found;\n }\n }\n return null;\n}\n\nconst folderPath = computed(() => {\n if (!props.item.folderId || !props.folders) {\n return null;\n }\n const path = buildFolderPath(props.folders, props.item.folderId);\n return path ? path.join(\"/\") : null;\n});\n\nconst mimeType = computed(() => props.item.mimeType ?? \"\");\nconst isImage = computed(() => isImageMimeType(mimeType.value));\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nconst filename = computed(() => props.item.filename || props.item.url);\n</script>\n\n<template>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <img\n v-if=\"isImage\"\n :src=\"item.url\"\n :alt=\"item.alt || filename\"\n class=\"tpl:size-10 tpl:shrink-0 tpl:rounded tpl:object-cover\"\n style=\"border: 1px solid var(--tpl-border)\"\n />\n <div\n v-else\n class=\"tpl:flex tpl:size-10 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:overflow-hidden tpl:rounded\"\n style=\"border: 1px solid var(--tpl-border)\"\n >\n <MediaFileIcon :mime-type=\"mimeType\" class=\"tpl-preview-icon\" />\n </div>\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ filename }}\n </p>\n <p\n class=\"tpl:mt-0.5 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <template v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> · </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template v-if=\"isImage && item.width && item.height\">\n · {{ item.width }}×{{ item.height }}px\n </template>\n <template v-if=\"folderPath\">\n ·\n <Folder class=\"tpl:mb-px tpl:inline\" :size=\"9\" :stroke-width=\"2\" />\n {{ folderPath }}\n </template>\n </p>\n <p\n v-if=\"isImage\"\n class=\"tpl:mt-0.5 tpl:truncate tpl:text-[10px] tpl:italic\"\n :class=\"{ 'tpl:invisible': !item.alt }\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ item.alt || \" \" }}\n </p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-preview-icon {\n transform: scale(0.45);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { Folder } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\n\nconst props = defineProps<{\n item: MediaAsset;\n folders?: MediaFolderNode[];\n}>();\n\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst { isImageMimeType } = useMediaCategories();\n\nfunction buildFolderPath(\n folderList: MediaFolderNode[],\n targetId: string,\n currentPath: string[] = [],\n): string[] | null {\n for (const folder of folderList) {\n const newPath = [...currentPath, folder.name];\n if (folder.id === targetId) {\n return newPath;\n }\n if (folder.children.length) {\n const found = buildFolderPath(folder.children, targetId, newPath);\n if (found) return found;\n }\n }\n return null;\n}\n\nconst folderPath = computed(() => {\n if (!props.item.folderId || !props.folders) {\n return null;\n }\n const path = buildFolderPath(props.folders, props.item.folderId);\n return path ? path.join(\"/\") : null;\n});\n\nconst mimeType = computed(() => props.item.mimeType ?? \"\");\nconst isImage = computed(() => isImageMimeType(mimeType.value));\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nconst filename = computed(() => props.item.filename || props.item.url);\n</script>\n\n<template>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <img\n v-if=\"isImage\"\n :src=\"item.url\"\n :alt=\"item.alt || filename\"\n class=\"tpl:size-10 tpl:shrink-0 tpl:rounded tpl:object-cover\"\n style=\"border: 1px solid var(--tpl-border)\"\n />\n <div\n v-else\n class=\"tpl:flex tpl:size-10 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:overflow-hidden tpl:rounded\"\n style=\"border: 1px solid var(--tpl-border)\"\n >\n <MediaFileIcon :mime-type=\"mimeType\" class=\"tpl-preview-icon\" />\n </div>\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ filename }}\n </p>\n <p\n class=\"tpl:mt-0.5 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <template v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> · </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template v-if=\"isImage && item.width && item.height\">\n · {{ item.width }}×{{ item.height }}px\n </template>\n <template v-if=\"folderPath\">\n ·\n <Folder class=\"tpl:mb-px tpl:inline\" :size=\"9\" :stroke-width=\"2\" />\n {{ folderPath }}\n </template>\n </p>\n <p\n v-if=\"isImage\"\n class=\"tpl:mt-0.5 tpl:truncate tpl:text-[10px] tpl:italic\"\n :class=\"{ 'tpl:invisible': !item.alt }\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ item.alt || \" \" }}\n </p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-preview-icon {\n transform: scale(0.45);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport type { MediaAsset, MediaUsageInfo } from \"@templatical/types\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n item: MediaAsset | null;\n usageInfo: MediaUsageInfo | null;\n isReplacing: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"replace\", file: File): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst fileInputRef = ref<HTMLInputElement | null>(null);\nconst selectedFile = ref<File | null>(null);\n\nconst extension = computed(() => {\n if (!props.item) {\n return \"\";\n }\n\n const parts = (props.item.filename ?? \"\").split(\".\");\n return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : \"\";\n});\n\nconst acceptPattern = computed(() => {\n return extension.value ? `.${extension.value}` : \"*\";\n});\n\nconst hasUsage = computed(() => {\n return (props.usageInfo?.templateCount ?? 0) > 0;\n});\n\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) {\n selectedFile.value = null;\n if (fileInputRef.value) {\n fileInputRef.value.value = \"\";\n }\n }\n },\n);\n\nfunction handleFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files && input.files.length > 0) {\n selectedFile.value = input.files[0];\n }\n}\n\nfunction handleReplace(): void {\n if (selectedFile.value) {\n emit(\"replace\", selectedFile.value);\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible && props.item !== null);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Escape\") {\n emit(\"close\");\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible && item\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"emit('close')\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-replace-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-replace-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.replaceWarningTitle }}\n </h3>\n\n <!-- Warning message -->\n <p class=\"tpl:mb-2 tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n t.mediaLibrary.replaceWarningMessage.replace(\n \"{extension}\",\n `.${extension}`,\n )\n }}\n </p>\n\n <!-- Usage warning -->\n <p\n v-if=\"hasUsage\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{\n t.mediaLibrary.replaceWarningUsageNote.replace(\n \"{count}\",\n usageInfo!.templateCount.toString(),\n )\n }}\n </p>\n\n <!-- Current file info -->\n <div\n class=\"tpl:mb-3 tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename }}\n </p>\n </div>\n\n <!-- File input -->\n <div class=\"tpl:mb-4\">\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.replaceSelectFile }}\n </label>\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :accept=\"acceptPattern\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-replace-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n @change=\"handleFileChange\"\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-replace-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isReplacing\"\n @click=\"emit('close')\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!selectedFile || isReplacing\"\n @click=\"handleReplace\"\n >\n {{\n isReplacing ? t.mediaLibrary.replacing : t.mediaLibrary.replace\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport type { MediaAsset, MediaUsageInfo } from \"@templatical/types\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n item: MediaAsset | null;\n usageInfo: MediaUsageInfo | null;\n isReplacing: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"replace\", file: File): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst fileInputRef = ref<HTMLInputElement | null>(null);\nconst selectedFile = ref<File | null>(null);\n\nconst extension = computed(() => {\n if (!props.item) {\n return \"\";\n }\n\n const parts = (props.item.filename ?? \"\").split(\".\");\n return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : \"\";\n});\n\nconst acceptPattern = computed(() => {\n return extension.value ? `.${extension.value}` : \"*\";\n});\n\nconst hasUsage = computed(() => {\n return (props.usageInfo?.templateCount ?? 0) > 0;\n});\n\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) {\n selectedFile.value = null;\n if (fileInputRef.value) {\n fileInputRef.value.value = \"\";\n }\n }\n },\n);\n\nfunction handleFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files && input.files.length > 0) {\n selectedFile.value = input.files[0];\n }\n}\n\nfunction handleReplace(): void {\n if (selectedFile.value) {\n emit(\"replace\", selectedFile.value);\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible && props.item !== null);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Escape\") {\n emit(\"close\");\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible && item\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"emit('close')\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-replace-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-replace-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.replaceWarningTitle }}\n </h3>\n\n <!-- Warning message -->\n <p class=\"tpl:mb-2 tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n t.mediaLibrary.replaceWarningMessage.replace(\n \"{extension}\",\n `.${extension}`,\n )\n }}\n </p>\n\n <!-- Usage warning -->\n <p\n v-if=\"hasUsage\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{\n t.mediaLibrary.replaceWarningUsageNote.replace(\n \"{count}\",\n usageInfo!.templateCount.toString(),\n )\n }}\n </p>\n\n <!-- Current file info -->\n <div\n class=\"tpl:mb-3 tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename }}\n </p>\n </div>\n\n <!-- File input -->\n <div class=\"tpl:mb-4\">\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.replaceSelectFile }}\n </label>\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :accept=\"acceptPattern\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-replace-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n @change=\"handleFileChange\"\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-replace-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isReplacing\"\n @click=\"emit('close')\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!selectedFile || isReplacing\"\n @click=\"handleReplace\"\n >\n {{\n isReplacing ? t.mediaLibrary.replacing : t.mediaLibrary.replace\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","/**\n * Joins labels the way the UI locale writes a list (`、` in Japanese, `, `\n * elsewhere). `locale` is free-text consumer config, so a malformed tag falls\n * back to the runtime locale instead of throwing mid-render.\n */\nexport function formatList(items: string[], locale?: string): string {\n // `unit` would be the natural type, but its Japanese form separates with\n // spaces; narrow `conjunction` gives `、` there and `A, B, C` in English.\n const options: Intl.ListFormatOptions = {\n style: \"narrow\",\n type: \"conjunction\",\n };\n try {\n return new Intl.ListFormat(locale, options).format(items);\n } catch (error) {\n if (!(error instanceof RangeError)) {\n throw error;\n }\n return new Intl.ListFormat(undefined, options).format(items);\n }\n}\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport { useDropZone, useFileDialog } from \"@vueuse/core\";\nimport { LoaderCircle, Upload } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport type { MediaCategory } from \"../../types\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { formatList } from \"../../utils/formatList\";\n\ndefineProps<{\n isUploading: boolean;\n uploadProgress: { current: number; total: number } | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"upload\", files: File[]): void;\n}>();\n\nconst { t, format } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst {\n allAcceptedInputString,\n restrictsMimeTypes,\n availableCategories,\n maxFileSize,\n isAcceptedFile,\n} = useMediaCategories();\n\nconst CATEGORY_LABEL_KEYS: Record<MediaCategory, string> = {\n images: \"filterImages\",\n documents: \"filterDocuments\",\n videos: \"filterVideos\",\n audio: \"filterAudio\",\n};\n\n// Read from the provider's limits, never a fixed string: the hint is the only\n// place an author learns what the library will refuse.\nconst acceptedHint = computed(() => {\n const labels = t.mediaLibrary as Record<string, string>;\n const formats = restrictsMimeTypes.value\n ? formatList(\n availableCategories.value.map(\n (category) => labels[CATEGORY_LABEL_KEYS[category]],\n ),\n uiLocale?.value,\n )\n : t.mediaLibrary.acceptedAnyType;\n if (!Number.isFinite(maxFileSize.value)) {\n return formats;\n }\n return format(t.mediaLibrary.acceptedFormatsMaxSize, {\n formats,\n size: formatFileSize(maxFileSize.value),\n });\n});\n\nconst rejectedNames = ref<string[]>([]);\n\nconst dropZoneRef = ref<HTMLElement>();\n\nfunction emitValidFiles(fileList: File[] | FileList): void {\n const valid: File[] = [];\n const rejected: string[] = [];\n for (const file of Array.from(fileList)) {\n if (isAcceptedFile(file)) {\n valid.push(file);\n } else {\n rejected.push(file.name);\n }\n }\n rejectedNames.value = rejected;\n if (valid.length) {\n emit(\"upload\", valid);\n }\n}\n\nconst { isOverDropZone } = useDropZone(dropZoneRef, {\n onDrop: (files) => {\n if (files?.length) {\n emitValidFiles(files);\n }\n },\n});\n\nconst { open: openFilePicker, onChange } = useFileDialog({\n accept: allAcceptedInputString.value,\n multiple: true,\n});\n\nonChange((fileList) => {\n if (fileList?.length) {\n emitValidFiles(fileList);\n }\n});\n</script>\n\n<template>\n <button\n ref=\"dropZoneRef\"\n type=\"button\"\n data-testid=\"media-upload-zone\"\n class=\"tpl-upload-zone tpl:flex tpl:w-full tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:justify-center tpl:rounded-lg tpl:border-2 tpl:border-dashed tpl:p-5 tpl:text-center tpl:transition-all tpl:duration-150\"\n :class=\"isOverDropZone ? 'tpl-upload-zone-active' : ''\"\n :aria-label=\"t.mediaLibrary.dropOrClick\"\n :aria-describedby=\"isUploading ? undefined : 'tpl-media-upload-hint'\"\n :aria-busy=\"isUploading\"\n style=\"\n border-color: var(--tpl-border-light);\n background-color: var(--tpl-bg);\n \"\n @click=\"openFilePicker()\"\n >\n <div v-if=\"isUploading\" class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n <span class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">{{\n uploadProgress && uploadProgress.total > 1\n ? format(t.mediaLibrary.uploadingProgress, {\n current: uploadProgress.current,\n total: uploadProgress.total,\n })\n : t.mediaLibrary.uploading\n }}</span>\n </div>\n <template v-else>\n <Upload\n class=\"tpl:mb-2\"\n :size=\"24\"\n :stroke-width=\"1.5\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{ t.mediaLibrary.dropOrClick }}\n </p>\n <p\n id=\"tpl-media-upload-hint\"\n data-testid=\"media-upload-hint\"\n class=\"tpl:mt-1 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ acceptedHint }}\n </p>\n </template>\n </button>\n <p\n v-if=\"rejectedNames.length\"\n data-testid=\"media-upload-rejected\"\n role=\"alert\"\n class=\"tpl:mt-1.5 tpl:text-xs tpl:break-words\"\n style=\"color: var(--tpl-danger)\"\n >\n {{\n format(t.mediaLibrary.uploadRejected, {\n files: rejectedNames.join(\", \"),\n })\n }}\n </p>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport { useDropZone, useFileDialog } from \"@vueuse/core\";\nimport { LoaderCircle, Upload } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport type { MediaCategory } from \"../../types\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { formatList } from \"../../utils/formatList\";\n\ndefineProps<{\n isUploading: boolean;\n uploadProgress: { current: number; total: number } | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"upload\", files: File[]): void;\n}>();\n\nconst { t, format } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst {\n allAcceptedInputString,\n restrictsMimeTypes,\n availableCategories,\n maxFileSize,\n isAcceptedFile,\n} = useMediaCategories();\n\nconst CATEGORY_LABEL_KEYS: Record<MediaCategory, string> = {\n images: \"filterImages\",\n documents: \"filterDocuments\",\n videos: \"filterVideos\",\n audio: \"filterAudio\",\n};\n\n// Read from the provider's limits, never a fixed string: the hint is the only\n// place an author learns what the library will refuse.\nconst acceptedHint = computed(() => {\n const labels = t.mediaLibrary as Record<string, string>;\n const formats = restrictsMimeTypes.value\n ? formatList(\n availableCategories.value.map(\n (category) => labels[CATEGORY_LABEL_KEYS[category]],\n ),\n uiLocale?.value,\n )\n : t.mediaLibrary.acceptedAnyType;\n if (!Number.isFinite(maxFileSize.value)) {\n return formats;\n }\n return format(t.mediaLibrary.acceptedFormatsMaxSize, {\n formats,\n size: formatFileSize(maxFileSize.value),\n });\n});\n\nconst rejectedNames = ref<string[]>([]);\n\nconst dropZoneRef = ref<HTMLElement>();\n\nfunction emitValidFiles(fileList: File[] | FileList): void {\n const valid: File[] = [];\n const rejected: string[] = [];\n for (const file of Array.from(fileList)) {\n if (isAcceptedFile(file)) {\n valid.push(file);\n } else {\n rejected.push(file.name);\n }\n }\n rejectedNames.value = rejected;\n if (valid.length) {\n emit(\"upload\", valid);\n }\n}\n\nconst { isOverDropZone } = useDropZone(dropZoneRef, {\n onDrop: (files) => {\n if (files?.length) {\n emitValidFiles(files);\n }\n },\n});\n\nconst { open: openFilePicker, onChange } = useFileDialog({\n accept: allAcceptedInputString.value,\n multiple: true,\n});\n\nonChange((fileList) => {\n if (fileList?.length) {\n emitValidFiles(fileList);\n }\n});\n</script>\n\n<template>\n <button\n ref=\"dropZoneRef\"\n type=\"button\"\n data-testid=\"media-upload-zone\"\n class=\"tpl-upload-zone tpl:flex tpl:w-full tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:justify-center tpl:rounded-lg tpl:border-2 tpl:border-dashed tpl:p-5 tpl:text-center tpl:transition-all tpl:duration-150\"\n :class=\"isOverDropZone ? 'tpl-upload-zone-active' : ''\"\n :aria-label=\"t.mediaLibrary.dropOrClick\"\n :aria-describedby=\"isUploading ? undefined : 'tpl-media-upload-hint'\"\n :aria-busy=\"isUploading\"\n style=\"\n border-color: var(--tpl-border-light);\n background-color: var(--tpl-bg);\n \"\n @click=\"openFilePicker()\"\n >\n <div v-if=\"isUploading\" class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n <span class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">{{\n uploadProgress && uploadProgress.total > 1\n ? format(t.mediaLibrary.uploadingProgress, {\n current: uploadProgress.current,\n total: uploadProgress.total,\n })\n : t.mediaLibrary.uploading\n }}</span>\n </div>\n <template v-else>\n <Upload\n class=\"tpl:mb-2\"\n :size=\"24\"\n :stroke-width=\"1.5\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{ t.mediaLibrary.dropOrClick }}\n </p>\n <p\n id=\"tpl-media-upload-hint\"\n data-testid=\"media-upload-hint\"\n class=\"tpl:mt-1 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ acceptedHint }}\n </p>\n </template>\n </button>\n <p\n v-if=\"rejectedNames.length\"\n data-testid=\"media-upload-rejected\"\n role=\"alert\"\n class=\"tpl:mt-1.5 tpl:text-xs tpl:break-words\"\n style=\"color: var(--tpl-danger)\"\n >\n {{\n format(t.mediaLibrary.uploadRejected, {\n files: rejectedNames.join(\", \"),\n })\n }}\n </p>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n usedBytes: number;\n limitBytes: number;\n size?: number;\n}>();\n\nconst { t, format } = useI18n();\nconst showTooltip = ref(false);\n\nconst size = computed(() => props.size ?? 24);\nconst strokeWidth = computed(() => Math.max(2, size.value / 8));\nconst radius = computed(() => (size.value - strokeWidth.value) / 2);\nconst circumference = computed(() => 2 * Math.PI * radius.value);\n\nconst percentage = computed(() => {\n if (props.limitBytes <= 0) return 0;\n return Math.min(100, (props.usedBytes / props.limitBytes) * 100);\n});\n\nconst strokeDashoffset = computed(() => {\n return circumference.value - (percentage.value / 100) * circumference.value;\n});\n\nconst progressColor = computed(() => {\n if (percentage.value >= 95) {\n return \"var(--tpl-danger)\";\n }\n if (percentage.value >= 75) {\n return \"var(--tpl-warning)\";\n }\n return \"var(--tpl-primary)\";\n});\n\nconst usedFormatted = computed(() => formatFileSize(props.usedBytes));\nconst limitFormatted = computed(() => formatFileSize(props.limitBytes));\nconst remainingBytes = computed(() =>\n Math.max(0, props.limitBytes - props.usedBytes),\n);\nconst remainingFormatted = computed(() => formatFileSize(remainingBytes.value));\n\nconst tooltipText = computed(() =>\n format(t.mediaLibrary.storageTooltip, {\n used: usedFormatted.value,\n total: limitFormatted.value,\n remaining: remainingFormatted.value,\n }),\n);\n</script>\n\n<template>\n <div\n data-testid=\"media-storage-ring\"\n class=\"tpl:relative tpl:inline-flex tpl:cursor-help tpl:items-center tpl:justify-center\"\n tabindex=\"0\"\n :aria-label=\"tooltipText\"\n @mouseenter=\"showTooltip = true\"\n @mouseleave=\"showTooltip = false\"\n @focus=\"showTooltip = true\"\n @blur=\"showTooltip = false\"\n >\n <svg\n :width=\"size\"\n :height=\"size\"\n class=\"tpl:-rotate-90\"\n :viewBox=\"`0 0 ${size} ${size}`\"\n >\n <!-- Background circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n style=\"stroke: var(--tpl-border)\"\n />\n <!-- Progress circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n :stroke=\"progressColor\"\n stroke-linecap=\"round\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"strokeDashoffset\"\n class=\"tpl:transition-all tpl:duration-300 tpl:ease-out\"\n />\n </svg>\n\n <!-- Tooltip -->\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0 tpl:translate-y-1\"\n enter-to-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-to-class=\"tpl:opacity-0 tpl:translate-y-1\"\n >\n <div\n v-if=\"showTooltip\"\n class=\"tpl:absolute tpl:top-full tpl:left-1/2 tpl:z-50 tpl:mt-2 tpl:-translate-x-1/2 tpl:rounded-md tpl:px-2.5 tpl:py-1.5 tpl:text-xs tpl:whitespace-nowrap tpl:shadow-lg\"\n style=\"background-color: var(--tpl-text); color: var(--tpl-bg-elevated)\"\n >\n {{ tooltipText }}\n <div\n class=\"tpl:absolute tpl:-top-1 tpl:left-1/2 tpl:size-2 tpl:-translate-x-1/2 tpl:rotate-45\"\n style=\"background-color: var(--tpl-text)\"\n />\n </div>\n </Transition>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n usedBytes: number;\n limitBytes: number;\n size?: number;\n}>();\n\nconst { t, format } = useI18n();\nconst showTooltip = ref(false);\n\nconst size = computed(() => props.size ?? 24);\nconst strokeWidth = computed(() => Math.max(2, size.value / 8));\nconst radius = computed(() => (size.value - strokeWidth.value) / 2);\nconst circumference = computed(() => 2 * Math.PI * radius.value);\n\nconst percentage = computed(() => {\n if (props.limitBytes <= 0) return 0;\n return Math.min(100, (props.usedBytes / props.limitBytes) * 100);\n});\n\nconst strokeDashoffset = computed(() => {\n return circumference.value - (percentage.value / 100) * circumference.value;\n});\n\nconst progressColor = computed(() => {\n if (percentage.value >= 95) {\n return \"var(--tpl-danger)\";\n }\n if (percentage.value >= 75) {\n return \"var(--tpl-warning)\";\n }\n return \"var(--tpl-primary)\";\n});\n\nconst usedFormatted = computed(() => formatFileSize(props.usedBytes));\nconst limitFormatted = computed(() => formatFileSize(props.limitBytes));\nconst remainingBytes = computed(() =>\n Math.max(0, props.limitBytes - props.usedBytes),\n);\nconst remainingFormatted = computed(() => formatFileSize(remainingBytes.value));\n\nconst tooltipText = computed(() =>\n format(t.mediaLibrary.storageTooltip, {\n used: usedFormatted.value,\n total: limitFormatted.value,\n remaining: remainingFormatted.value,\n }),\n);\n</script>\n\n<template>\n <div\n data-testid=\"media-storage-ring\"\n class=\"tpl:relative tpl:inline-flex tpl:cursor-help tpl:items-center tpl:justify-center\"\n tabindex=\"0\"\n :aria-label=\"tooltipText\"\n @mouseenter=\"showTooltip = true\"\n @mouseleave=\"showTooltip = false\"\n @focus=\"showTooltip = true\"\n @blur=\"showTooltip = false\"\n >\n <svg\n :width=\"size\"\n :height=\"size\"\n class=\"tpl:-rotate-90\"\n :viewBox=\"`0 0 ${size} ${size}`\"\n >\n <!-- Background circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n style=\"stroke: var(--tpl-border)\"\n />\n <!-- Progress circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n :stroke=\"progressColor\"\n stroke-linecap=\"round\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"strokeDashoffset\"\n class=\"tpl:transition-all tpl:duration-300 tpl:ease-out\"\n />\n </svg>\n\n <!-- Tooltip -->\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0 tpl:translate-y-1\"\n enter-to-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-to-class=\"tpl:opacity-0 tpl:translate-y-1\"\n >\n <div\n v-if=\"showTooltip\"\n class=\"tpl:absolute tpl:top-full tpl:left-1/2 tpl:z-50 tpl:mt-2 tpl:-translate-x-1/2 tpl:rounded-md tpl:px-2.5 tpl:py-1.5 tpl:text-xs tpl:whitespace-nowrap tpl:shadow-lg\"\n style=\"background-color: var(--tpl-text); color: var(--tpl-bg-elevated)\"\n >\n {{ tooltipText }}\n <div\n class=\"tpl:absolute tpl:-top-1 tpl:left-1/2 tpl:size-2 tpl:-translate-x-1/2 tpl:rotate-45\"\n style=\"background-color: var(--tpl-text)\"\n />\n </div>\n </Transition>\n </div>\n</template>\n","import type { MediaFolder } from \"@templatical/types\";\n\n/**\n * A folder plus the children derived from {@link MediaFolder.parentId}.\n *\n * {@link MediaFoldersProvider.list} is flat; the tree is a UI concern so a\n * store never has to return nested nodes.\n */\nexport interface MediaFolderNode extends MediaFolder {\n children: MediaFolderNode[];\n}\n\n/**\n * Group a flat folder list into a tree via `parentId`.\n *\n * A `parentId` that is null, omitted, or not in the list is a root — a\n * dangling reference must still render rather than vanish.\n */\nexport function treeFolders(folders: MediaFolder[]): MediaFolderNode[] {\n const nodes = new Map<string, MediaFolderNode>();\n for (const folder of folders) {\n nodes.set(folder.id, { ...folder, children: [] });\n }\n\n const roots: MediaFolderNode[] = [];\n for (const node of nodes.values()) {\n const parentId = node.parentId ?? null;\n const parent = parentId ? nodes.get(parentId) : undefined;\n if (parent) {\n parent.children.push(node);\n } else {\n roots.push(node);\n }\n }\n return roots;\n}\n","import type {\n CropData,\n MediaEditSaveResult,\n} from \"../components/media/MediaEditModal.vue\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { useMediaLibrary } from \"../composable\";\nimport { treeFolders, type MediaFolderNode } from \"../utils/treeFolders\";\n\ntype UseMediaLibraryReturn = ReturnType<typeof useMediaLibrary>;\nimport { useClipboard, useDebounceFn } from \"@vueuse/core\";\nimport { computed, ref, watch, type ComputedRef, type Ref } from \"vue\";\n\nexport interface UseMediaLibraryUIOptions {\n library: UseMediaLibraryReturn;\n translations:\n | { mediaLibrary: Record<string, string> }\n | Ref<{ mediaLibrary: Record<string, string> }>;\n}\n\nexport interface UseMediaLibraryUIReturn {\n layoutMode: Ref<\"grid\" | \"list\">;\n showSidebar: Ref<boolean>;\n searchInput: Ref<string>;\n editingItem: Ref<MediaAsset | null>;\n showImportUrlModal: Ref<boolean>;\n showMovePicker: Ref<boolean>;\n\n selectedUrl: ComputedRef<string | null>;\n hasFrequentlyUsed: ComputedRef<boolean>;\n displayItems: ComputedRef<MediaAsset[]>;\n hasUsedFiles: ComputedRef<boolean>;\n folderTree: ComputedRef<MediaFolderNode[]>;\n\n copy: (text: string) => Promise<void>;\n copied: Ref<boolean>;\n\n getCategoryLabel: (category: string) => string;\n\n handleSearchInput: (value: string) => void;\n handleUpload: (files: File[]) => Promise<void>;\n handleSelect: (item: MediaAsset) => void;\n handleCreateFolder: (name: string, parentId?: string | null) => Promise<void>;\n handleRenameFolder: (folderId: string, name: string) => Promise<void>;\n handleDeleteFolder: (folderId: string) => Promise<void>;\n handleEditItem: (item: MediaAsset) => void;\n handleEditSave: (\n mediaId: string,\n filename: string,\n altText?: string,\n cropData?: CropData,\n ) => Promise<MediaEditSaveResult>;\n handleImportFromUrl: (url: string) => Promise<void>;\n handleMoveToFolder: (folderId: string | null) => Promise<void>;\n handleDeleteClick: () => Promise<void>;\n handleReplaceItem: (item: MediaAsset) => void;\n handleReplaceFile: (file: File) => Promise<void>;\n\n resetUI: () => void;\n}\n\nexport function useMediaLibraryUI(\n options: UseMediaLibraryUIOptions,\n): UseMediaLibraryUIReturn {\n const { library, translations } = options;\n\n function getTranslations() {\n if (\"value\" in translations && typeof translations.value === \"object\") {\n return (translations as Ref<{ mediaLibrary: Record<string, string> }>)\n .value;\n }\n return translations as { mediaLibrary: Record<string, string> };\n }\n\n const layoutMode = ref<\"grid\" | \"list\">(\"grid\");\n const showSidebar = ref(false);\n const searchInput = ref(\"\");\n const editingItem = ref<MediaAsset | null>(null);\n const showImportUrlModal = ref(false);\n const showMovePicker = ref(false);\n\n const selectedUrl = computed(() => library.previewItem.value?.url ?? null);\n\n const hasFrequentlyUsed = computed(() => {\n return library.frequentlyUsedItems.value.length > 0;\n });\n\n const displayItems = computed(() => {\n if (library.viewMode.value === \"frequently-used\") {\n return library.frequentlyUsedItems.value;\n }\n return library.items.value;\n });\n\n const hasUsedFiles = computed(() => {\n return Object.values(library.deleteUsageInfo.value).some(\n (info) => info.templateCount > 0,\n );\n });\n\n const folderTree = computed(() => treeFolders(library.folders.value));\n\n const categoryLabels: Record<string, () => string> = {\n images: () => getTranslations().mediaLibrary.filterImages,\n documents: () => getTranslations().mediaLibrary.filterDocuments,\n videos: () => getTranslations().mediaLibrary.filterVideos,\n audio: () => getTranslations().mediaLibrary.filterAudio,\n };\n\n function getCategoryLabel(category: string): string {\n return categoryLabels[category]?.() ?? category;\n }\n\n watch(showSidebar, (show) => {\n if (show) {\n library.loadFolders();\n }\n });\n\n const debouncedSearch = useDebounceFn((value: string) => {\n library.search(value);\n }, 300);\n\n function handleSearchInput(value: string): void {\n searchInput.value = value;\n debouncedSearch(value);\n }\n\n const { copy, copied } = useClipboard({ copiedDuring: 2000, legacy: true });\n\n async function handleUpload(files: File[]): Promise<void> {\n await library.uploadFiles(files);\n }\n\n function handleSelect(item: MediaAsset): void {\n library.selectItem(item);\n }\n\n async function handleCreateFolder(\n name: string,\n parentId?: string | null,\n ): Promise<void> {\n await library.createFolder(name, parentId);\n }\n\n async function handleRenameFolder(\n folderId: string,\n name: string,\n ): Promise<void> {\n await library.renameFolder(folderId, name);\n }\n\n async function handleDeleteFolder(folderId: string): Promise<void> {\n await library.deleteFolder(folderId);\n }\n\n function handleEditItem(item: MediaAsset): void {\n editingItem.value = item;\n }\n\n async function handleEditSave(\n mediaId: string,\n filename: string,\n altText?: string,\n cropData?: CropData,\n ): Promise<MediaEditSaveResult> {\n // A failed replace stops here: saving the metadata anyway would close the\n // dialog over an image that never changed.\n if (cropData) {\n const replaced = await library.replaceMediaDirectly(\n mediaId,\n cropData.file,\n );\n if (!replaced) {\n return \"failed\";\n }\n }\n const updated = await library.updateFile(mediaId, filename, altText);\n if (!updated) {\n return cropData ? \"replaced\" : \"failed\";\n }\n editingItem.value = null;\n return \"saved\";\n }\n\n async function handleImportFromUrl(url: string): Promise<void> {\n const result = await library.importFromUrl(url);\n if (result) {\n showImportUrlModal.value = false;\n }\n }\n\n async function handleMoveToFolder(folderId: string | null): Promise<void> {\n showMovePicker.value = false;\n await library.moveSelected(folderId);\n }\n\n async function handleDeleteClick(): Promise<void> {\n await library.checkUsageBeforeDelete();\n }\n\n function handleReplaceItem(item: MediaAsset): void {\n library.checkUsageBeforeReplace(item);\n }\n\n async function handleReplaceFile(file: File): Promise<void> {\n await library.replaceFile(file);\n }\n\n function resetUI(): void {\n library.clearSelection();\n library.cancelDelete();\n library.cancelReplace();\n searchInput.value = \"\";\n // Assign, don't `search(\"\")`: that would list on close. The next open\n // already calls `loadItems()` with whatever `searchQuery` holds.\n library.searchQuery.value = \"\";\n library.categoryFilter.value = null;\n library.sortOption.value = \"newest\";\n library.viewMode.value = \"files\";\n editingItem.value = null;\n showImportUrlModal.value = false;\n }\n\n return {\n layoutMode,\n showSidebar,\n searchInput,\n editingItem,\n showImportUrlModal,\n showMovePicker,\n selectedUrl,\n hasFrequentlyUsed,\n displayItems,\n hasUsedFiles,\n folderTree,\n copy,\n copied,\n getCategoryLabel,\n handleSearchInput,\n handleUpload,\n handleSelect,\n handleCreateFolder,\n handleRenameFolder,\n handleDeleteFolder,\n handleEditItem,\n handleEditSave,\n handleImportFromUrl,\n handleMoveToFolder,\n handleDeleteClick,\n handleReplaceItem,\n handleReplaceFile,\n resetUI,\n };\n}\n","import type {\n MediaAsset,\n MediaAssetPatch,\n MediaCategory,\n MediaCreateInput,\n MediaFolder,\n MediaFoldersProvider,\n MediaFolderInput,\n MediaListParams,\n MediaProvider,\n MediaStorageInfo,\n MediaUsageInfo,\n} from \"@templatical/types\";\nimport { ref, toValue, type MaybeRefOrGetter, type Ref } from \"vue\";\n\nexport type MediaViewMode = \"files\" | \"frequently-used\";\n\nexport interface UseMediaLibraryOptions {\n /**\n * Storage backend. The composable is transport-agnostic and never talks\n * to a network itself.\n */\n provider: MediaProvider;\n onError?: (error: Error) => void;\n /**\n * Forwarded on `list` / `create` / `importFromUrl` when a template is\n * loaded. Read at call time — a ref that fills after setup must still\n * reach the store.\n */\n templateId?: MaybeRefOrGetter<string | undefined>;\n}\n\n/**\n * Reactive state over a {@link MediaProvider}.\n *\n * Owns the listing, selection, preview, sequential upload loop and the\n * delete-usage flow. Chrome that should hide for a `false` method is the\n * modal's job; this composable simply does not call a disabled method.\n */\nexport function useMediaLibrary(options: UseMediaLibraryOptions) {\n const { provider } = options;\n\n const items: Ref<MediaAsset[]> = ref([]);\n const folders: Ref<MediaFolder[]> = ref([]);\n const currentFolderId: Ref<string | null> = ref(null);\n const viewMode: Ref<MediaViewMode> = ref(\"files\");\n const searchQuery: Ref<string> = ref(\"\");\n const categoryFilter: Ref<MediaCategory | null> = ref(null);\n const sortOption: Ref<string> = ref(\"newest\");\n const isLoading = ref(false);\n const isUploading = ref(false);\n const hasMore = ref(false);\n const nextCursor: Ref<string | null> = ref(null);\n const uploadProgress: Ref<{ current: number; total: number } | null> =\n ref(null);\n const selectedItems: Ref<Set<string>> = ref(new Set());\n const previewItem: Ref<MediaAsset | null> = ref(null);\n const frequentlyUsedItems: Ref<MediaAsset[]> = ref([]);\n const deleteUsageInfo: Ref<Record<string, MediaUsageInfo>> = ref({});\n const showDeleteWarning = ref(false);\n const pendingDeleteIds: Ref<string[]> = ref([]);\n const isImportingFromUrl = ref(false);\n const importFromUrlError: Ref<string | null> = ref(null);\n const isReplacing = ref(false);\n const replaceError: Ref<string | null> = ref(null);\n const showReplaceWarning = ref(false);\n const pendingReplaceItem: Ref<MediaAsset | null> = ref(null);\n const replaceUsageInfo: Ref<MediaUsageInfo | null> = ref(null);\n const storageInfo: Ref<MediaStorageInfo | null> = ref(null);\n\n // Monotonic token so an out-of-order list/loadMore response (folder\n // switch / search change before the previous request settled) cannot\n // overwrite the current view with stale data.\n let browseRequestId = 0;\n\n /**\n * A handler that throws must not turn a completed write into a rejected\n * one — the UI would report a failure for an asset that was stored.\n */\n function notify(run: () => void): void {\n try {\n run();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function foldersProvider(): MediaFoldersProvider | null {\n return provider.folders === false ? null : provider.folders;\n }\n\n /**\n * `sort` is absent: {@link MediaListParams} has no sort field.\n * `sortOption` stays as local UI state; changing it reloads the current\n * filters without inventing an order the store did not advertise.\n */\n function resolvedTemplateId(): string | undefined {\n return toValue(options.templateId);\n }\n\n function currentListParams(cursor?: string): MediaListParams {\n const params: MediaListParams = {};\n if (searchQuery.value) {\n params.search = searchQuery.value;\n } else if (currentFolderId.value != null) {\n params.folderId = currentFolderId.value;\n }\n if (categoryFilter.value) {\n params.category = categoryFilter.value;\n }\n if (cursor) {\n params.cursor = cursor;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n params.templateId = templateId;\n }\n return params;\n }\n\n async function loadItems(): Promise<void> {\n const requestId = ++browseRequestId;\n isLoading.value = true;\n const params = currentListParams();\n try {\n const page = await provider.list(params);\n if (requestId !== browseRequestId) return;\n items.value = page.items;\n nextCursor.value = page.nextCursor ?? null;\n hasMore.value = !!page.nextCursor;\n } catch (error) {\n if (requestId !== browseRequestId) return;\n options.onError?.(error as Error);\n } finally {\n if (requestId === browseRequestId) {\n isLoading.value = false;\n }\n }\n }\n\n async function loadMore(): Promise<void> {\n if (!hasMore.value || !nextCursor.value || isLoading.value) return;\n\n const requestId = ++browseRequestId;\n isLoading.value = true;\n const params = currentListParams(nextCursor.value);\n try {\n const page = await provider.list(params);\n if (requestId !== browseRequestId) return;\n items.value = [...items.value, ...page.items];\n nextCursor.value = page.nextCursor ?? null;\n hasMore.value = !!page.nextCursor;\n } catch (error) {\n if (requestId !== browseRequestId) return;\n options.onError?.(error as Error);\n } finally {\n if (requestId === browseRequestId) {\n isLoading.value = false;\n }\n }\n }\n\n async function search(query: string): Promise<void> {\n searchQuery.value = query;\n await loadItems();\n }\n\n async function filterByCategory(\n category: MediaCategory | null,\n ): Promise<void> {\n categoryFilter.value = category;\n await loadItems();\n }\n\n async function sortBy(option: string): Promise<void> {\n sortOption.value = option;\n await loadItems();\n }\n\n async function navigateToFolder(folderId: string | null): Promise<void> {\n viewMode.value = \"files\";\n currentFolderId.value = folderId;\n searchQuery.value = \"\";\n selectedItems.value = new Set();\n previewItem.value = null;\n await loadItems();\n }\n\n async function showFrequentlyUsed(): Promise<void> {\n viewMode.value = \"frequently-used\";\n currentFolderId.value = null;\n searchQuery.value = \"\";\n selectedItems.value = new Set();\n previewItem.value = null;\n await loadFrequentlyUsed();\n }\n\n async function uploadFile(file: File): Promise<MediaAsset | null> {\n const { create } = provider;\n if (typeof create !== \"function\") {\n return null;\n }\n\n isUploading.value = true;\n try {\n const input: MediaCreateInput = { file };\n if (currentFolderId.value != null) {\n input.folderId = currentFolderId.value;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n input.templateId = templateId;\n }\n const media = await create(input);\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n return media;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n } finally {\n isUploading.value = false;\n }\n }\n\n async function uploadFiles(files: File[]): Promise<void> {\n const { create } = provider;\n if (typeof create !== \"function\") {\n return;\n }\n\n isUploading.value = true;\n uploadProgress.value = { current: 0, total: files.length };\n try {\n for (let i = 0; i < files.length; i++) {\n uploadProgress.value = { current: i + 1, total: files.length };\n try {\n const input: MediaCreateInput = { file: files[i] };\n if (currentFolderId.value != null) {\n input.folderId = currentFolderId.value;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n input.templateId = templateId;\n }\n const media = await create(input);\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n } finally {\n isUploading.value = false;\n uploadProgress.value = null;\n }\n }\n\n async function moveSelected(targetFolderId: string | null): Promise<void> {\n if (selectedItems.value.size === 0) {\n return;\n }\n\n const foldersApi = foldersProvider();\n const { move } = foldersApi ?? { move: false };\n if (typeof move !== \"function\") {\n return;\n }\n\n try {\n const movedItems = await move([...selectedItems.value], targetFolderId);\n if (currentFolderId.value === null) {\n const movedMap = new Map(movedItems.map((item) => [item.id, item]));\n items.value = items.value.map((item) => movedMap.get(item.id) ?? item);\n } else {\n items.value = items.value.filter(\n (item) => !selectedItems.value.has(item.id),\n );\n }\n selectedItems.value = new Set();\n previewItem.value = null;\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function updateFile(\n mediaId: string,\n filename: string,\n altText?: string,\n ): Promise<boolean> {\n const { update } = provider;\n if (typeof update !== \"function\") {\n return false;\n }\n\n try {\n const patch: MediaAssetPatch = { filename };\n if (altText !== undefined) {\n patch.alt = altText;\n }\n const updated = await update(mediaId, patch);\n items.value = items.value.map((item) =>\n item.id === mediaId ? updated : item,\n );\n if (previewItem.value?.id === mediaId) {\n previewItem.value = updated;\n }\n notify(() => provider.onUpdated?.(updated));\n return true;\n } catch (error) {\n options.onError?.(error as Error);\n return false;\n }\n }\n\n function captureLocalAssets(ids: string[]): MediaAsset[] {\n const captured = new Map<string, MediaAsset>();\n for (const item of items.value) {\n if (ids.includes(item.id)) {\n captured.set(item.id, item);\n }\n }\n for (const item of frequentlyUsedItems.value) {\n if (ids.includes(item.id) && !captured.has(item.id)) {\n captured.set(item.id, item);\n }\n }\n return [...captured.values()];\n }\n\n function dropLocalAssets(ids: string[]): void {\n items.value = items.value.filter((item) => !ids.includes(item.id));\n frequentlyUsedItems.value = frequentlyUsedItems.value.filter(\n (item) => !ids.includes(item.id),\n );\n selectedItems.value = new Set();\n previewItem.value = null;\n }\n\n async function deleteSelected(): Promise<void> {\n if (selectedItems.value.size === 0) return;\n\n const { delete: providerDelete } = provider;\n if (typeof providerDelete !== \"function\") {\n return;\n }\n\n const ids = [...selectedItems.value];\n const captured = captureLocalAssets(ids);\n\n try {\n await providerDelete(ids);\n dropLocalAssets(ids);\n for (const asset of captured) {\n notify(() => provider.onDeleted?.(asset));\n }\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function loadFrequentlyUsed(): Promise<void> {\n const { frequentlyUsed } = provider;\n if (typeof frequentlyUsed !== \"function\") {\n return;\n }\n\n try {\n frequentlyUsedItems.value = await frequentlyUsed();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function checkUsageBeforeDelete(): Promise<boolean> {\n if (selectedItems.value.size === 0) {\n return false;\n }\n\n pendingDeleteIds.value = [...selectedItems.value];\n\n const { checkUsage } = provider;\n if (typeof checkUsage !== \"function\") {\n deleteUsageInfo.value = {};\n showDeleteWarning.value = true;\n return false;\n }\n\n try {\n const usage = await checkUsage(pendingDeleteIds.value);\n deleteUsageInfo.value = usage;\n\n const hasUsage = Object.values(usage).some(\n (info) => info.templateCount > 0,\n );\n\n showDeleteWarning.value = true;\n return hasUsage;\n } catch (error) {\n options.onError?.(error as Error);\n return false;\n }\n }\n\n async function confirmDelete(): Promise<void> {\n const { delete: providerDelete } = provider;\n if (typeof providerDelete !== \"function\") {\n return;\n }\n\n showDeleteWarning.value = false;\n\n if (pendingDeleteIds.value.length === 0) {\n return;\n }\n\n const ids = pendingDeleteIds.value;\n const captured = captureLocalAssets(ids);\n\n try {\n await providerDelete(ids);\n dropLocalAssets(ids);\n pendingDeleteIds.value = [];\n deleteUsageInfo.value = {};\n for (const asset of captured) {\n notify(() => provider.onDeleted?.(asset));\n }\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function cancelDelete(): void {\n showDeleteWarning.value = false;\n pendingDeleteIds.value = [];\n deleteUsageInfo.value = {};\n }\n\n async function importFromUrl(url: string): Promise<MediaAsset | null> {\n const importUrl = provider.importFromUrl;\n if (typeof importUrl !== \"function\") {\n return null;\n }\n\n isImportingFromUrl.value = true;\n importFromUrlError.value = null;\n try {\n const media = await importUrl(\n url,\n currentFolderId.value,\n resolvedTemplateId(),\n );\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n return media;\n } catch (error) {\n importFromUrlError.value =\n error instanceof Error ? error.message : \"Import failed\";\n options.onError?.(error as Error);\n return null;\n } finally {\n isImportingFromUrl.value = false;\n }\n }\n\n function toggleSelection(id: string): void {\n const next = new Set(selectedItems.value);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n selectedItems.value = next;\n }\n\n function clearSelection(): void {\n selectedItems.value = new Set();\n previewItem.value = null;\n }\n\n function selectItem(item: MediaAsset): void {\n previewItem.value = item;\n selectedItems.value = new Set([item.id]);\n }\n\n async function loadStorage(): Promise<void> {\n const { storage } = provider;\n if (typeof storage !== \"function\") {\n storageInfo.value = null;\n return;\n }\n\n try {\n storageInfo.value = await storage();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function loadFolders(): Promise<void> {\n const foldersApi = foldersProvider();\n if (!foldersApi) {\n return;\n }\n\n try {\n folders.value = await foldersApi.list();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function createFolder(\n name: string,\n parentId?: string | null,\n ): Promise<MediaFolder | null> {\n const foldersApi = foldersProvider();\n const { create } = foldersApi ?? { create: false };\n if (typeof create !== \"function\") {\n return null;\n }\n\n try {\n const input: MediaFolderInput = { name };\n if (parentId !== undefined) {\n input.parentId = parentId;\n }\n const folder = await create(input);\n await loadFolders();\n return folder;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n }\n }\n\n function findFolderInTree(\n folderList: MediaFolder[],\n id: string,\n ): MediaFolder | null {\n for (const folder of folderList) {\n if (folder.id === id) return folder;\n }\n return null;\n }\n\n async function renameFolder(folderId: string, name: string): Promise<void> {\n const foldersApi = foldersProvider();\n const { update } = foldersApi ?? { update: false };\n if (typeof update !== \"function\") {\n return;\n }\n\n try {\n await update(folderId, { name });\n await loadFolders();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function deleteFolder(folderId: string): Promise<void> {\n const foldersApi = foldersProvider();\n const { delete: deleteFn } = foldersApi ?? { delete: false };\n if (typeof deleteFn !== \"function\") {\n return;\n }\n\n try {\n const folder = findFolderInTree(folders.value, folderId);\n const parentId = folder?.parentId ?? null;\n\n await deleteFn(folderId);\n\n if (currentFolderId.value === folderId) {\n currentFolderId.value = parentId;\n }\n\n await loadFolders();\n await loadItems();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function checkUsageBeforeReplace(item: MediaAsset): Promise<void> {\n pendingReplaceItem.value = item;\n replaceError.value = null;\n\n const { checkUsage } = provider;\n if (typeof checkUsage !== \"function\") {\n replaceUsageInfo.value = null;\n showReplaceWarning.value = true;\n return;\n }\n\n try {\n const usage = await checkUsage([item.id]);\n replaceUsageInfo.value = usage[item.id] ?? null;\n showReplaceWarning.value = true;\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function cancelReplace(): void {\n showReplaceWarning.value = false;\n pendingReplaceItem.value = null;\n replaceUsageInfo.value = null;\n replaceError.value = null;\n }\n\n function applyReplaced(updated: MediaAsset): void {\n items.value = items.value.map((item) =>\n item.id === updated.id ? updated : item,\n );\n\n frequentlyUsedItems.value = frequentlyUsedItems.value.map((item) =>\n item.id === updated.id ? updated : item,\n );\n\n if (previewItem.value?.id === updated.id) {\n previewItem.value = updated;\n }\n\n notify(() => provider.onUpdated?.(updated));\n }\n\n async function replaceFile(file: File): Promise<MediaAsset | null> {\n const { replace } = provider;\n if (typeof replace !== \"function\" || !pendingReplaceItem.value) {\n return null;\n }\n\n isReplacing.value = true;\n replaceError.value = null;\n\n try {\n const updated = await replace(pendingReplaceItem.value.id, file);\n applyReplaced(updated);\n\n showReplaceWarning.value = false;\n pendingReplaceItem.value = null;\n replaceUsageInfo.value = null;\n\n return updated;\n } catch (error) {\n replaceError.value =\n error instanceof Error ? error.message : \"Replace failed\";\n options.onError?.(error as Error);\n return null;\n } finally {\n isReplacing.value = false;\n }\n }\n\n async function replaceMediaDirectly(\n mediaId: string,\n file: File,\n ): Promise<MediaAsset | null> {\n const { replace } = provider;\n if (typeof replace !== \"function\") {\n return null;\n }\n\n try {\n const updated = await replace(mediaId, file);\n applyReplaced(updated);\n return updated;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n }\n }\n\n return {\n items,\n folders,\n currentFolderId,\n viewMode,\n searchQuery,\n categoryFilter,\n sortOption,\n isLoading,\n isUploading,\n uploadProgress,\n hasMore,\n selectedItems,\n previewItem,\n frequentlyUsedItems,\n deleteUsageInfo,\n showDeleteWarning,\n loadItems,\n loadMore,\n search,\n filterByCategory,\n sortBy,\n navigateToFolder,\n showFrequentlyUsed,\n uploadFile,\n uploadFiles,\n moveSelected,\n updateFile,\n deleteSelected,\n isImportingFromUrl,\n importFromUrlError,\n importFromUrl,\n toggleSelection,\n clearSelection,\n selectItem,\n loadFolders,\n createFolder,\n renameFolder,\n deleteFolder,\n findFolderInTree,\n loadFrequentlyUsed,\n checkUsageBeforeDelete,\n confirmDelete,\n cancelDelete,\n isReplacing,\n replaceError,\n showReplaceWarning,\n pendingReplaceItem,\n replaceUsageInfo,\n checkUsageBeforeReplace,\n cancelReplace,\n replaceFile,\n replaceMediaDirectly,\n storageInfo,\n loadStorage,\n };\n}\n","<script setup lang=\"ts\">\nimport MediaBreadcrumb from \"./media/MediaBreadcrumb.vue\";\nimport MediaFolderTree from \"./media/MediaFolderTree.vue\";\nimport MediaGrid from \"./media/MediaGrid.vue\";\nimport MediaImportUrlModal from \"./media/MediaImportUrlModal.vue\";\nimport MediaMovePicker from \"./media/MediaMovePicker.vue\";\nimport MediaPreviewPanel from \"./media/MediaPreviewPanel.vue\";\nimport MediaReplaceModal from \"./media/MediaReplaceModal.vue\";\nimport MediaUploadZone from \"./media/MediaUploadZone.vue\";\nimport StorageProgressRing from \"./media/StorageProgressRing.vue\";\nimport { useMediaCategories } from \"../composables/useMediaCategories\";\nimport { useMediaLibraryUI } from \"../composables/useMediaLibraryUI\";\nimport { useMediaLibrary } from \"../composable\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport {\n Check,\n Copy,\n Grid2x2,\n Link,\n List,\n PanelLeft,\n Search,\n X,\n} from \"@lucide/vue\";\nimport {\n computed,\n defineAsyncComponent,\n onBeforeUnmount,\n provide,\n watch,\n} from \"vue\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { MEDIA_LIMITS_KEY } from \"../keys\";\n\nconst props = defineProps<{\n provider: MediaProvider;\n accept?: MediaCategory[];\n templateId?: string;\n onError?: (error: Error) => void;\n translations: MediaTranslations;\n showClose?: boolean;\n showConfirm?: boolean;\n confirmTestId?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"confirm\", item: MediaAsset): void;\n (e: \"ready\"): void;\n}>();\n\nconst MediaEditModal = defineAsyncComponent(\n () => import(\"./media/MediaEditModal.vue\"),\n);\n\nconst t = computed(() => props.translations);\nconst resolvedConfirmTestId = computed(\n () => props.confirmTestId ?? \"media-confirm\",\n);\n\nconst mediaLimits = {\n get maxFileSize() {\n return props.provider.maxFileSize;\n },\n get mimeTypes() {\n return props.provider.mimeTypes;\n },\n get accept() {\n return props.accept;\n },\n};\nprovide(MEDIA_LIMITS_KEY, mediaLimits);\n\nconst { isAcceptedMimeType, availableCategories } =\n useMediaCategories(mediaLimits);\n\nconst library = useMediaLibrary({\n provider: props.provider,\n templateId: () => props.templateId,\n onError: (error) => props.onError?.(error),\n});\n\nconst ui = useMediaLibraryUI({\n library,\n translations: t,\n});\n\nconst canCreate = computed(() => typeof props.provider.create === \"function\");\nconst canFolders = computed(() => props.provider.folders !== false);\nconst canImport = computed(\n () => typeof props.provider.importFromUrl === \"function\",\n);\nconst canDelete = computed(() => typeof props.provider.delete === \"function\");\nconst canUpdate = computed(() => typeof props.provider.update === \"function\");\nconst canReplace = computed(() => typeof props.provider.replace === \"function\");\nconst canFrequentlyUsed = computed(\n () => typeof props.provider.frequentlyUsed === \"function\",\n);\nconst canMove = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.move === \"function\";\n});\nconst canCreateFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.create === \"function\";\n});\nconst canRenameFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.update === \"function\";\n});\nconst canDeleteFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.delete === \"function\";\n});\n\nconst isInitialLoad = computed(\n () => library.isLoading.value && ui.displayItems.value.length === 0,\n);\n\nconst isConfirmable = computed(() => {\n const item = library.previewItem.value;\n if (!item) {\n return false;\n }\n if (!props.accept?.length) {\n return true;\n }\n return isAcceptedMimeType(item.mimeType ?? \"\", props.accept);\n});\n\nconst selectedDeletableCount = computed(() => {\n if (!canDelete.value) {\n return 0;\n }\n let count = 0;\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n count += 1;\n }\n }\n return count;\n});\n\nconst nestedDialogOpen = computed(\n () =>\n ui.showImportUrlModal.value ||\n ui.editingItem.value !== null ||\n library.showReplaceWarning.value,\n);\n\nfunction handleEscape(): boolean {\n if (nestedDialogOpen.value) {\n return true;\n }\n if (ui.showMovePicker.value) {\n ui.showMovePicker.value = false;\n return true;\n }\n if (library.showDeleteWarning.value) {\n library.cancelDelete();\n return true;\n }\n return false;\n}\n\nfunction confirmSelection(): void {\n if (!isConfirmable.value || !library.previewItem.value) {\n return;\n }\n emit(\"confirm\", library.previewItem.value);\n}\n\nfunction handleCategoryChange(event: Event): void {\n const value = (event.target as HTMLSelectElement).value;\n library.filterByCategory(value ? (value as MediaCategory) : null);\n}\n\nasync function handleDeleteClick(): Promise<void> {\n const next = new Set<string>();\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n next.add(id);\n }\n }\n library.selectedItems.value = next;\n await ui.handleDeleteClick();\n}\n\nwatch(\n () => props.accept,\n (accept) => {\n if (accept?.length === 1) {\n library.categoryFilter.value = accept[0];\n }\n },\n { immediate: true },\n);\n\nlibrary.loadItems();\nlibrary.loadFrequentlyUsed();\nlibrary.loadStorage();\nemit(\"ready\");\n\nonBeforeUnmount(() => {\n ui.resetUI();\n});\n\ndefineExpose({ nestedDialogOpen, handleEscape, library, ui });\n</script>\n\n<template>\n <div\n class=\"tpl:relative tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:overflow-hidden\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-5 tpl:py-3.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <h2\n id=\"tpl-media-library-title\"\n class=\"tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.title }}\n </h2>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <StorageProgressRing\n v-if=\"library.storageInfo.value\"\n :used-bytes=\"library.storageInfo.value.usedBytes\"\n :limit-bytes=\"library.storageInfo.value.limitBytes\"\n :size=\"22\"\n />\n <div class=\"tpl:relative\">\n <input\n :value=\"ui.searchInput.value\"\n type=\"search\"\n :disabled=\"isInitialLoad\"\n :aria-label=\"t.mediaLibrary.searchAriaLabel\"\n class=\"tpl:w-52 tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-3 tpl:pl-8 tpl:text-xs tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:outline-none tpl:focus:shadow-[var(--tpl-ring)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.searchPlaceholder\"\n @input=\"\n ui.handleSearchInput(($event.target as HTMLInputElement).value)\n \"\n />\n <Search\n class=\"tpl:absolute tpl:top-1/2 tpl:left-2.5 tpl:-translate-y-1/2\"\n :size=\"13\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-text-dim)\"\n />\n </div>\n <button\n v-if=\"showClose\"\n type=\"button\"\n :aria-label=\"t.mediaLibrary.close\"\n class=\"tpl:flex tpl:size-7 tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"emit('close')\"\n >\n <X :size=\"18\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Body -->\n <div class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:overflow-hidden\">\n <Transition\n enter-active-class=\"tpl:transition-all tpl:duration-200 tpl:ease-out\"\n enter-from-class=\"tpl:-ml-48 tpl:opacity-0\"\n enter-to-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-150 tpl:ease-in\"\n leave-from-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-to-class=\"tpl:-ml-48 tpl:opacity-0\"\n >\n <div\n v-if=\"canFolders && ui.showSidebar.value\"\n class=\"tpl:flex tpl:w-48 tpl:shrink-0 tpl:flex-col tpl:border-r\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <MediaFolderTree\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n :view-mode=\"library.viewMode.value\"\n :has-frequently-used=\"\n canFrequentlyUsed && ui.hasFrequentlyUsed.value\n \"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"library.navigateToFolder\"\n @create-folder=\"ui.handleCreateFolder\"\n @rename-folder=\"ui.handleRenameFolder\"\n @delete-folder=\"ui.handleDeleteFolder\"\n @show-frequently-used=\"library.showFrequentlyUsed\"\n />\n </div>\n </Transition>\n\n <!-- Content area -->\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:flex-col\">\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-4 tpl:py-2.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"canFolders\"\n type=\"button\"\n data-testid=\"media-folder-toggle\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: ui.showSidebar.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor: ui.showSidebar.value\n ? 'var(--tpl-bg)'\n : 'transparent',\n border: ui.showSidebar.value\n ? '1px solid var(--tpl-border)'\n : '1px solid transparent',\n }\"\n :title=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-label=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-pressed=\"ui.showSidebar.value\"\n @click=\"ui.showSidebar.value = !ui.showSidebar.value\"\n >\n <PanelLeft :size=\"16\" :stroke-width=\"2\" />\n </button>\n\n <template v-if=\"library.viewMode.value === 'frequently-used'\">\n <span\n class=\"tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.frequentlyUsed }}\n </span>\n </template>\n <template v-else>\n <MediaBreadcrumb\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @navigate=\"library.navigateToFolder\"\n />\n </template>\n\n <div\n class=\"tpl:flex tpl:rounded-md tpl:p-0.5\"\n style=\"\n border: 1px solid var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewGrid\"\n :aria-label=\"t.mediaLibrary.viewGrid\"\n :aria-pressed=\"ui.layoutMode.value === 'grid'\"\n @click=\"ui.layoutMode.value = 'grid'\"\n >\n <Grid2x2 :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewList\"\n :aria-label=\"t.mediaLibrary.viewList\"\n :aria-pressed=\"ui.layoutMode.value === 'list'\"\n @click=\"ui.layoutMode.value = 'list'\"\n >\n <List :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <select\n v-if=\"availableCategories.length > 1\"\n :aria-label=\"t.mediaLibrary.filterAriaLabel\"\n class=\"tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-7 tpl:pl-2.5 tpl:text-xs tpl:transition-all tpl:duration-150 tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :value=\"library.categoryFilter.value ?? ''\"\n @change=\"handleCategoryChange\"\n >\n <option value=\"\">\n {{ t.mediaLibrary.filterAll }}\n </option>\n <option\n v-for=\"category in availableCategories\"\n :key=\"category\"\n :value=\"category\"\n >\n {{ ui.getCategoryLabel(category) }}\n </option>\n </select>\n </div>\n </div>\n\n <div class=\"tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto\">\n <div\n v-if=\"\n library.viewMode.value === 'files' && (canCreate || canImport)\n \"\n class=\"tpl:px-4 tpl:pt-3\"\n >\n <MediaUploadZone\n v-if=\"canCreate\"\n :is-uploading=\"library.isUploading.value\"\n :upload-progress=\"library.uploadProgress.value\"\n @upload=\"ui.handleUpload\"\n />\n <button\n v-if=\"canImport\"\n data-testid=\"media-import-url\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-dashed tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text-muted);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showImportUrlModal.value = true\"\n >\n <Link :size=\"14\" :stroke-width=\"2\" />\n {{ t.mediaLibrary.importFromUrl }}\n </button>\n </div>\n\n <MediaGrid\n :items=\"ui.displayItems.value\"\n :selected-ids=\"library.selectedItems.value\"\n :is-loading=\"library.isLoading.value\"\n :has-more=\"\n library.viewMode.value === 'files' && library.hasMore.value\n \"\n :accept=\"accept\"\n :layout=\"ui.layoutMode.value\"\n :search-query=\"library.searchQuery.value\"\n :can-update=\"canUpdate\"\n :can-replace=\"canReplace\"\n @select=\"ui.handleSelect\"\n @toggle=\"library.toggleSelection\"\n @load-more=\"library.loadMore\"\n @edit=\"ui.handleEditItem\"\n @replace=\"ui.handleReplaceItem\"\n @confirm=\"confirmSelection\"\n />\n </div>\n </div>\n </div>\n\n <MediaImportUrlModal\n :visible=\"ui.showImportUrlModal.value\"\n :is-importing=\"library.isImportingFromUrl.value\"\n :error=\"library.importFromUrlError.value\"\n @import=\"ui.handleImportFromUrl\"\n @close=\"ui.showImportUrlModal.value = false\"\n />\n\n <MediaEditModal\n v-if=\"ui.editingItem.value\"\n :visible=\"true\"\n :item=\"ui.editingItem.value\"\n :save=\"ui.handleEditSave\"\n :can-crop=\"canReplace && ui.editingItem.value.canUpdate !== false\"\n @close=\"ui.editingItem.value = null\"\n />\n\n <MediaReplaceModal\n :visible=\"library.showReplaceWarning.value\"\n :item=\"library.pendingReplaceItem.value\"\n :usage-info=\"library.replaceUsageInfo.value\"\n :is-replacing=\"library.isReplacing.value\"\n :error=\"library.replaceError.value\"\n @replace=\"ui.handleReplaceFile\"\n @close=\"library.cancelReplace\"\n />\n\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"library.showDeleteWarning.value\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-delete-title\"\n class=\"tpl:absolute tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n background-color: var(--tpl-overlay);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n @click.self=\"library.cancelDelete\"\n >\n <div\n class=\"tpl-scale-in tpl:mx-4 tpl:w-full tpl:max-w-sm tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-media-delete-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.deleteWarningTitle }}\n </h3>\n <p\n class=\"tpl:text-xs\"\n :class=\"ui.hasUsedFiles.value ? 'tpl:mb-2' : 'tpl:mb-4'\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningMessage }}\n </p>\n <p\n v-if=\"ui.hasUsedFiles.value\"\n data-testid=\"media-delete-usage\"\n class=\"tpl:mb-4 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningUsageNote }}\n </p>\n\n <div\n v-if=\"ui.hasUsedFiles.value\"\n class=\"tpl:mb-4 tpl:max-h-32 tpl:overflow-y-auto tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div\n v-for=\"(info, mediaId) in library.deleteUsageInfo.value\"\n :key=\"mediaId\"\n class=\"tpl:text-xs\"\n style=\"color: var(--tpl-text)\"\n >\n <template v-if=\"info.templateCount > 0\">\n <span class=\"tpl:font-medium\">\n {{\n ui.displayItems.value.find((i) => i.id === mediaId)\n ?.filename || mediaId\n }}\n </span>\n <span style=\"color: var(--tpl-text-muted)\">\n -\n {{\n t.mediaLibrary.usedInTemplates.replace(\n \"{count}\",\n info.templateCount.toString(),\n )\n }}\n </span>\n </template>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.cancelDelete\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.confirmDelete\"\n >\n {{\n ui.hasUsedFiles.value\n ? t.mediaLibrary.deleteAnyway\n : t.mediaLibrary.confirmDelete\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Footer -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-t tpl:px-5 tpl:py-3\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-3\">\n <MediaPreviewPanel\n v-if=\"library.previewItem.value\"\n :item=\"library.previewItem.value\"\n :folders=\"ui.folderTree.value\"\n />\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-5\">\n <div\n v-if=\"library.selectedItems.value.size > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n v-if=\"library.previewItem.value\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: ui.copied.value\n ? 'var(--tpl-success)'\n : 'var(--tpl-border)',\n color: ui.copied.value ? 'var(--tpl-success)' : 'var(--tpl-text)',\n backgroundColor: 'var(--tpl-bg)',\n }\"\n @click=\"ui.copy(ui.selectedUrl.value!)\"\n >\n <Copy v-if=\"!ui.copied.value\" :size=\"12\" :stroke-width=\"2\" />\n <Check v-else :size=\"12\" :stroke-width=\"2\" />\n {{\n ui.copied.value ? t.mediaLibrary.copied : t.mediaLibrary.copyUrl\n }}\n </button>\n <div v-if=\"canMove\" class=\"tpl:relative\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showMovePicker.value = !ui.showMovePicker.value\"\n >\n {{ t.mediaLibrary.moveSelected }}\n </button>\n <MediaMovePicker\n v-if=\"ui.showMovePicker.value\"\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @select=\"ui.handleMoveToFolder\"\n @close=\"ui.showMovePicker.value = false\"\n />\n </div>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"selectedDeletableCount > 0\"\n data-testid=\"media-delete\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"handleDeleteClick\"\n >\n {{ t.mediaLibrary.deleteSelected }}\n </button>\n <button\n v-if=\"showConfirm\"\n :data-testid=\"resolvedConfirmTestId\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-4 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!isConfirmable\"\n @click=\"confirmSelection\"\n >\n {{\n accept?.length\n ? t.mediaLibrary.selectImage\n : t.mediaLibrary.selectFile\n }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaBreadcrumb from \"./media/MediaBreadcrumb.vue\";\nimport MediaFolderTree from \"./media/MediaFolderTree.vue\";\nimport MediaGrid from \"./media/MediaGrid.vue\";\nimport MediaImportUrlModal from \"./media/MediaImportUrlModal.vue\";\nimport MediaMovePicker from \"./media/MediaMovePicker.vue\";\nimport MediaPreviewPanel from \"./media/MediaPreviewPanel.vue\";\nimport MediaReplaceModal from \"./media/MediaReplaceModal.vue\";\nimport MediaUploadZone from \"./media/MediaUploadZone.vue\";\nimport StorageProgressRing from \"./media/StorageProgressRing.vue\";\nimport { useMediaCategories } from \"../composables/useMediaCategories\";\nimport { useMediaLibraryUI } from \"../composables/useMediaLibraryUI\";\nimport { useMediaLibrary } from \"../composable\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport {\n Check,\n Copy,\n Grid2x2,\n Link,\n List,\n PanelLeft,\n Search,\n X,\n} from \"@lucide/vue\";\nimport {\n computed,\n defineAsyncComponent,\n onBeforeUnmount,\n provide,\n watch,\n} from \"vue\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { MEDIA_LIMITS_KEY } from \"../keys\";\n\nconst props = defineProps<{\n provider: MediaProvider;\n accept?: MediaCategory[];\n templateId?: string;\n onError?: (error: Error) => void;\n translations: MediaTranslations;\n showClose?: boolean;\n showConfirm?: boolean;\n confirmTestId?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"confirm\", item: MediaAsset): void;\n (e: \"ready\"): void;\n}>();\n\nconst MediaEditModal = defineAsyncComponent(\n () => import(\"./media/MediaEditModal.vue\"),\n);\n\nconst t = computed(() => props.translations);\nconst resolvedConfirmTestId = computed(\n () => props.confirmTestId ?? \"media-confirm\",\n);\n\nconst mediaLimits = {\n get maxFileSize() {\n return props.provider.maxFileSize;\n },\n get mimeTypes() {\n return props.provider.mimeTypes;\n },\n get accept() {\n return props.accept;\n },\n};\nprovide(MEDIA_LIMITS_KEY, mediaLimits);\n\nconst { isAcceptedMimeType, availableCategories } =\n useMediaCategories(mediaLimits);\n\nconst library = useMediaLibrary({\n provider: props.provider,\n templateId: () => props.templateId,\n onError: (error) => props.onError?.(error),\n});\n\nconst ui = useMediaLibraryUI({\n library,\n translations: t,\n});\n\nconst canCreate = computed(() => typeof props.provider.create === \"function\");\nconst canFolders = computed(() => props.provider.folders !== false);\nconst canImport = computed(\n () => typeof props.provider.importFromUrl === \"function\",\n);\nconst canDelete = computed(() => typeof props.provider.delete === \"function\");\nconst canUpdate = computed(() => typeof props.provider.update === \"function\");\nconst canReplace = computed(() => typeof props.provider.replace === \"function\");\nconst canFrequentlyUsed = computed(\n () => typeof props.provider.frequentlyUsed === \"function\",\n);\nconst canMove = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.move === \"function\";\n});\nconst canCreateFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.create === \"function\";\n});\nconst canRenameFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.update === \"function\";\n});\nconst canDeleteFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.delete === \"function\";\n});\n\nconst isInitialLoad = computed(\n () => library.isLoading.value && ui.displayItems.value.length === 0,\n);\n\nconst isConfirmable = computed(() => {\n const item = library.previewItem.value;\n if (!item) {\n return false;\n }\n if (!props.accept?.length) {\n return true;\n }\n return isAcceptedMimeType(item.mimeType ?? \"\", props.accept);\n});\n\nconst selectedDeletableCount = computed(() => {\n if (!canDelete.value) {\n return 0;\n }\n let count = 0;\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n count += 1;\n }\n }\n return count;\n});\n\nconst nestedDialogOpen = computed(\n () =>\n ui.showImportUrlModal.value ||\n ui.editingItem.value !== null ||\n library.showReplaceWarning.value,\n);\n\nfunction handleEscape(): boolean {\n if (nestedDialogOpen.value) {\n return true;\n }\n if (ui.showMovePicker.value) {\n ui.showMovePicker.value = false;\n return true;\n }\n if (library.showDeleteWarning.value) {\n library.cancelDelete();\n return true;\n }\n return false;\n}\n\nfunction confirmSelection(): void {\n if (!isConfirmable.value || !library.previewItem.value) {\n return;\n }\n emit(\"confirm\", library.previewItem.value);\n}\n\nfunction handleCategoryChange(event: Event): void {\n const value = (event.target as HTMLSelectElement).value;\n library.filterByCategory(value ? (value as MediaCategory) : null);\n}\n\nasync function handleDeleteClick(): Promise<void> {\n const next = new Set<string>();\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n next.add(id);\n }\n }\n library.selectedItems.value = next;\n await ui.handleDeleteClick();\n}\n\nwatch(\n () => props.accept,\n (accept) => {\n if (accept?.length === 1) {\n library.categoryFilter.value = accept[0];\n }\n },\n { immediate: true },\n);\n\nlibrary.loadItems();\nlibrary.loadFrequentlyUsed();\nlibrary.loadStorage();\nemit(\"ready\");\n\nonBeforeUnmount(() => {\n ui.resetUI();\n});\n\ndefineExpose({ nestedDialogOpen, handleEscape, library, ui });\n</script>\n\n<template>\n <div\n class=\"tpl:relative tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:overflow-hidden\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-5 tpl:py-3.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <h2\n id=\"tpl-media-library-title\"\n class=\"tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.title }}\n </h2>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <StorageProgressRing\n v-if=\"library.storageInfo.value\"\n :used-bytes=\"library.storageInfo.value.usedBytes\"\n :limit-bytes=\"library.storageInfo.value.limitBytes\"\n :size=\"22\"\n />\n <div class=\"tpl:relative\">\n <input\n :value=\"ui.searchInput.value\"\n type=\"search\"\n :disabled=\"isInitialLoad\"\n :aria-label=\"t.mediaLibrary.searchAriaLabel\"\n class=\"tpl:w-52 tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-3 tpl:pl-8 tpl:text-xs tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:outline-none tpl:focus:shadow-[var(--tpl-ring)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.searchPlaceholder\"\n @input=\"\n ui.handleSearchInput(($event.target as HTMLInputElement).value)\n \"\n />\n <Search\n class=\"tpl:absolute tpl:top-1/2 tpl:left-2.5 tpl:-translate-y-1/2\"\n :size=\"13\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-text-dim)\"\n />\n </div>\n <button\n v-if=\"showClose\"\n type=\"button\"\n :aria-label=\"t.mediaLibrary.close\"\n class=\"tpl:flex tpl:size-7 tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"emit('close')\"\n >\n <X :size=\"18\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Body -->\n <div class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:overflow-hidden\">\n <Transition\n enter-active-class=\"tpl:transition-all tpl:duration-200 tpl:ease-out\"\n enter-from-class=\"tpl:-ml-48 tpl:opacity-0\"\n enter-to-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-150 tpl:ease-in\"\n leave-from-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-to-class=\"tpl:-ml-48 tpl:opacity-0\"\n >\n <div\n v-if=\"canFolders && ui.showSidebar.value\"\n class=\"tpl:flex tpl:w-48 tpl:shrink-0 tpl:flex-col tpl:border-r\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <MediaFolderTree\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n :view-mode=\"library.viewMode.value\"\n :has-frequently-used=\"\n canFrequentlyUsed && ui.hasFrequentlyUsed.value\n \"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"library.navigateToFolder\"\n @create-folder=\"ui.handleCreateFolder\"\n @rename-folder=\"ui.handleRenameFolder\"\n @delete-folder=\"ui.handleDeleteFolder\"\n @show-frequently-used=\"library.showFrequentlyUsed\"\n />\n </div>\n </Transition>\n\n <!-- Content area -->\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:flex-col\">\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-4 tpl:py-2.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"canFolders\"\n type=\"button\"\n data-testid=\"media-folder-toggle\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: ui.showSidebar.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor: ui.showSidebar.value\n ? 'var(--tpl-bg)'\n : 'transparent',\n border: ui.showSidebar.value\n ? '1px solid var(--tpl-border)'\n : '1px solid transparent',\n }\"\n :title=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-label=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-pressed=\"ui.showSidebar.value\"\n @click=\"ui.showSidebar.value = !ui.showSidebar.value\"\n >\n <PanelLeft :size=\"16\" :stroke-width=\"2\" />\n </button>\n\n <template v-if=\"library.viewMode.value === 'frequently-used'\">\n <span\n class=\"tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.frequentlyUsed }}\n </span>\n </template>\n <template v-else>\n <MediaBreadcrumb\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @navigate=\"library.navigateToFolder\"\n />\n </template>\n\n <div\n class=\"tpl:flex tpl:rounded-md tpl:p-0.5\"\n style=\"\n border: 1px solid var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewGrid\"\n :aria-label=\"t.mediaLibrary.viewGrid\"\n :aria-pressed=\"ui.layoutMode.value === 'grid'\"\n @click=\"ui.layoutMode.value = 'grid'\"\n >\n <Grid2x2 :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewList\"\n :aria-label=\"t.mediaLibrary.viewList\"\n :aria-pressed=\"ui.layoutMode.value === 'list'\"\n @click=\"ui.layoutMode.value = 'list'\"\n >\n <List :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <select\n v-if=\"availableCategories.length > 1\"\n :aria-label=\"t.mediaLibrary.filterAriaLabel\"\n class=\"tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-7 tpl:pl-2.5 tpl:text-xs tpl:transition-all tpl:duration-150 tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :value=\"library.categoryFilter.value ?? ''\"\n @change=\"handleCategoryChange\"\n >\n <option value=\"\">\n {{ t.mediaLibrary.filterAll }}\n </option>\n <option\n v-for=\"category in availableCategories\"\n :key=\"category\"\n :value=\"category\"\n >\n {{ ui.getCategoryLabel(category) }}\n </option>\n </select>\n </div>\n </div>\n\n <div class=\"tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto\">\n <div\n v-if=\"\n library.viewMode.value === 'files' && (canCreate || canImport)\n \"\n class=\"tpl:px-4 tpl:pt-3\"\n >\n <MediaUploadZone\n v-if=\"canCreate\"\n :is-uploading=\"library.isUploading.value\"\n :upload-progress=\"library.uploadProgress.value\"\n @upload=\"ui.handleUpload\"\n />\n <button\n v-if=\"canImport\"\n data-testid=\"media-import-url\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-dashed tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text-muted);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showImportUrlModal.value = true\"\n >\n <Link :size=\"14\" :stroke-width=\"2\" />\n {{ t.mediaLibrary.importFromUrl }}\n </button>\n </div>\n\n <MediaGrid\n :items=\"ui.displayItems.value\"\n :selected-ids=\"library.selectedItems.value\"\n :is-loading=\"library.isLoading.value\"\n :has-more=\"\n library.viewMode.value === 'files' && library.hasMore.value\n \"\n :accept=\"accept\"\n :layout=\"ui.layoutMode.value\"\n :search-query=\"library.searchQuery.value\"\n :can-update=\"canUpdate\"\n :can-replace=\"canReplace\"\n @select=\"ui.handleSelect\"\n @toggle=\"library.toggleSelection\"\n @load-more=\"library.loadMore\"\n @edit=\"ui.handleEditItem\"\n @replace=\"ui.handleReplaceItem\"\n @confirm=\"confirmSelection\"\n />\n </div>\n </div>\n </div>\n\n <MediaImportUrlModal\n :visible=\"ui.showImportUrlModal.value\"\n :is-importing=\"library.isImportingFromUrl.value\"\n :error=\"library.importFromUrlError.value\"\n @import=\"ui.handleImportFromUrl\"\n @close=\"ui.showImportUrlModal.value = false\"\n />\n\n <MediaEditModal\n v-if=\"ui.editingItem.value\"\n :visible=\"true\"\n :item=\"ui.editingItem.value\"\n :save=\"ui.handleEditSave\"\n :can-crop=\"canReplace && ui.editingItem.value.canUpdate !== false\"\n @close=\"ui.editingItem.value = null\"\n />\n\n <MediaReplaceModal\n :visible=\"library.showReplaceWarning.value\"\n :item=\"library.pendingReplaceItem.value\"\n :usage-info=\"library.replaceUsageInfo.value\"\n :is-replacing=\"library.isReplacing.value\"\n :error=\"library.replaceError.value\"\n @replace=\"ui.handleReplaceFile\"\n @close=\"library.cancelReplace\"\n />\n\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"library.showDeleteWarning.value\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-delete-title\"\n class=\"tpl:absolute tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n background-color: var(--tpl-overlay);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n @click.self=\"library.cancelDelete\"\n >\n <div\n class=\"tpl-scale-in tpl:mx-4 tpl:w-full tpl:max-w-sm tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-media-delete-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.deleteWarningTitle }}\n </h3>\n <p\n class=\"tpl:text-xs\"\n :class=\"ui.hasUsedFiles.value ? 'tpl:mb-2' : 'tpl:mb-4'\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningMessage }}\n </p>\n <p\n v-if=\"ui.hasUsedFiles.value\"\n data-testid=\"media-delete-usage\"\n class=\"tpl:mb-4 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningUsageNote }}\n </p>\n\n <div\n v-if=\"ui.hasUsedFiles.value\"\n class=\"tpl:mb-4 tpl:max-h-32 tpl:overflow-y-auto tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div\n v-for=\"(info, mediaId) in library.deleteUsageInfo.value\"\n :key=\"mediaId\"\n class=\"tpl:text-xs\"\n style=\"color: var(--tpl-text)\"\n >\n <template v-if=\"info.templateCount > 0\">\n <span class=\"tpl:font-medium\">\n {{\n ui.displayItems.value.find((i) => i.id === mediaId)\n ?.filename || mediaId\n }}\n </span>\n <span style=\"color: var(--tpl-text-muted)\">\n -\n {{\n t.mediaLibrary.usedInTemplates.replace(\n \"{count}\",\n info.templateCount.toString(),\n )\n }}\n </span>\n </template>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.cancelDelete\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.confirmDelete\"\n >\n {{\n ui.hasUsedFiles.value\n ? t.mediaLibrary.deleteAnyway\n : t.mediaLibrary.confirmDelete\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Footer -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-t tpl:px-5 tpl:py-3\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-3\">\n <MediaPreviewPanel\n v-if=\"library.previewItem.value\"\n :item=\"library.previewItem.value\"\n :folders=\"ui.folderTree.value\"\n />\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-5\">\n <div\n v-if=\"library.selectedItems.value.size > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n v-if=\"library.previewItem.value\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: ui.copied.value\n ? 'var(--tpl-success)'\n : 'var(--tpl-border)',\n color: ui.copied.value ? 'var(--tpl-success)' : 'var(--tpl-text)',\n backgroundColor: 'var(--tpl-bg)',\n }\"\n @click=\"ui.copy(ui.selectedUrl.value!)\"\n >\n <Copy v-if=\"!ui.copied.value\" :size=\"12\" :stroke-width=\"2\" />\n <Check v-else :size=\"12\" :stroke-width=\"2\" />\n {{\n ui.copied.value ? t.mediaLibrary.copied : t.mediaLibrary.copyUrl\n }}\n </button>\n <div v-if=\"canMove\" class=\"tpl:relative\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showMovePicker.value = !ui.showMovePicker.value\"\n >\n {{ t.mediaLibrary.moveSelected }}\n </button>\n <MediaMovePicker\n v-if=\"ui.showMovePicker.value\"\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @select=\"ui.handleMoveToFolder\"\n @close=\"ui.showMovePicker.value = false\"\n />\n </div>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"selectedDeletableCount > 0\"\n data-testid=\"media-delete\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"handleDeleteClick\"\n >\n {{ t.mediaLibrary.deleteSelected }}\n </button>\n <button\n v-if=\"showConfirm\"\n :data-testid=\"resolvedConfirmTestId\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-4 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!isConfirmable\"\n @click=\"confirmSelection\"\n >\n {{\n accept?.length\n ? t.mediaLibrary.selectImage\n : t.mediaLibrary.selectFile\n }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaLibraryChrome from \"../components/MediaLibraryChrome.vue\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { computed, provide, ref, toRef } from \"vue\";\nimport { POPOVER_TARGET_KEY, TRANSLATIONS_KEY, UI_LOCALE_KEY } from \"../keys\";\n\nconst props = defineProps<{\n /**\n * Storage backend. **A prop, not an injection.** A bare string never\n * resolves the `Symbol` a host provides, and the miss is silent. A prop\n * is checked at compile time.\n */\n provider: MediaProvider;\n translations: MediaTranslations;\n onSelect?: (asset: MediaAsset) => void;\n accept?: MediaCategory[];\n locale?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"ready\"): void;\n}>();\n\nconst t = computed(() => props.translations);\n\n// `TRANSLATIONS_KEY`, not the bare string: a string never resolves the\n// `Symbol` a host provides under the same name. Wrapped in a ref because\n// the key carries one — this shell always has its strings up front.\nprovide(TRANSLATIONS_KEY, ref(props.translations));\n\nprovide(\n UI_LOCALE_KEY,\n toRef(() => props.locale),\n);\n\nconst popoverRoot = ref<HTMLElement | null>(null);\nprovide(POPOVER_TARGET_KEY, popoverRoot);\n\nfunction handleConfirm(item: MediaAsset): void {\n props.onSelect?.(item);\n}\n</script>\n\n<template>\n <div\n ref=\"popoverRoot\"\n class=\"tpl tpl:flex tpl:flex-col tpl:overflow-hidden tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n width: 100%;\n height: 100%;\n background-color: var(--tpl-bg-elevated);\n border: 1px solid var(--tpl-border);\n \"\n >\n <MediaLibraryChrome\n :provider=\"provider\"\n :accept=\"accept\"\n :translations=\"t\"\n :show-confirm=\"!!onSelect\"\n confirm-test-id=\"media-select\"\n @confirm=\"handleConfirm\"\n @ready=\"emit('ready')\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaLibraryChrome from \"../components/MediaLibraryChrome.vue\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { computed, provide, ref, toRef } from \"vue\";\nimport { POPOVER_TARGET_KEY, TRANSLATIONS_KEY, UI_LOCALE_KEY } from \"../keys\";\n\nconst props = defineProps<{\n /**\n * Storage backend. **A prop, not an injection.** A bare string never\n * resolves the `Symbol` a host provides, and the miss is silent. A prop\n * is checked at compile time.\n */\n provider: MediaProvider;\n translations: MediaTranslations;\n onSelect?: (asset: MediaAsset) => void;\n accept?: MediaCategory[];\n locale?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"ready\"): void;\n}>();\n\nconst t = computed(() => props.translations);\n\n// `TRANSLATIONS_KEY`, not the bare string: a string never resolves the\n// `Symbol` a host provides under the same name. Wrapped in a ref because\n// the key carries one — this shell always has its strings up front.\nprovide(TRANSLATIONS_KEY, ref(props.translations));\n\nprovide(\n UI_LOCALE_KEY,\n toRef(() => props.locale),\n);\n\nconst popoverRoot = ref<HTMLElement | null>(null);\nprovide(POPOVER_TARGET_KEY, popoverRoot);\n\nfunction handleConfirm(item: MediaAsset): void {\n props.onSelect?.(item);\n}\n</script>\n\n<template>\n <div\n ref=\"popoverRoot\"\n class=\"tpl tpl:flex tpl:flex-col tpl:overflow-hidden tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n width: 100%;\n height: 100%;\n background-color: var(--tpl-bg-elevated);\n border: 1px solid var(--tpl-border);\n \"\n >\n <MediaLibraryChrome\n :provider=\"provider\"\n :accept=\"accept\"\n :translations=\"t\"\n :show-confirm=\"!!onSelect\"\n confirm-test-id=\"media-select\"\n @confirm=\"handleConfirm\"\n @ready=\"emit('ready')\"\n />\n </div>\n</template>\n","/// <reference types=\"vite/client\" />\n\nimport type en from \"./locales/en\";\n\nexport type MediaTranslations = typeof en;\n\n// Vite resolves this glob at build time. Adding a new locale file\n// automatically registers it — no array to keep in sync.\nconst localeModules = import.meta.glob<{ default: MediaTranslations }>(\n \"./locales/*.ts\",\n);\n\nfunction localesFromGlob(modules: Record<string, unknown>): string[] {\n return Object.keys(modules)\n .map((path) => path.match(/\\/([^/]+)\\.ts$/)?.[1])\n .filter((locale): locale is string => Boolean(locale));\n}\n\nconst supportedLocales = localesFromGlob(localeModules);\n\nfunction canonicalize(locale: string): string {\n return locale.trim().replace(/_/g, \"-\").toLowerCase();\n}\n\nfunction findSupportedLocale(locale: string): string | undefined {\n const canonical = canonicalize(locale);\n return supportedLocales.find((s) => canonicalize(s) === canonical);\n}\n\nfunction getBaseLocale(locale: string): string {\n return canonicalize(locale).split(\"-\")[0];\n}\n\nfunction resolveLocale(locale: string): string {\n return (\n findSupportedLocale(locale) ??\n findSupportedLocale(getBaseLocale(locale)) ??\n \"en\"\n );\n}\n\nexport async function loadMediaTranslations(\n locale: string,\n): Promise<MediaTranslations> {\n const target = resolveLocale(locale);\n const loader = localeModules[`./locales/${target}.ts`];\n const module = await loader();\n return module.default;\n}\n","import MediaLibrary from \"./MediaLibrary.vue\";\nimport { loadMediaTranslations, type MediaTranslations } from \"../i18n\";\nimport type { MediaLibraryConfig, MediaLibraryInstance } from \"./types\";\nimport { createApp, h, ref, type App, type Ref } from \"vue\";\n\nimport \"../styles/index.css\";\n\nexport type { MediaAsset, MediaFolder } from \"@templatical/types\";\nexport type { MediaLibraryConfig, MediaLibraryInstance } from \"./types\";\n\nlet appInstance: App | null = null;\nconst mediaLibraryRef: Ref<InstanceType<typeof MediaLibrary> | null> =\n ref(null);\n\nasync function init(config: MediaLibraryConfig): Promise<MediaLibraryInstance> {\n const container =\n typeof config.container === \"string\"\n ? document.querySelector(config.container)\n : config.container;\n\n if (!container) {\n throw new Error(`Container element not found: ${config.container}`);\n }\n\n const translations: MediaTranslations = await loadMediaTranslations(\n config.locale ?? \"en\",\n );\n\n applyTheme(container as HTMLElement, config.theme);\n\n // Unmount any prior app *after* awaits so concurrent init() calls don't\n // both pass an early check while appInstance is still null and orphan\n // the first-mounted app.\n if (appInstance) {\n unmount();\n }\n\n return new Promise((resolve, reject) => {\n try {\n appInstance = createApp({\n setup() {\n const onReady = () => {\n const instance: MediaLibraryInstance = {\n setTheme: (theme) => applyTheme(container as HTMLElement, theme),\n unmount,\n };\n\n resolve(instance);\n };\n\n return () =>\n h(MediaLibrary, {\n provider: config.provider,\n translations,\n onSelect: config.onSelect,\n accept: config.accept,\n locale: config.locale,\n ref: mediaLibraryRef,\n onReady,\n });\n },\n });\n\n appInstance.mount(container);\n } catch (error) {\n reject(error);\n }\n });\n}\n\nfunction unmount(): void {\n if (appInstance) {\n appInstance.unmount();\n appInstance = null;\n mediaLibraryRef.value = null;\n }\n}\n\nfunction applyTheme(\n container: HTMLElement,\n theme?: { primaryColor?: string; borderRadius?: number },\n): void {\n if (!theme) {\n return;\n }\n\n if (theme.primaryColor) {\n // `--tpl-user-*`: `.tpl` redeclares `--tpl-primary` from this override.\n // Writing `--tpl-primary` on the container is wiped by that declaration.\n container.style.setProperty(\"--tpl-user-primary\", theme.primaryColor);\n }\n\n if (theme.borderRadius !== undefined) {\n container.style.setProperty(\"--tpl-user-radius\", `${theme.borderRadius}px`);\n container.style.setProperty(\n \"--tpl-user-radius-sm\",\n `${Math.max(0, theme.borderRadius - 3)}px`,\n );\n container.style.setProperty(\n \"--tpl-user-radius-lg\",\n `${theme.borderRadius + 4}px`,\n );\n }\n}\n\nconst TemplaticalMedia = {\n init,\n unmount,\n};\n\nif (typeof window !== \"undefined\") {\n (\n window as unknown as Window & { TemplaticalMedia: typeof TemplaticalMedia }\n ).TemplaticalMedia = TemplaticalMedia;\n}\n\nexport { init, unmount };\n\nexport default TemplaticalMedia;\n"],"mappings":";;;;;;;;;;;;;;;;;;EAMA,IAAM8U,IAAQC,GAKRC,IAAOH,GAIP,EAAE,SAAMN,EAAQ,GAEhBU,IAAiBvE,QAAe;GACpC,IAAI,CAACoE,EAAM,iBAAiB,OAAO,CAAC;GACpC,IAAMI,IAA0B,CAAC;GAEjC,OADAC,EAAUL,EAAM,SAASA,EAAM,iBAAiBI,CAAI,GAC7CA;EACT,CAAC;EAED,SAASC,EACP,GACA,GACA,GACS;GACT,KAAK,IAAMC,KAAUC,GAAY;IAC/B,IAAID,EAAO,OAAOE,GAEhB,OADAJ,EAAK,KAAKE,CAAM,GACT;IAET,IAAID,EAAUC,EAAO,UAAUE,GAAUJ,CAAI,GAE3C,OADAA,EAAK,QAAQE,CAAM,GACZ;GAEX;GACA,OAAO;EACT;mBAKUH,EAAA,MAAe,SAAM,KAD7B9U,EAAA,GAAAC,EAwBM,OAxBNJ,IAwBM,CAnBJK,EAMS,UAAA;GALP,OAAM;GACN,OAAA,EAAAoU,OAAA,qBAAA;GACC,SAAK,AAAAe,EAAA,QAAA,MAAER,EAAI,YAAA,IAAA;EAET,GAAA1U,EAAAC,EAAAkV,CAAA,CAAC,CAAC,aAAa,QAAQ,GAAA,CAAA,IAE5BtV,EAAA,EAAA,GAAAC,EAWWI,GAAA,MAAAC,EAXyBwU,EAAA,QAAlB,GAAQ,OAAgCpE,EAAA,GAAA0E,EAAAjE,GAAA,EAAA,KAAA8D,EAAO,GAAA,GAAA,CAC/D1U,EAA6CH,EAAAoR,CAAA,GAAA;GAA9B,MAAM;GAAK,gBAAc;EAEhC,CAAA,GAAA+D,IAAQT,EAAA,MAAe,SAAM,KADrC9U,EAAA,GAAAC,EAOS,UAAA;;GALP,OAAM;GACN,OAAA,EAAAqU,OAAA,qBAAA;GACC,UAAK,MAAEO,EAAI,YAAaI,EAAO,EAAE;EAE/B,GAAA9U,EAAA8U,EAAO,IAAI,GAAA,GAAAnV,EAAA,MAEhBE,EAAA,GAAAC,EAAoE,QAApEF,IAAoEI,EAArB8U,EAAO,IAAI,GAAA,CAAA,EAAA,GAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;EE5DhE,IAAMN,IAAQC,GASRC,IAAOH,GAOP,EAAE,SAAMN,EAAQ,GAEhB8B,IAAa1D,EAAI,EAAK,GACtB2D,IAAa3D,EAAI,EAAK,GACtB4D,IAAc5D,EAAI,EAAE,GACpB6D,IAAsB7D,EAAI,EAAK,GAC/B8D,IAAgB9D,EAAI,EAAE,GACtB+D,IAAgB/D,EAAI,EAAK,GAIzBgE,IAAcjG,QAAeoE,EAAM,OAAO,SAAS,SAAS,CAAC,GAE7D8B,IAAqBlG,QAAeoE,EAAM,QAAQ,CAAa,GAE/D+B,IAAWnG,QAAeoE,EAAM,oBAAoBA,EAAM,OAAO,EAAE,GAEnEgC,IAAqBpG,QACpBoE,EAAM,kBACJiC,EAAejC,EAAM,OAAO,UAAUA,EAAM,eAAe,IAD/B,EAEpC,GAEKkC,IAAetG,QACb2F,EAAW,SAASS,EAAmB,KAC/C;EAEA,SAASC,EAAe,GAA4B,GAAqB;GACvE,KAAK,IAAM3B,KAAU6B,GAEnB,IADI7B,EAAO,OAAO8B,KACdH,EAAe3B,EAAO,UAAU8B,CAAE,GAAG,OAAO;GAElD,OAAO;EACT;EAEA,SAASC,IAAqB;GAC5B,EAAW,QAAQ,CAACd,EAAW;EACjC;EAEA,SAASe,IAAoB;GAE3B,AADA,EAAW,QAAQ,IACnB,EAAY,QAAQtC,EAAM,OAAO;EACnC;EAEA,SAASuC,IAAsB;GAC7B,IAAMC,IAAUf,EAAY,MAAM,KAAK;GAKvC,AAJIe,KAAWA,MAAYxC,EAAM,OAAO,QACtCE,EAAK,gBAAgBF,EAAM,OAAO,IAAIwC,CAAO,GAE/C,EAAW,QAAQ,IACnB,EAAY,QAAQ;EACtB;EAEA,SAASC,KAAqB;GAE5B,AADA,EAAW,QAAQ,IACnB,EAAY,QAAQ;EACtB;EAEA,SAASC,KAA6B;GAGpC,AAFA,EAAoB,QAAQ,IAC5B,EAAc,QAAQ,IACtB,EAAW,QAAQ;EACrB;EAEA,SAASC,KAA+B;GAKtC,AAJIhB,EAAc,MAAM,KAAK,KAC3BzB,EAAK,gBAAgByB,EAAc,MAAM,KAAK,GAAG3B,EAAM,OAAO,EAAE,GAElE,EAAoB,QAAQ,IAC5B,EAAc,QAAQ;EACxB;EAEA,SAAS4C,KAA8B;GAErC,AADA,EAAoB,QAAQ,IAC5B,EAAc,QAAQ;EACxB;EAEA,SAASC,IAA0B;GACjC,IAAIjB,EAAc,OAAO;IAEvB,AADA1B,EAAK,gBAAgBF,EAAM,OAAO,EAAE,GACpC,EAAc,QAAQ;IACtB;GACF;GACA,EAAc,QAAQ;EACxB;EAEA,SAAS8C,EAAc,GAAyB;GAC9C,IAAMC,IAAOC,EAAM;GACnB,AAAID,KAASC,EAAM,cAA8B,SAASD,CAAI,MAG9D,EAAc,QAAQ;EACxB;;;GAIE,OAAAtW,EAAA,GAAAC,EAgKM,OAAA,MAAA,CA9JJC,EAkHM,OAAA;IAjHJ,OAAM;IACL,OAAKC,EAAA;KAA4B,aAAA,GAAAqT,EAAA,QAAK,KAAA,EAAA;KAAwC,iBAAA8B,EAAA,QAAQ,yBAAA;KAA0D,OAAAA,EAAA,QAAQ,uBAAA;;IAKxJ,YAAUe;;IAIHjB,EAAA,SAAeH,EAAA,SADvBjV,EAAA,GAAAC,EAkBS,UAAA;;KAhBP,MAAK;KACL,OAAM;KACL,iBAAewV,EAAA;KACf,cAAuBA,EAAA,QAA2BrV,EAAA8T,CAAA,CAAC,CAAC,aAAa,iBAA6B9T,EAAA8T,CAAA,CAAC,CAAC,aAAa;KAK7G,SAAK7T,EAAOuV,GAAY,CAAA,MAAA,CAAA;IAEzBtV,GAAAA,CAAAA,EAKEF,EAAAgQ,CAAA,GAAA;KAJA,OAAK7P,EAAA,CAAC,6CAA2C,EAAA,iBACtBkV,EAAA,MAAY,CAAA,CAAA;KACtC,MAAM;KACN,gBAAc;IAGnBzV,GAAAA,MAAAA,GAAAA,CAAAA,OAAAA,CAAAA,CAAAA,GAAAA,GAAAA,EAAAA,MAAAA,EAAA,GAAAC,EAA+C,QAA/CV,EAA+C;IAG/CW,EASS,UAAA;KARP,MAAK;KACL,OAAM;KACL,SAAK,AAAA+T,EAAA,QAAA,MAAER,EAAI,YAAaD,EAAA,OAAO,EAAE;IAElClT,GAAAA,CAAAA,EAA8DF,EAAA8Q,CAAA,GAAA;KAAtD,OAAM;KAAgB,MAAM;KAAK,gBAAc;IACtC,CAAA,GAAA6D,EAAA,QAC0B/F,EAAA,IAAA,EAAA,KAAzChP,EAAA,GAAAC,EAAmD,QAAnDT,IAAmDgB,EAArBgT,EAAA,OAAO,IAAI,GAAA,CAAA,EAAA,CAAA;IAMrCuB,EAAA,QADRrU,GAAAV,EAAA,GAAAC,EAeE,SAAA;;KAbS,uBAAA,AAAAgU,EAAA,QAAA,MAAA,EAAW,QAAAwC;KACpB,MAAK;KACL,OAAM;KACN,OAAA;MAAA,gBAAA;MAAA,oBAAA;MAAAvD,OAAA;KAAA;KAKA,WAAA;KACC,WAAO,CAAQvS,EAAAmV,GAAa,CAAA,OAAA,CAAA,GACPnV,EAAAN,EAAA2V,IAAY,CAAA,MAAA,CAAA,GAAA,CAAA,QAAA,CAAA,CAAA;KACjC,QAAMF;KACN,SAAK,AAAA7B,EAAA,OAAA5T,QAAN,CAAA,GAAW,CAAA,MAAA,CAAA;IAZF,GAAA,MAAA,IAAAkU,EAAA,IAAA,CAAA,CAAA3T,GAAAoU,EAAA,KAAW,CAAA,CAAA,IAAAhG,EAAA,IAAA,EAAA;IAgBb+F,EAAA,qBADT/U,EAAA,GAAAC,EAoDO,QAAA;;KAlDL,OAAKM,EAAA,CAAC,6EACa+U,EAAA,SAAYH,EAAA,QAAA,oBAAA,8EAAA,CAAA;;KAOvBE,EAAA,SAAsB7B,EAAA,mBAD9BxT,EAAA,GAAAC,EASS,UAAA;;MAPP,MAAK;MACL,OAAM;MACL,OAAOG,EAAA8T,CAAA,CAAC,CAAC,aAAa;MACtB,cAAY9T,EAAA8T,CAAA,CAAC,CAAC,aAAa;MAC3B,SAAK7T,EAAO4V,IAAoB,CAAA,MAAA,CAAA;KAEjC3V,GAAAA,CAAAA,EAAqCF,EAAAqR,CAAA,GAAA;MAA9B,MAAM;MAAK,gBAAc;;KAG1B+B,EAAA,mBADRxT,EAAA,GAAAC,EASS,UAAA;;MAPP,MAAK;MACL,OAAM;MACL,OAAOG,EAAA8T,CAAA,CAAC,CAAC,aAAa;MACtB,cAAY9T,EAAA8T,CAAA,CAAC,CAAC,aAAa;MAC3B,SAAK7T,EAAOwV,GAAW,CAAA,MAAA,CAAA;KAExBvV,GAAAA,CAAAA,EAAuCF,EAAAmQ,CAAA,GAAA;MAA9B,MAAM;MAAK,gBAAc;;KAG5BiD,EAAA,mBADRxT,EAAA,GAAAC,EAsBS,UAAA;;MApBP,MAAK;MACL,eAAY;MACZ,OAAM;MACL,OAAoBkV,EAAA,QAA8B/U,EAAA8T,CAAA,CAAC,CAAC,aAAa,sBAAoC9T,EAAA8T,CAAA,CAAC,CAAC,aAAa;MAKpH,cAAyBiB,EAAA,QAA8B/U,EAAA8T,CAAA,CAAC,CAAC,aAAa,sBAAoC9T,EAAA8T,CAAA,CAAC,CAAC,aAAa;MAKzH,SAAK7T,EAAO+V,GAAiB,CAAA,MAAA,CAAA;KAE9B9V,GAAAA,CAAAA,EAIEF,EAAAS,EAAA,GAAA;MAHC,MAAM;MACN,gBAAc;MACf,OAAA,EAAAqS,OAAA,oBAAA;;;GAOG,GAAA,EAAA,GAAAuC,EAAA,SAAXzV,EAAA,GAAAC,EAwCM,OAAAJ,IAAA,EAvCJG,EAAA,EAAA,GAAAC,EAeEa,GAAA,MAAAC,EAdgByS,EAAA,OAAO,WAAhB,OADTxT,EAAA,GAAAgB,EAeEwV,GAAA;IAbC,KAAKE,EAAM;IACX,QAAQA;IACR,qBAAmBlD,EAAA;IACnB,OAAOA,EAAA,QAAK;IACZ,qBAAmBA,EAAA;IACnB,qBAAmBA,EAAA;IACnB,qBAAmBA,EAAA;IACnB,YAAQ,AAAAS,EAAA,QAAA,MAAER,EAAI,YAAagD,CAAM;IACjC,gBAAa,AAAAxC,EAAA,QAAc,GAAM,MAAaR,EAAI,gBAAiBkD,GAAMC,CAAQ;IAGjF,gBAAa,AAAA3C,EAAA,QAAG,GAAI,MAASR,EAAI,gBAAiBkC,GAAIgB,CAAI;IAC1D,gBAAa,AAAA1C,EAAA,QAAA,MAAER,EAAI,gBAAiBgD,CAAM;;;;;;;;MAKrC,GAAA,GAAA,IAAAxB,EAAA,SADRjV,EAAA,GAAAC,EAoBM,OAAA;;IAlBH,OAAKE,EAAA,EAAA,aAAA,IAAqBqT,EAAA,QAAK,KAAA,KAAA,EAAA,IAAA,CAAA;IAChC,OAAM;GAEN9S,GAAAA,CAAAA,EAAAR,EAcE,SAAA;IAbS,uBAAA,AAAA+T,EAAA,QAAA,MAAA,EAAa,QAAAwC;IACtB,MAAK;IACL,OAAM;IACN,OAAA;KAAA,gBAAA;KAAA,oBAAA;KAAAvD,OAAA;IAAA;IAKC,aAAa9S,EAAA8T,CAAA,CAAC,CAAC,aAAa;IAC7B,WAAA;IACC,WAAO,CAAQvT,EAAAuV,IAAsB,CAAA,OAAA,CAAA,GAChBvV,EAAAN,EAAA8V,IAAqB,CAAA,MAAA,CAAA,GAAA,CAAA,QAAA,CAAA,CAAA;IAC1C,QAAMD;GAZE,GAAA,MAAA,IAAAtB,EAAA,GAAA,CAAA,CAAAhU,GAAAsU,EAAA,KAAa,CAAA,CAAA,CAAA,GAAA,CAAA,KAAAlG,EAAA,IAAA,EAAA,CAAA,CAAA,KAAAA,EAAA,IAAA,EAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;EEnPhC,IAAMyE,IAAOH,GAQP,EAAE,SAAMN,EAAQ,GAEhBiE,IAAa7F,EAAI,EAAK,GACtB8F,IAAgB9F,EAAI,EAAE;EAE5B,SAAS+F,IAA0B;GAEjC,AADA,EAAW,QAAQ,IACnB,EAAc,QAAQ;EACxB;EAEA,SAASC,IAA4B;GAKnC,AAJIF,EAAc,MAAM,KAAK,KAC3BzD,EAAK,gBAAgByD,EAAc,MAAM,KAAK,CAAC,GAEjD,EAAW,QAAQ,IACnB,EAAc,QAAQ;EACxB;EAEA,SAASG,IAAqB;GAE5B,AADA,EAAW,QAAQ,IACnB,EAAc,QAAQ;EACxB;oBAIEhW,EAAA,GAAAC,EA6FM,OA7FNJ,IA6FM;GAxFJK,EAgBS,UAAA;IAfP,OAAM;IACL,OAAKC,EAAA;KAAuC,iBAAAgS,EAAA,aAAQ,WAAgBA,EAAA,oBAAe,OAAA,yBAAA;KAAqG,OAAAA,EAAA,aAAQ,WAAgBA,EAAA,oBAAe,OAAA,uBAAA;;IAU/N,SAAK,AAAAS,EAAA,QAAA,MAAER,EAAI,YAAA,IAAA;GAEZhS,GAAAA,CAAAA,EAAuCC,EAAAmP,CAAA,GAAA;IAAhC,MAAM;IAAK,gBAAc;GAAOlP,CAAAA,GAAAA,EAAA,MACvCC,EAAGF,EAAAwS,CAAA,CAAC,CAAC,aAAa,QAAQ,GAAA,CAAA,CAAA,GAAA,CAAA;IAI5B7S,EAAA,EAAA,GAAAC,EAaEO,GAAA,MAAAC,EAZiB0R,EAAA,UAAV,OADTnS,EAAA,GAAAU,EAaEC,IAAA;IAXC,KAAK6R,EAAO;IACJ;IACR,qBAAmBL,EAAA;IACnB,OAAO;IACP,qBAAmBA,EAAA;IACnB,qBAAmBA,EAAA;IACnB,qBAAmBA,EAAA;IACnB,YAAQ,AAAAS,EAAA,QAAA,MAAER,EAAI,YAAagD,CAAM;IACjC,gBAAa,AAAAxC,EAAA,QAAG,GAAM,MAAaR,EAAI,gBAAiBkD,GAAMC,CAAQ;IACtE,gBAAa,AAAA3C,EAAA,QAAG,GAAI,MAASR,EAAI,gBAAiBkC,GAAIgB,CAAI;IAC1D,gBAAa,AAAA1C,EAAA,QAAA,MAAER,EAAI,gBAAiBgD,CAAM;;;;;;;;GAIlCQ,EAAA,SAAX5V,EAAA,GAAAC,EAgBM,OAhBNH,IAgBM,CAfJc,EAAAV,EAcE,SAAA;IAbS,uBAAA,AAAA0S,EAAA,QAAA,MAAA,EAAa,QAAAwC;IACtB,MAAK;IACL,OAAM;IACN,OAAA;KAAA,gBAAA;KAAA,oBAAA;KAAAvD,OAAA;IAAA;IAKC,aAAaxR,EAAAwS,CAAA,CAAC,CAAC,aAAa;IAC7B,WAAA;IACC,WAAO,CAAQhS,EAAAkV,GAAmB,CAAA,OAAA,CAAA,GACblV,EAAAC,EAAAkV,GAAY,CAAA,MAAA,CAAA,GAAA,CAAA,QAAA,CAAA,CAAA;IACjC,QAAMD;GAZE,GAAA,MAAA,IAAAL,EAAA,GAAA,CAAA,CAAA3U,GAAA8U,EAAA,KAAa,CAAA,CAAA,CAAA,CAAA,KAAAlI,EAAA,IAAA,EAAA;GAkBjB,CAAAiI,EAAA,SAAczD,EAAA,mBADvBnS,EAAA,GAAAC,EAQS,UAAA;;IANP,OAAM;IACN,OAAA,EAAA4R,OAAA,wBAAA;IACC,SAAOiE;GAER1V,GAAAA,CAAAA,EAAuCC,EAAA+P,CAAA,GAAA;IAAhC,MAAM;IAAK,gBAAc;GAAO9P,CAAAA,GAAAA,EAAA,MACvCC,EAAGF,EAAAwS,CAAA,CAAC,CAAC,aAAa,SAAS,GAAA,CAAA,CAAA,CAAA,KAAAlF,EAAA,IAAA,EAAA;GAI7B,AAAAiF,EAAA,OAAA1S,EAA0B,OAAA,EAArB,OAAM,aAAY,GAAA,MAAA,EAAA;GAIfiS,EAAA,qBADRnS,EAAA,GAAAC,EAmBS,UAAA;;IAjBP,eAAY;IACZ,OAAM;IACL,OAAKE,EAAA;;KAAiF,iBAAAgS,EAAA,aAAQ,oBAAA,yBAAA;KAAkH,OAAAA,EAAA,aAAQ,oBAAA,uBAAA;;IAWxN,SAAK,AAAAS,EAAA,QAAA,MAAER,EAAI,oBAAA;GAEZhS,GAAAA,CAAAA,EAAuCC,EAAAoP,CAAA,GAAA;IAAhC,MAAM;IAAK,gBAAc;GAAOnP,CAAAA,GAAAA,EAAA,MACvCC,EAAGF,EAAAwS,CAAA,CAAC,CAAC,aAAa,cAAc,GAAA,CAAA,CAAA,GAAA,CAAA,KAAAlF,EAAA,IAAA,EAAA;;;;;;;EE3ItC,IAAMuE,IAAQC,GAWR+D,IAAgD;GACpD,mBAAmB;IACjB,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,2EAA2E;IACzE,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,sBAAsB;IACpB,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,qEAAqE;IACnE,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,4BAA4B;IAC1B,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,6EAA6E;IAC3E,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,iCAAiC;IAC/B,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,YAAY;IACV,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,cAAc;IACZ,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,aAAa;IACX,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,mBAAmB;IACjB,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,cAAc;IACZ,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,cAAc;IACZ,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,aAAa;IACX,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;GACA,aAAa;IACX,OAAO;IACP,OAAO;IACP,SAAS;IACT,MAAM;GACR;EACF,GAEMC,IAAiC;GACrC,OAAO;GACP,OAAO;GACP,SAAS;GACT,MAAM;EACR,GAEMC,IAAStI,QACSoI,EAAchE,EAAM,aAAaiE,CACzD;oBAIEjV,EAAA,GAAAC,EAuDM,OAAA;GAtDJ,OAAM;GACL,OAAKC,EAAA,EAAA,iBAAqBgV,EAAA,MAAO,QAAO,CAAA;EAGjC,GAAA,CAAAA,EAAA,MAAO,SAAI,cADnBlV,EAAA,GAAAC,EAcM,OAAA;;GAZJ,OAAM;GACN,QAAO;GACP,SAAQ;GACR,MAAK;GACL,QAAO;GACP,gBAAa;GACZ,OAAKC,EAAA,EAAA,OAAWgV,EAAA,MAAO,MAAK,CAAA;;GAE7B/U,EAAuE,QAAA,EAAjE,GAAE,6DAA4D,GAAA,MAAA,EAAA;GACpEA,EAAoC,YAAA,EAA1B,QAAO,iBAAgB,GAAA,MAAA,EAAA;GACjCA,EAAuC,QAAA;IAAjC,IAAG;IAAI,IAAG;IAAK,IAAG;IAAK,IAAG;;GAChCA,EAAuC,QAAA;IAAjC,IAAG;IAAI,IAAG;IAAK,IAAG;IAAK,IAAG;;EAIrB,CAAA,GAAA,CAAA,KAAA+U,EAAA,MAAO,SAAI,WADxBlV,EAAA,GAAAC,EAYM,OAAA;;GAVJ,OAAM;GACN,QAAO;GACP,SAAQ;GACR,MAAK;GACL,QAAO;GACP,gBAAa;GACZ,OAAKC,EAAA,EAAA,OAAWgV,EAAA,MAAO,MAAK,CAAA;EAE7B/U,GAAAA,CAAAA,GAAAA,AAAAA,EAAAA,OAAAA,CAAAA,EAAkD,QAAA;GAA5C,GAAE;GAAI,GAAE;GAAI,OAAM;GAAK,QAAO;GAAK,IAAG;EAC5CA,GAAAA,MAAAA,EAAAA,GAAAA,EAAuE,WAAA;GAA9D,QAAO;GAAmB,MAAK;GAAe,QAAO;EAGhEH,GAAAA,MAAAA,EAAAA,CAAAA,CAAAA,GAAAA,CAAAA,MAAAA,EAAA,GAAAC,EAaM,OAAA;;GAXJ,OAAM;GACN,QAAO;GACP,SAAQ;GACR,MAAK;GACL,QAAO;GACP,gBAAa;GACZ,OAAKC,EAAA,EAAA,OAAWgV,EAAA,MAAO,MAAK,CAAA;;GAE7B/U,EAA4B,QAAA,EAAtB,GAAE,kBAAiB,GAAA,MAAA,EAAA;GACzBA,EAA+B,UAAA;IAAvB,IAAG;IAAI,IAAG;IAAK,GAAE;;GACzBA,EAAgC,UAAA;IAAxB,IAAG;IAAK,IAAG;IAAK,GAAE;;EAG5BA,CAAAA,GAAAA,CAAAA,IAAAA,EAKO,QAAA;GAJL,OAAM;GACL,OAAKD,EAAA,EAAA,OAAWgV,EAAA,MAAO,MAAK,CAAA;EAE1B,GAAA9U,EAAA8U,EAAA,MAAO,KAAK,GAAA,CAAA,CAAA,GAAA,CAAA;;;;;AEnJrB,SAAgBC,GACd,GACA,GACA,GACQ;CACR,IAAMC,IAAS,IAAI,KAAKC,CAAG;CAC3B,IAAI,OAAO,MAAMD,EAAO,QAAQ,CAAC,GAAG,OAAO;CAE3C,IAAME,IAAMC,GAAQ,KAAK,CAAC,CAAC,QAAQ,MAAM,GAAG,KAAK,KAAA;CAEjD,IAAI;EACF,OAAOH,EAAO,mBAAmBE,GAAKE,CAAO;CAC/C,QAAQ;EACN,OAAOJ,EAAO,mBAAmB,KAAA,GAAWI,CAAO;CACrD;AACF;;;ACzCA,IAAMC,KAAQ;CAAC;CAAK;CAAM;CAAM;AAAI;AAGpC,SAAgBC,EAAe,GAAuB;CACpD,IAAIC,IAAQC,GACRC,IAAO;CACX,OAAOF,KAAS,QAAQE,IAAOJ,GAAM,SAAS,IAE5C,AADA,KAAS,MACT,KAAQ;CAGV,OAAO,GADS,KAAK,MAAME,IAAQ,EAAE,IAAI,GACvB,GAAGF,GAAMI;AAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECAA,IAAM7E,IAAQC,GAYRC,IAAOH,GASP,EAAE,uBAAoB,uBAAoBd,EAAmB;EAEnE,SAAS0H,EAAO,GAA0B;GACxC,OAAOC,EAAK,YAAY;EAC1B;EAEA,SAASC,EAAQ,GAA0B;GACzC,OAAOD,EAAK,gBAAgBA,EAAK;EACnC;EAEA,SAASE,EAAa,GAA2B;GAK/C,OAJI,CAAC9G,EAAM,UAAUA,EAAM,OAAO,WAAW,KAItC+G,EAAmBJ,EAAOC,CAAI,GAAG5G,EAAM,MAAM;EACtD;EAEA,SAASgH,EAAS,GAA2B;GAC3C,OAAOhH,EAAM,cAAc,MAAQ4G,EAAK,cAAc;EACxD;EAEA,SAASK,EAAY,GAA2B;GAC9C,OAAOjH,EAAM,eAAe,MAAQ4G,EAAK,cAAc;EACzD;EAEA,SAASM,EAAgB,GAAkB,GAAyB;GAClE,IAAIlE,EAAM,WAAWA,EAAM,SAAS;IAClC,EAAK,UAAU4D,EAAK,EAAE;IACtB;GACF;GACA,EAAK,UAAUA,CAAI;EACrB;EAEA,SAASO,EAAmB,GAAwB;GAClD,AAAIL,EAAaF,CAAI,KACnB1G,EAAK,WAAW0G,CAAI;EAExB;EAEA,SAASQ,EAAkB,GAAkB,GAA4B;GACvE,CAAIpE,EAAM,QAAQ,WAAWA,EAAM,QAAQ,SAG3CA,EAAM,eAAe,GACrBA,EAAM,gBAAgB,GACtB9C,EAAK,UAAU0G,CAAI;EACrB;EAEA,SAASS,EAAW,GAA2B;GAC7C,OAAOrH,EAAM,YAAY,IAAI4G,EAAK,EAAE;EACtC;EAEA,SAASU,EAAkB,GAA0B;GACnD,OAAOD,EAAWT,CAAI,IAClB,oBACA;EACN;EAEA,IAAM,EAAE,SAAMnH,EAAQ,GAChB8H,IAAWhL,EAAO2C,GAAe,IAAI,GAErCsI,IAAc3J,EAAwB,IAAI;EAEhD,GACE2J,IACC,CAAC,EAAE,yBAAsB;GACxB,AAAIC,KAAkBzH,EAAM,WAAW,CAACA,EAAM,aAC5CE,EAAK,UAAU;EAEnB,GACA,EAAE,WAAW,GAAI,CACnB;EAEA,SAASwH,EAAW,GAAyB;GAC3C,OAAOvD,GAAmBwD,GAASJ,GAAU,OAAO;IAClD,MAAM;IACN,OAAO;IACP,KAAK;GACP,CAAC;EACH;oBAIErW,EAAA,GAAAC,EAsRM,OAtRN7B,IAsRM;GApRI2Q,EAAA,aAAaA,EAAA,MAAM,WAAM,KADjC/O,EAAA,GAAAC,EAmBM,OAAA;;IAjBJ,MAAK;IACL,aAAU;IACT,cAAYC,EAAAuP,CAAA,CAAC,CAAC,aAAa;IAC3B,OAAKtP,EAAW4O,EAAA,WAAM,SAAA,oCAAA,oCAAA;GAMvB/O,GAAAA,EAAAA,EAAA,GAAAC,EAOEG,GAAA,MAAAC,EANY,IAAL,MADTC,EAOE,OAAA;IALC,KAAKoW;IACN,OAAKvW,EAAA,CAAC,4BACE4O,EAAA,WAAM,SAAA,aAAA,mBAAA,CAAA;IACd,OAAA,EAAA,oBAAA,sBAAA;IACA,eAAY;cAKH,GAAA,EAAA,EAAA,GAAA,IAAA8E,EAAA,KAAA9E,EAAA,MAAM,WAAM,KADzB/O,EAAA,GAAAC,EAeM,OAfN3B,IAeM,CAXJiC,EAKEL,EAAAkM,CAAA,GAAA;IAJA,OAAM;IACL,MAAM;IACN,gBAAc;IACf,OAAA,EAAAqC,OAAA,sBAAA;GAEFnO,CAAAA,GAAAA,EAII,KAJJ/B,IAIIiC,EAFAuO,EAAA,cAAc7O,EAAAuP,CAAA,CAAC,CAAC,aAAa,kBAAkBvP,EAAAuP,CAAA,CAAC,CAAC,aAAa,OAAO,GAAA,CAAA,CAAA,CAAA,KAM9DV,EAAA,WAAM,UAgHnB/O,EAAA,GAAAC,EA+GM,OAAA;;IA7GJ,MAAK;IACJ,cAAYC,EAAAuP,CAAA,CAAC,CAAC,aAAa;IAC5B,wBAAqB;IACrB,OAAM;GAENzP,GAAAA,EAAAA,EAAA,EAAA,GAAAC,EAuGMG,GAAA,MAAAC,EAtGW0O,EAAA,QAAR,OADT/O,EAAA,GAAAC,EAuGM,OAAA;IArGH,KAAKyV,EAAK;IACX,MAAK;IACL,UAAS;IACT,eAAY;IACZ,OAAKvV,EAAA,CAAC,6JAA2J,CAI5I,CAAAyV,EAAaF,CAAI,KAAA,CAAMS,EAAWT,CAAI,IAAA,mBAAA,EAAA,CAAA,CAAA;IAH1D,iBAAeA,EAAK;IACpB,iBAAeS,EAAWT,CAAI;IAC9B,cAAYA,EAAK,YAAYA,EAAK;IAIlC,OAAKjV,EAAA,EAA+B,iBAAA0V,EAAWT,CAAI,IAAA,wBAAA,cAAA,CAAA;IAKnD,UAAK,MAAEM,EAAgBN,GAAM1D,CAAM;IACnC,aAAQ,MAAEiE,EAAmBP,CAAI;IACjC,YAAO,MAAEQ,EAAkBR,GAAM1D,CAAM;;IAExC1R,EAcM,OAdNhB,IAcM,CATIY,EAAAyW,CAAA,CAAe,CAAClB,EAAOC,CAAI,CAAA,KADnC1V,EAAA,GAAAC,EAME,OAAA;;KAJC,KAAK0V,EAAQD,CAAI;KACjB,KAAKA,EAAK,YAAYA,EAAK;KAC5B,OAAM;KACN,SAAQ;IAEV1V,GAAAA,MAAAA,GAAAA,EAAAA,MAAAA,EAAA,GAAAC,EAEM,OAFNT,IAEM,CADJe,EAA2CI,IAAA,EAA3B,aAAW8U,EAAOC,CAAI,EAAA,GAAA,MAAA,GAAA,CAAA,WAAA,CAAA,CAAA,CAAA,EAAA,CAAA;IAI1CpV,EAsBM,OAtBNb,IAsBM,CArBJa,EAKI,KALJZ,IAKIc,EADCkV,EAAK,YAAYA,EAAK,GAAG,GAAA,CAAA,GAE9BpV,EAcI,KAdJX,IAcI;KAbc+V,EAAK,QAAI,OACCnL,EAAA,IAAA,EAAA,KAD1BvK,EAAA,GAAAC,EAEaG,GAAA,EAAA,KAAA,EAAA,GAAA,CADXU,EAAAN,EAAAN,EAAAsT,CAAA,CAAc,CAACkC,EAAK,IAAI,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA;KAEVA,EAAK,aAArB1V,EAAA,GAAAC,EAGWG,GAAA,EAAA,KAAA,EAAA,GAAA,CAFOsV,EAAK,QAAI,OAAoBnL,EAAA,IAAA,EAAA,KAA7CvK,EAAA,GAAAC,EAAwDG,GAAA,EAAA,KAAA,EAAA,GAAA,CAArBU,EAAA,KAAU,CAAA,GAAA,EAAA,IAAWA,EAAA,MACxDN,EAAGgW,EAAWd,EAAK,SAAS,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,KAAAnL,EAAA,IAAA,EAAA;KAGtBrK,EAAAyW,CAAA,CAAe,CAAClB,EAAOC,CAAI,CAAA,KAAMA,EAAK,SAASA,EAAK,UAD5D1V,EAAA,GAAAC,EAKWG,GAAA,EAAA,KAAA,EAAA,GAAA,CAHVU,EAAA,QAECN,EAAGkV,EAAK,KAAK,IAAG,MAAOlV,EAAGkV,EAAK,MAAM,GAAA,CAAA,CAAA,GAAA,EAAA,KAAAnL,EAAA,IAAA,EAAA;;IAK3CjK,EA4BM,OAAA,EA3BJ,OAAKH,EAAA,CAAC,8DACEiW,EAAkBV,CAAI,CAAA,CAAA,EAAA,GAAA,CAGtBI,EAASJ,CAAI,KADrB1V,EAAA,GAAAC,EAWS,UAAA;;KATP,MAAK;KACL,eAAY;KACZ,OAAM;KACN,OAAA,EAAAwO,OAAA,wBAAA;KACC,OAAOvO,EAAAuP,CAAA,CAAC,CAAC,aAAa;KACtB,cAAYvP,EAAAuP,CAAA,CAAC,CAAC,aAAa;KAC3B,SAAK5O,GAAA,MAAOmO,EAAI,QAAS0G,CAAI,GAAA,CAAA,MAAA,CAAA;IAE9BnV,GAAAA,CAAAA,EAAuCL,EAAA4L,CAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;IAG5B,CAAA,CAAA,GAAA,GAAAsJ,EAAA,KAAA7K,EAAA,IAAA,EAAA,GAAAwL,EAAYL,CAAI,KADxB1V,EAAA,GAAAC,EAWS,UAAA;;KATP,MAAK;KACL,eAAY;KACZ,OAAM;KACN,OAAA,EAAAwO,OAAA,wBAAA;KACC,OAAOvO,EAAAuP,CAAA,CAAC,CAAC,aAAa;KACtB,cAAYvP,EAAAuP,CAAA,CAAC,CAAC,aAAa;KAC3B,SAAK5O,GAAA,MAAOmO,EAAI,WAAY0G,CAAI,GAAA,CAAA,MAAA,CAAA;IAEjCnV,GAAAA,CAAAA,EAA0CL,EAAA0M,CAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;;IAKjCuJ,EAAWT,CAAI,KADvB1V,EAAA,GAAAC,EAUM,OAVNH,IAUM,CADJS,EAAsCL,EAAAmN,CAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;;oCA7NvCrN,EAAA,GAAAC,EA+GM,OAAA;;IA7GJ,MAAK;IACJ,cAAYC,EAAAuP,CAAA,CAAC,CAAC,aAAa;IAC5B,wBAAqB;IACrB,OAAM;GAENzP,GAAAA,EAAAA,EAAA,EAAA,GAAAC,EAuGMG,GAAA,MAAAC,EAtGW0O,EAAA,QAAR,OADT/O,EAAA,GAAAC,EAuGM,OAAA;IArGH,KAAKyV,EAAK;IACX,MAAK;IACL,UAAS;IACT,eAAY;IACZ,OAAKvV,EAAA,CAAC,6HAA2H;;KAI5E,CAAAyV,EAAaF,CAAI,KAAA,CAAMS,EAAWT,CAAI,IAAA,mBAAA;KAAqCS,EAAWT,CAAI,IAAA,6BAAA;;IAH9I,iBAAeA,EAAK;IACpB,iBAAeS,EAAWT,CAAI;IAC9B,cAAYA,EAAK,YAAYA,EAAK;IAMlC,OAAKjV,EAAA;KAA2B,aAAA0V,EAAWT,CAAI,IAAA,uBAAA;KAAkF,iBAAA,CAAAE,EAAaF,CAAI,KAAA,CAAMS,EAAWT,CAAI,IAAA,kBAAA;;IAOvK,UAAK,MAAEM,EAAgBN,GAAM1D,CAAM;IACnC,aAAQ,MAAEiE,EAAmBP,CAAI;IACjC,YAAO,MAAEQ,EAAkBR,GAAM1D,CAAM;;IAExC1R,EASM,OATN5B,IASM,CAPIwB,EAAAyW,CAAA,CAAe,CAAClB,EAAOC,CAAI,CAAA,KADnC1V,EAAA,GAAAC,EAME,OAAA;;KAJC,KAAK0V,EAAQD,CAAI;KACjB,KAAKA,EAAK,YAAYA,EAAK;KAC5B,OAAM;KACN,SAAQ;IAEV1V,GAAAA,MAAAA,GAAAA,EAAAA,MAAAA,EAAA,GAAAU,EAAkDC,IAAA;;KAA3B,aAAW8U,EAAOC,CAAI;;IAE/CpV,EAoBM,OApBN1B,IAoBM,CAnBJ0B,EAKI,KALJzB,IAKI2B,EADCkV,EAAK,YAAYA,EAAK,GAAG,GAAA,CAAA,GAE9BpV,EAYI,KAZJxB,IAYI,CARU4W,EAAK,QAAI,OACKnL,EAAA,IAAA,EAAA,KAD1BvK,EAAA,GAAAC,EAES,QAAAlB,IAAAyB,EADPN,EAAAsT,CAAA,CAAc,CAACkC,EAAK,IAAI,CAAA,GAAA,CAAA,IAGlBxV,EAAAyW,CAAA,CAAe,CAAClB,EAAOC,CAAI,CAAA,KAAMA,EAAK,SAASA,EAAK,UAD5D1V,EAAA,GAAAC,EAIO,QAAAjB,IAAAwB,EADFkV,EAAK,KAAK,IAAG,MAAOlV,EAAGkV,EAAK,MAAM,GAAA,CAAA,KAAAnL,EAAA,IAAA,EAAA,CAAA,CAAA,CAAA,CAAA;IAI3CjK,EAkCM,OAAA,EAjCJ,OAAKH,EAAA,CAAC,oGACEiW,EAAkBV,CAAI,CAAA,CAAA,EAAA,GAAA,CAGtBI,EAASJ,CAAI,KADrB1V,EAAA,GAAAC,EAcS,UAAA;;KAZP,MAAK;KACL,eAAY;KACZ,OAAM;KACN,OAAA;MAAA,oBAAA;MAAAwO,OAAA;KAAA;KAIC,OAAOvO,EAAAuP,CAAA,CAAC,CAAC,aAAa;KACtB,cAAYvP,EAAAuP,CAAA,CAAC,CAAC,aAAa;KAC3B,SAAK5O,GAAA,MAAOmO,EAAI,QAAS0G,CAAI,GAAA,CAAA,MAAA,CAAA;IAE9BnV,GAAAA,CAAAA,EAAuCL,EAAA4L,CAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;IAG5B,CAAA,CAAA,GAAA,GAAA2I,EAAA,KAAAlK,EAAA,IAAA,EAAA,GAAAwL,EAAYL,CAAI,KADxB1V,EAAA,GAAAC,EAcS,UAAA;;KAZP,MAAK;KACL,eAAY;KACZ,OAAM;KACN,OAAA;MAAA,oBAAA;MAAAwO,OAAA;KAAA;KAIC,OAAOvO,EAAAuP,CAAA,CAAC,CAAC,aAAa;KACtB,cAAYvP,EAAAuP,CAAA,CAAC,CAAC,aAAa;KAC3B,SAAK5O,GAAA,MAAOmO,EAAI,WAAY0G,CAAI,GAAA,CAAA,MAAA,CAAA;IAEjCnV,GAAAA,CAAAA,EAA0CL,EAAA0M,CAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;;IAIjCuJ,EAAWT,CAAI,KADvB1V,EAAA,GAAAC,EAUM,OAVNd,IAUM,CADJoB,EAAsCL,EAAAmN,CAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;;cAKvCrN,GAAAA,GAAAA,EAAAA,GAAAA,GAAAA,EAAAA;GAiHAM,EAAyC,OAAA;IAAhC,SAAA;IAAJ,KAAIgW;IAAc,OAAM;;GAGrBvH,EAAA,aAAaA,EAAA,MAAM,SAAM,KADjC/O,EAAA,GAAAC,EAUM,OAVNF,IAUM,CANJQ,EAKEL,EAAAiN,CAAA,GAAA;IAJA,OAAM;IACL,MAAM;IACN,gBAAc;IACf,OAAA,EAAAsB,OAAA,qBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EEjYR,IAAMK,IAAQC,GAMRC,IAAOH,GAKP,EAAE,SAAMN,EAAQ,GAChBgJ,IAAalM,EAAOwC,GAAc,IAAI,GACtC2J,IAAgBnM,EAAOuC,GAAoBjB,EAAwB,IAAI,CAAC,GAExE8K,IAAW9K,EAAI,EAAE;EAEvB,QACQmC,EAAM,UACX,MAAY;GACX,AAAI4I,MACF,EAAS,QAAQ;EAErB,CACF;EAEA,SAASC,IAAqB;GAC5B,IAAMC,IAAaH,EAAS,MAAM,KAAK;GACvC,AAAKG,KAAc,GAAM,eAIzB5I,EAAK,UAAU4I,CAAU;EAC3B;EAEA,SAASC,IAAoB;GAC3B,AAAK/I,EAAM,eACTE,EAAK,OAAO;EAEhB;EAEA,IAAM8I,IAAYnL,EAAwB,IAAI,GACxCoL,IAAarN,QAAeoE,EAAM,OAAO;EAC/C,GAAagJ,GAAWC,CAAU;EAElC,SAASC,EAAc,GAA4B;GAMjD,AALAlG,EAAM,gBAAgB,GAClBA,EAAM,QAAQ,YAChBA,EAAM,eAAe,GACrB6F,EAAa,IAEX7F,EAAM,QAAQ,YAChB+F,EAAY;EAEhB;oBAIEnW,EAAA,GAAAC,EA+GWC,GAAA,EA/GA,IAAIC,EAAA2V,CAAA,KAAa,OAAA,GAAA,CAC1B1V,EA6GaC,GAAA;GA5GX,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;GACf,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;;GAEf,SAAAC,QAoGM,CAnGE+M,EAAA,WADRrN,EAAA,GAAAO,EAoGM,OAAA;;IAlGH,kBAAgBJ,EAAA0V,CAAA;IAChB,OAAKrV,EAAA,CAAcL,EAAA2V,CAAA,IAAgB,KAAA,IAAS,OAAA,qFAAA,CAAA;IAI7C,OAAA,EAAA,oBAAA,qBAAA;IACC,SAAKrV,EAAO0V,GAAW,CAAA,MAAA,CAAA;IACvB,WAASG;GAEV5V,GAAAA,CAAAA,EAwFM,OAAA;IAvFA,SAAA;IAAJ,KAAI0V;IACJ,MAAK;IACL,cAAW;IACX,mBAAgB;IAChB,OAAM;IACN,OAAA,EAAA,oBAAA,yBAAA;;IAEA1V,EAMK,MANLnB,IAMKoB,EADAR,EAAA4N,CAAA,CAAC,CAAC,aAAa,aAAa,GAAA,CAAA;IAIjCrN,EAiBM,OAjBNlB,IAiBM,CAhBJoB,EAAAF,EAeE,SAAA;KAdS,uBAAA,AAAAoN,EAAA,QAAA,MAAA,EAAQ,QAAAwC;KACjB,MAAK;KACJ,cAAYnQ,EAAA4N,CAAA,CAAC,CAAC,aAAa;KAC3B,gBAAY,CAAA,CAAIV,EAAA;KAChB,oBAAkBA,EAAA,QAAK,2BAA8B,KAAA;KACtD,OAAM;KACN,OAAA;MAAA,gBAAA;MAAA,oBAAA;MAAAN,OAAA;KAAA;KAKC,aAAa5M,EAAA4N,CAAA,CAAC,CAAC,aAAa;KAC5B,UAAUV,EAAA;KACX,WAAA;IAbS,GAAA,MAAA,GAAAgI,EAAA,GAAA,CAAA,CAAAxU,GAAAkV,EAAA,KAAQ,CAAA,CAAA,CAAA,CAAA;IAkBb1I,EAAA,SADRrN,EAAA,GAAAO,EAQI,KARJb,IAQIiB,EADC0M,EAAA,KAAK,GAAA,CAAA,KAAAxE,EAAA,IAAA,EAAA;IAIVnI,EAwCM,OAxCNf,IAwCM,CAvCJe,EAcS,UAAA;KAbP,OAAKF,EAAA,CAAC,oIAAkI,EAOrE,yCAAA6M,EAAA,YAAA,CAAA,CAAA;KANnE,OAAA;MAAA,gBAAA;MAAAN,OAAA;MAAA,oBAAA;KAAA;KAKC,UAAUM,EAAA;KAIV,SAAO8I;IAEL,GAAAxV,EAAAR,EAAA4N,CAAA,CAAC,CAAC,aAAa,MAAM,GAAA,IAAAnO,EAAA,GAE1Bc,EAuBS,UAAA;KAtBP,OAAM;KACN,OAAA;MAAA,oBAAA;MAAAqM,OAAA;KAAA;KAIC,UAAQ,CAAGgJ,EAAA,MAAS,KAAI,KAAM1I,EAAA;KAC9B,SAAO4I;IAGA,GAAA,CAAA5I,EAAA,eADRrN,EAAA,GAAAO,EAUO,QAVPT,IAUO,CANLM,EAIED,EAAAsL,CAAA,GAAA;KAHA,OAAM;KACL,MAAM;KACN,gBAAc;IACf1K,CAAAA,GAAAA,EAAA,MACFJ,EAAGR,EAAA4N,CAAA,CAAC,CAAC,aAAa,SAAS,GAAA,CAAA,CAAA,CAAA,MAE7B/N,EAAA,GAAAO,EAEO,QAAAR,IAAAY,EADFR,EAAA4N,CAAA,CAAC,CAAC,aAAa,MAAM,GAAA,CAAA,EAAA,GAAA,GAAA0H,EAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;EE7JxC,IAAMnI,IAAOH,GAKP,EAAE,SAAMN,EAAQ,GAChB+J,IAAY3L,EAA2B,IAAI;EAQjD,SAAS4L,EACP,GACA,IAAgB,GACF;GACd,IAAMC,IAAuB,CAAC;GAC9B,KAAK,IAAMpJ,KAAU6B,GAEnB,AADAuH,EAAO,KAAK;IAAE,IAAIpJ,EAAO;IAAI,MAAMA,EAAO;IAAM;GAAM,CAAC,GACnDA,EAAO,SAAS,UAClBoJ,EAAO,KAAK,GAAGD,EAAenJ,EAAO,UAAUqJ,IAAQ,CAAC,CAAC;GAG7D,OAAOD;EACT;SAEApK,GAAekK,SAAiB;GAC9B,EAAK,OAAO;EACd,CAAC,cAICvV,EAAA,GAAAC,EA6CM,OAAA;GA5CA,SAAA;GAAJ,KAAIsV;GACJ,eAAY;GACZ,OAAM;GACN,OAAA;IAAA,gBAAA;IAAA,oBAAA;GAAA;EAKArV,GAAAA,CAAAA,EAmCM,OAnCNN,IAmCM,CAjCIoM,EAAA,oBAAe,OAMOxE,EAAA,IAAA,EAAA,KAP9BxH,EAAA,GAAAC,EAQS,UAAA;;GANP,OAAM;GACN,OAAA,EAAAyL,OAAA,kBAAA;GACC,SAAK,AAAAe,EAAA,QAAA,MAAER,EAAI,UAAA,IAAA;EAEZ9L,GAAAA,CAAAA,EAA4DC,EAAAiJ,CAAA,GAAA;GAAtD,OAAM;GAAgB,MAAM;GAAK,gBAAc;EAAOhJ,CAAAA,GAAAA,EAAA,MAC5DC,EAAGF,EAAAsM,CAAA,CAAC,CAAC,aAAa,UAAU,GAAA,CAAA,CAAA,CAAA,KAE9B1M,EAAA,EAAA,GAAAC,EAwBSO,GAAA,MAAAC,EAvBU+U,EAAexJ,EAAA,OAAO,IAAhC,OADThM,EAAA,GAAAC,EAwBS,UAAA;GAtBN,KAAKoM,EAAO;GACb,OAAM;GACL,OAAK3L,EAAA;IAA8B,aAAA,GAAA2L,EAAO,QAAK,KAAA,GAAA;IAA6C,OAAAA,EAAO,OAAOL,EAAA,kBAAA,wBAAA;IAA4G,SAAAK,EAAO,OAAOL,EAAA,kBAAe,KAAA;;GAQnP,UAAUK,EAAO,OAAOL,EAAA;GACxB,UAAK,MAAEC,EAAI,UAAWI,EAAO,EAAE;;GAEhClM,EAA8DC,EAAAsJ,CAAA,GAAA;IAAtD,OAAM;IAAgB,MAAM;IAAK,gBAAc;;GACvDxJ,EAAmD,QAAnDJ,IAAmDQ,EAArB+L,EAAO,IAAI,GAAA,CAAA;GAEjCA,EAAO,OAAOL,EAAA,mBADtBhM,EAAA,GAAAC,EAMO,QANPF,IAMOO,EADFF,EAAAsM,CAAA,CAAC,CAAC,aAAa,aAAa,GAAA,CAAA,KAAAlF,EAAA,IAAA,EAAA;;;;;;;;;;;;;;;;;;;;EE5EzC,IAAMuE,IAAQC,GAKRsH,IAAWhL,EAAO2C,GAAe,IAAI,GAErC,EAAE,uBAAoBD,EAAmB;EAE/C,SAASmL,EACP,GACA,GACA,IAAwB,CAAC,GACR;GACjB,KAAK,IAAM9J,KAAUC,GAAY;IAC/B,IAAM8J,IAAU,CAAC,GAAGC,GAAahK,EAAO,IAAI;IAC5C,IAAIA,EAAO,OAAOE,GAChB,OAAO6J;IAET,IAAI/J,EAAO,SAAS,QAAQ;KAC1B,IAAMiK,IAAQH,EAAgB9J,EAAO,UAAUE,GAAU6J,CAAO;KAChE,IAAIE,GAAO,OAAOA;IACpB;GACF;GACA,OAAO;EACT;EAEA,IAAMC,IAAa5O,QAAe;GAChC,IAAI,CAACoE,EAAM,KAAK,YAAY,CAACA,EAAM,SACjC,OAAO;GAET,IAAMI,IAAOgK,EAAgBpK,EAAM,SAASA,EAAM,KAAK,QAAQ;GAC/D,OAAOI,IAAOA,EAAK,KAAK,GAAG,IAAI;EACjC,CAAC,GAEKqK,IAAW7O,QAAeoE,EAAM,KAAK,YAAY,EAAE,GACnD0K,IAAU9O,QAAeiM,EAAgB4C,EAAS,KAAK,CAAC;EAE9D,SAAS/C,EAAW,GAAyB;GAC3C,OAAOvD,GAAmBwD,GAASJ,GAAU,OAAO;IAClD,MAAM;IACN,OAAO;IACP,KAAK;GACP,CAAC;EACH;EAEA,IAAMoD,IAAW/O,QAAeoE,EAAM,KAAK,YAAYA,EAAM,KAAK,GAAG;oBAInE7K,EAAA,GAAAC,EAmDM,OAnDNP,IAmDM,CAjDI6V,EAAA,SADRvV,EAAA,GAAAC,EAME,OAAA;;GAJC,KAAK6K,EAAA,KAAK;GACV,KAAKA,EAAA,KAAK,OAAO0K,EAAA;GAClB,OAAM;GACN,OAAA,EAAAZ,QAAA,8BAAA;EAEF5U,GAAAA,MAAAA,GAAAA,EAAAA,MAAAA,EAAA,GAAAC,EAMM,OANNL,IAMM,CADJM,EAAgEC,IAAA;GAAhD,aAAWmV,EAAA;GAAU,OAAM;EAE7ClV,GAAAA,MAAAA,GAAAA,CAAAA,WAAAA,CAAAA,CAAAA,CAAAA,IAAAA,EAmCM,OAnCNP,IAmCM;GAlCJO,EAKI,KALJN,IAKIO,EADCmV,EAAA,KAAQ,GAAA,CAAA;GAEbpV,EAmBI,KAnBJL,IAmBI;IAfc+K,EAAA,KAAK,QAAI,OACCxE,EAAA,IAAA,EAAA,KAD1BtG,EAAA,GAAAC,EAEaK,GAAA,EAAA,KAAA,EAAA,GAAA,CADXC,EAAAF,EAAAG,EAAA+O,CAAA,CAAc,CAACzE,EAAA,KAAK,IAAI,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA;IAEVA,EAAA,KAAK,aAArB9K,EAAA,GAAAC,EAGWK,GAAA,EAAA,KAAA,EAAA,GAAA,CAFOwK,EAAA,KAAK,QAAI,OAAoBxE,EAAA,IAAA,EAAA,KAA7CtG,EAAA,GAAAC,EAAwDK,GAAA,EAAA,KAAA,EAAA,GAAA,CAArBC,EAAA,KAAU,CAAA,GAAA,EAAA,IAAWA,EAAA,MACxDF,EAAGkS,EAAWzH,EAAA,KAAK,SAAS,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,KAAAxE,EAAA,IAAA,EAAA;IAEdiP,EAAA,SAAWzK,EAAA,KAAK,SAASA,EAAA,KAAK,UAA9C9K,EAAA,GAAAC,EAEWK,GAAA,EAAA,KAAA,EAAA,GAAA,CAF2CC,EAAA,QAC3CF,EAAGyK,EAAA,KAAK,KAAK,IAAG,MAAOzK,EAAGyK,EAAA,KAAK,MAAM,IAAG,OACnD,CAAA,CAAA,GAAA,EAAA,KAAAxE,EAAA,IAAA,EAAA;IACgB+O,EAAA,SAAhBrV,EAAA,GAAAC,EAIWK,GAAA,EAAA,KAAA,EAAA,GAAA;KAJiB,AAAAiL,EAAA,OAAAhL,EAAA,OAE1B,EAAA;KAAAL,EAAmEM,EAAAgI,CAAA,GAAA;MAA3D,OAAM;MAAwB,MAAM;MAAI,gBAAc;;KAAKjI,EAAA,MACnEF,EAAGgV,EAAA,KAAU,GAAA,CAAA;;;GAITE,EAAA,SADRvV,EAAA,GAAAC,EAOI,KAAA;;IALF,OAAKS,EAAA,CAAC,sDAAoD,EAAA,iBAAA,CAC9BoK,EAAA,KAAK,IAAG,CAAA,CAAA;IACpC,OAAA,EAAAN,OAAA,sBAAA;GAEG,GAAAnK,EAAAyK,EAAA,KAAK,OAAG,MAAA,GAAA,CAAA,KAAAxE,EAAA,IAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EEtGnB,IAAMuE,IAAQC,GAQRC,IAAOH,GAKP,EAAE,SAAMN,EAAQ,GAChBgJ,IAAalM,EAAOwC,GAAc,IAAI,GACtC2J,IAAgBnM,EAAOuC,GAAoBjB,EAAwB,IAAI,CAAC,GAExE6N,IAAe7N,EAA6B,IAAI,GAChD8N,IAAe9N,EAAiB,IAAI,GAEpC+N,IAAYhQ,QAAe;GAC/B,IAAI,CAACoE,EAAM,MACT,OAAO;GAGT,IAAM6L,KAAS7L,EAAM,KAAK,YAAY,GAAA,CAAI,MAAM,GAAG;GACnD,OAAO6L,EAAM,SAAS,IAAIA,EAAMA,EAAM,SAAS,EAAE,CAAC,YAAY,IAAI;EACpE,CAAC,GAEKC,IAAgBlQ,QACbgQ,EAAU,QAAQ,IAAIA,EAAU,UAAU,GAClD,GAEKG,IAAWnQ,SACPoE,EAAM,WAAW,iBAAiB,KAAK,CAChD;EAED,QACQA,EAAM,UACX,MAAY;GACX,AAAK4I,MACH,EAAa,QAAQ,MACjB8C,EAAa,UACf,EAAa,MAAM,QAAQ;EAGjC,CACF;EAEA,SAASM,EAAiB,GAAoB;GAC5C,IAAMC,IAAQjJ,EAAM;GACpB,AAAIiJ,EAAM,SAASA,EAAM,MAAM,SAAS,MACtC,EAAa,QAAQA,EAAM,MAAM;EAErC;EAEA,SAASC,IAAsB;GAC7B,AAAIP,EAAa,SACfzL,EAAK,WAAWyL,EAAa,KAAK;EAEtC;EAEA,IAAM3C,IAAYnL,EAAwB,IAAI,GACxCoL,IAAarN,QAAeoE,EAAM,WAAWA,EAAM,SAAS,IAAI;EACtE,GAAagJ,GAAWC,CAAU;EAElC,SAASC,EAAc,GAA4B;GAEjD,AADAlG,EAAM,gBAAgB,GAClBA,EAAM,QAAQ,YAChB9C,EAAK,OAAO;EAEhB;oBAIEtJ,EAAA,GAAAC,EA0IWC,GAAA,EA1IA,IAAIC,EAAA2R,CAAA,KAAa,OAAA,GAAA,CAC1B1R,EAwIaC,GAAA;GAvIX,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;GACf,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;;GAEf,SAAAC,QA+HM,CA9HE+I,EAAA,WAAWA,EAAA,QADnBrJ,EAAA,GAAAO,EA+HM,OAAA;;IA7HH,kBAAgBJ,EAAA0R,CAAA;IAChB,OAAKrR,EAAA,CAAcL,EAAA2R,CAAA,IAAgB,KAAA,IAAS,OAAA,qFAAA,CAAA;IAI7C,OAAA,EAAA,oBAAA,qBAAA;IACC,SAAK,AAAAhI,EAAA,OAAArJ,GAAA,MAAO6I,EAAI,OAAA,GAAA,CAAA,MAAA,CAAA;IAChB,WAASgJ;GAEV5R,GAAAA,CAAAA,EAmHM,OAAA;IAlHA,SAAA;IAAJ,KAAI0R;IACJ,MAAK;IACL,cAAW;IACX,mBAAgB;IAChB,OAAM;IACN,OAAA,EAAA,oBAAA,yBAAA;;IAEA1R,EAMK,MANLtB,IAMKuB,EADAR,EAAA4J,CAAA,CAAC,CAAC,aAAa,mBAAmB,GAAA,CAAA;IAIvCrJ,EAOI,KAPJrB,IAOIsB,EALAR,EAAA4J,CAAA,CAAC,CAAC,aAAa,sBAAsB,QAAA,eAA4D,IAAAiL,EAAA,OAAA,CAAA,GAAA,CAAA;IAS7FG,EAAA,SADRnV,EAAA,GAAAO,EAWI,KAXJjB,IAWIqB,EALAR,EAAA4J,CAAA,CAAC,CAAC,aAAa,wBAAwB,QAAA,WAAoDV,EAAA,UAAW,cAAc,SAAQ,CAAA,CAAA,GAAA,CAAA,KAAAxE,EAAA,IAAA,EAAA;IAQhInE,EAUM,OAVNnB,IAUM,CANJmB,EAKI,KALJlB,IAKImB,EADC0I,EAAA,KAAK,QAAQ,GAAA,CAAA,CAAA,CAAA;IAKpB3I,EAqBM,OArBNjB,IAqBM,CApBJiB,EAKQ,SALRhB,IAKQiB,EADHR,EAAA4J,CAAA,CAAC,CAAC,aAAa,iBAAiB,GAAA,CAAA,GAErCrJ,EAaE,SAAA;KAZI,SAAA;KAAJ,KAAIoU;KACJ,MAAK;KACJ,QAAQI,EAAA;KACR,gBAAY,CAAA,CAAI7L,EAAA;KAChB,oBAAkBA,EAAA,QAAK,4BAA+B,KAAA;KACvD,OAAM;KACN,OAAA;MAAA,gBAAA;MAAA,oBAAA;MAAAN,OAAA;KAAA;KAKC,UAAQqM;;IAKL/L,EAAA,SADRrJ,EAAA,GAAAO,EAQI,KARJX,IAQIe,EADC0I,EAAA,KAAK,GAAA,CAAA,KAAAxE,EAAA,IAAA,EAAA;IAIVnE,EA0BM,OA1BNb,IA0BM,CAzBJa,EAWS,UAAA;KAVP,OAAM;KACN,OAAA;MAAA,gBAAA;MAAAqI,OAAA;MAAA,oBAAA;KAAA;KAKC,UAAUM,EAAA;KACV,SAAK,AAAAS,EAAA,QAAA,MAAER,EAAI,OAAA;IAET,GAAA3I,EAAAR,EAAA4J,CAAA,CAAC,CAAC,aAAa,MAAM,GAAA,GAAAjK,EAAA,GAE1BY,EAYS,UAAA;KAXP,OAAM;KACN,OAAA;MAAA,oBAAA;MAAAqI,OAAA;KAAA;KAIC,UAAQ,CAAGgM,EAAA,SAAgB1L,EAAA;KAC3B,SAAOiM;IAGN,GAAA3U,EAAA0I,EAAA,cAAclJ,EAAA4J,CAAA,CAAC,CAAC,aAAa,YAAY5J,EAAA4J,CAAA,CAAC,CAAC,aAAa,OAAO,GAAA,GAAAhK,EAAA,CAAA,CAAA;;;;;;;;AEhN/E,SAAgBwV,GAAW,GAAiB,GAAyB;CAGnE,IAAM3H,IAAkC;EACtC,OAAO;EACP,MAAM;CACR;CACA,IAAI;EACF,OAAO,IAAI,KAAK,WAAWD,GAAQC,CAAO,CAAC,CAAC,OAAO4H,CAAK;CAC1D,SAASC,GAAO;EACd,IAAI,EAAEA,aAAiB,aACrB,MAAMA;EAER,OAAO,IAAI,KAAK,WAAW,KAAA,GAAW7H,CAAO,CAAC,CAAC,OAAO4H,CAAK;CAC7D;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECJA,IAAMlM,IAAOH,GAIP,EAAE,MAAG,cAAWN,EAAQ,GACxB8H,IAAWhL,EAAO2C,GAAe,IAAI,GAErC,EACJ,2BACA,uBACA,wBACA,gBACA,sBACED,EAAmB,GAEjB4N,IAAqD;GACzD,QAAQ;GACR,WAAW;GACX,QAAQ;GACR,OAAO;EACT,GAIMC,IAAelR,QAAe;GAClC,IAAMmR,IAASpM,EAAE,cACXqM,IAAUC,EAAmB,QAC/Bd,GACEe,EAAoB,MAAM,KACvB,MAAaH,EAAOF,EAAoBM,GAC3C,GACA5F,GAAU,KACZ,IACA5G,EAAE,aAAa;GAInB,OAHK,OAAO,SAASyM,EAAY,KAAK,IAG/BC,EAAO1M,EAAE,aAAa,wBAAwB;IACnD;IACA,MAAM+D,EAAe0I,EAAY,KAAK;GACxC,CAAC,IALQJ;EAMX,CAAC,GAEKM,IAAgBzP,EAAc,CAAC,CAAC,GAEhC0P,IAAc1P,EAAiB;EAErC,SAAS2P,EAAe,GAAmC;GACzD,IAAMC,IAAgB,CAAC,GACjBC,IAAqB,CAAC;GAC5B,KAAK,IAAMC,KAAQ,MAAM,KAAKC,CAAQ,GACpC,AAAIC,EAAeF,CAAI,IACrBF,EAAM,KAAKE,CAAI,IAEfD,EAAS,KAAKC,EAAK,IAAI;GAI3B,AADA,EAAc,QAAQD,GAClBD,EAAM,UACRvN,EAAK,UAAUuN,CAAK;EAExB;EAEA,IAAM,EAAE,sBAAmB9O,GAAY4O,GAAa,EAClD,SAAS,MAAU;GACjB,AAAIO,GAAO,UACTN,EAAeM,CAAK;EAExB,EACF,CAAC,GAEK,EAAE,MAAMC,GAAgB,gBAAa3O,EAAc;GACvD,QAAQ4O,EAAuB;GAC/B,UAAU;EACZ,CAAC;SAEDC,GAAU,MAAa;GACrB,AAAIL,GAAU,UACZJ,EAAeI,CAAQ;EAE3B,CAAC,cAICzV,EAAAA,GAAAA,EAAAA,GAAAA,MAAAA,CAAAA,EAkDS,UAAA;GAjDH,SAAA;GAAJ,KAAIoV;GACJ,MAAK;GACL,eAAY;GACZ,OAAKnV,EAAA,CAAC,qNACEC,EAAA6V,CAAA,IAAc,2BAAA,EAAA,CAAA;GACrB,cAAY7V,EAAAsI,CAAA,CAAC,CAAC,aAAa;GAC3B,oBAAkBV,EAAA,cAAc,KAAA,IAAS;GACzC,aAAWA,EAAA;GACZ,OAAA;IAAA,gBAAA;IAAA,oBAAA;GAAA;GAIC,SAAK,AAAAS,EAAA,QAAA,MAAErI,EAAA0V,CAAA,CAAc,CAAA;EAEX,GAAA,CAAA9N,EAAA,eAAXjI,EAAA,GAAAC,EAeM,OAfNN,IAeM,CAdJW,EAKED,EAAAgG,CAAA,GAAA;GAJA,OAAM;GACL,MAAM;GACN,gBAAc;GACf,OAAA,EAAAsB,OAAA,qBAAA;EAEFxH,CAAAA,GAAAA,EAOS,QAPTP,IAOSW,EANP0H,EAAA,kBAAkBA,EAAA,eAAe,QAAK,IAAiB5H,EAAAgV,CAAA,CAAM,CAAChV,EAAAsI,CAAA,CAAC,CAAC,aAAa,mBAAiB;GAA2B,SAAAV,EAAA,eAAe;GAA8B,OAAAA,EAAA,eAAe;EAAkC5H,CAAAA,IAAAA,EAAAsI,CAAA,CAAC,CAAC,aAAa,SAAS,GAAA,CAAA,CAAA,CAAA,MAQnP3I,EAAA,GAAAC,EAkBWC,GAAA,EAAA,KAAA,EAAA,GAAA;GAjBTI,EAKED,EAAAuF,CAAA,GAAA;IAJA,OAAM;IACL,MAAM;IACN,gBAAc;IACf,OAAA,EAAA+B,OAAA,sBAAA;;GAEFxH,EAEI,KAFJN,IAEIU,EADCF,EAAAsI,CAAA,CAAC,CAAC,aAAa,WAAW,GAAA,CAAA;GAE/BxI,EAOI,KAPJL,IAOIS,EADCuU,EAAA,KAAY,GAAA,CAAA;EAKb,GAAA,EAAA,EAAA,GAAA,IAAAR,EAAA,GAAAgB,EAAA,MAAc,UADtBtV,EAAA,GAAAC,EAYI,KAZJF,IAYIQ,EAJAF,EAAAgV,CAAA,CAAM,CAAChV,EAAAsI,CAAA,CAAC,CAAC,aAAa,gBAAc,EAAmB,OAAA2M,EAAA,MAAc,KAAI,IAAA,EAAA,CAAA,CAAA,GAAA,CAAA,KAAA7R,EAAA,IAAA,EAAA,CAAA,GAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EE1J/E,IAAMuE,IAAQC,GAMR,EAAE,MAAG,cAAWR,EAAQ,GACxBgP,IAAc5Q,EAAI,EAAK,GAEvB6Q,IAAO9S,QAAeoE,EAAM,QAAQ,EAAE,GACtC2O,IAAc/S,QAAe,KAAK,IAAI,GAAG8S,EAAK,QAAQ,CAAC,CAAC,GACxDE,IAAShT,SAAgB8S,EAAK,QAAQC,EAAY,SAAS,CAAC,GAC5DE,IAAgBjT,QAAe,IAAI,KAAK,KAAKgT,EAAO,KAAK,GAEzDE,IAAalT,QACboE,EAAM,cAAc,IAAU,IAC3B,KAAK,IAAI,KAAMA,EAAM,YAAYA,EAAM,aAAc,GAAG,CAChE,GAEK+O,IAAmBnT,QAChBiT,EAAc,QAASC,EAAW,QAAQ,MAAOD,EAAc,KACvE,GAEKG,IAAgBpT,QAChBkT,EAAW,SAAS,KACf,sBAELA,EAAW,SAAS,KACf,uBAEF,oBACR,GAEKG,IAAgBrT,QAAe8I,EAAe1E,EAAM,SAAS,CAAC,GAC9DkP,IAAiBtT,QAAe8I,EAAe1E,EAAM,UAAU,CAAC,GAChEmP,IAAiBvT,QACrB,KAAK,IAAI,GAAGoE,EAAM,aAAaA,EAAM,SAAS,CAChD,GACMoP,IAAqBxT,QAAe8I,EAAeyK,EAAe,KAAK,CAAC,GAExEE,IAAczT,QAClByR,EAAO1M,EAAE,aAAa,gBAAgB;GACpC,MAAMsO,EAAc;GACpB,OAAOC,EAAe;GACtB,WAAWE,EAAmB;EAChC,CAAC,CACH;oBAIErW,EAAA,GAAAC,EA6DM,OAAA;GA5DJ,eAAY;GACZ,OAAM;GACN,UAAS;GACR,cAAYqW,EAAA;GACZ,cAAU,AAAA3O,EAAA,QAAA,MAAE,EAAA,QAAW;GACvB,cAAU,AAAAA,EAAA,QAAA,MAAE,EAAA,QAAW;GACvB,SAAK,AAAAA,EAAA,QAAA,MAAE,EAAA,QAAW;GAClB,QAAI,AAAAA,EAAA,QAAA,MAAE,EAAA,QAAW;EAElB3H,GAAAA,EAAAA,EAAA,GAAAC,EA4BM,OAAA;GA3BH,OAAO0V,EAAA;GACP,QAAQA,EAAA;GACT,OAAM;GACL,SAAO,OAASA,EAAA,MAAI,GAAIA,EAAA;EAGzBzV,GAAAA,CAAAA,EAOE,UAAA;GANC,IAAIyV,EAAA,QAAI;GACR,IAAIA,EAAA,QAAI;GACR,GAAGE,EAAA;GACJ,MAAK;GACJ,gBAAcD,EAAA;GACf,OAAA,EAAAW,QAAA,oBAAA;EAGFrW,GAAAA,MAAAA,GAAAA,EAAAA,GAAAA,EAWE,UAAA;GAVC,IAAIyV,EAAA,QAAI;GACR,IAAIA,EAAA,QAAI;GACR,GAAGE,EAAA;GACJ,MAAK;GACJ,gBAAcD,EAAA;GACd,QAAQK,EAAA;GACT,kBAAe;GACd,oBAAkBH,EAAA;GAClB,qBAAmBE,EAAA;GACpB,OAAM;EAKV7V,GAAAA,MAAAA,GAAAA,EAAAA,CAAAA,GAAAA,GAAAA,EAAAA,IAAAA,EAmBaC,GAAA;GAlBX,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;GACf,sBAAmB;GACnB,oBAAiB;GACjB,kBAAe;;GAEf,SAAAC,QAUM,CATEqV,EAAA,SADR1V,EAAA,GAAAC,EAUM,OAVNF,IAUM,CALDO,EAAAC,EAAA+V,EAAA,KAAW,IAAG,KACjB,CAAA,GAAA,AAAA3O,EAAA,OAAAzH,EAGE,OAAA;IAFA,OAAM;IACN,OAAA,EAAA,oBAAA,kBAAA;;;;;;;;AE9FV,SAAgBsW,GAAY,GAA2C;CACrE,IAAMC,oBAAQ,IAAI,IAA6B;CAC/C,KAAK,IAAMlP,KAAU6B,GACnB,EAAM,IAAI7B,EAAO,IAAI;EAAE,GAAGA;EAAQ,UAAU,CAAC;CAAE,CAAC;CAGlD,IAAMmP,IAA2B,CAAC;CAClC,KAAK,IAAMC,KAAQF,EAAM,OAAO,GAAG;EACjC,IAAMnM,IAAWqM,EAAK,YAAY,MAC5BC,IAAStM,IAAWmM,EAAM,IAAInM,CAAQ,IAAI,KAAA;EAChD,AAAIsM,IACFA,EAAO,SAAS,KAAKD,CAAI,IAEzBD,EAAM,KAAKC,CAAI;CAEnB;CACA,OAAOD;AACT;;;ACyBA,SAAgBG,GACd,GACyB;CACzB,IAAM,EAAE,YAAS,oBAAiBpL;CAElC,SAASqL,IAAkB;EAKzB,OAJI,WAAWC,KAAgB,OAAOA,EAAa,SAAU,WACnDA,EACL,QAEEA;CACT;CAEA,IAAMC,IAAalS,EAAqB,MAAM,GACxCmS,IAAcnS,EAAI,EAAK,GACvBoS,IAAcpS,EAAI,EAAE,GACpBqS,IAAcrS,EAAuB,IAAI,GACzCsS,IAAqBtS,EAAI,EAAK,GAC9BuS,IAAiBvS,EAAI,EAAK,GAE1BwS,IAAczU,QAAe0U,EAAQ,YAAY,OAAO,OAAO,IAAI,GAEnEC,IAAoB3U,QACjB0U,EAAQ,oBAAoB,MAAM,SAAS,CACnD,GAEKE,IAAe5U,QACf0U,EAAQ,SAAS,UAAU,oBACtBA,EAAQ,oBAAoB,QAE9BA,EAAQ,MAAM,KACtB,GAEKG,IAAe7U,QACZ,OAAO,OAAO0U,EAAQ,gBAAgB,KAAK,CAAC,CAAC,MACjD,MAASI,EAAK,gBAAgB,CACjC,CACD,GAEKC,IAAa/U,QAAe2T,GAAYe,EAAQ,QAAQ,KAAK,CAAC,GAE9DM,IAA+C;EACnD,cAAcf,EAAgB,CAAC,CAAC,aAAa;EAC7C,iBAAiBA,EAAgB,CAAC,CAAC,aAAa;EAChD,cAAcA,EAAgB,CAAC,CAAC,aAAa;EAC7C,aAAaA,EAAgB,CAAC,CAAC,aAAa;CAC9C;CAEA,SAASgB,EAAiB,GAA0B;EAClD,OAAOD,EAAezD,EAAS,GAAG,KAAKA;CACzC;CAEA,EAAM6C,IAAc,MAAS;EAC3B,AAAIc,KACFR,EAAQ,YAAY;CAExB,CAAC;CAED,IAAMS,IAAkBnS,IAAe,MAAkB;EACvD,EAAQ,OAAO+F,CAAK;CACtB,GAAG,GAAG;CAEN,SAASqM,EAAkB,GAAqB;EAE9C,AADA,EAAY,QAAQrM,GACpBoM,EAAgBpM,CAAK;CACvB;CAEA,IAAM,EAAE,SAAM,cAAW3F,GAAa;EAAE,cAAc;EAAM,QAAQ;CAAK,CAAC;CAE1E,eAAeiS,EAAa,GAA8B;EACxD,MAAMX,EAAQ,YAAYxC,CAAK;CACjC;CAEA,SAASoD,EAAa,GAAwB;EAC5C,EAAQ,WAAWtK,CAAI;CACzB;CAEA,eAAeuK,EACb,GACA,GACe;EACf,MAAMb,EAAQ,aAAalN,GAAMC,CAAQ;CAC3C;CAEA,eAAe+N,EACb,GACA,GACe;EACf,MAAMd,EAAQ,aAAae,GAAUjO,CAAI;CAC3C;CAEA,eAAekO,EAAmB,GAAiC;EACjE,MAAMhB,EAAQ,aAAae,CAAQ;CACrC;CAEA,SAASE,EAAe,GAAwB;EAC9C,EAAY,QAAQ3K;CACtB;CAEA,eAAe4K,EACb,GACA,GACA,GACA,GAC8B;EAiB9B,OAdIC,KAKE,CAAC,MAJkBnB,EAAQ,qBAC7BoB,GACAD,EAAS,IACX,IAES,WAIN,MADiBnB,EAAQ,WAAWoB,GAAS/G,GAAUgH,CAAO,KAInE,EAAY,QAAQ,MACb,WAHEF,IAAW,aAAa;CAInC;CAEA,eAAeG,EAAoB,GAA4B;EAE7D,AAAI,MADiBtB,EAAQ,cAAcuB,CAAG,MAE5C,EAAmB,QAAQ;CAE/B;CAEA,eAAeC,EAAmB,GAAwC;EAExE,AADA,EAAe,QAAQ,IACvB,MAAMxB,EAAQ,aAAae,CAAQ;CACrC;CAEA,eAAexO,IAAmC;EAChD,MAAMyN,EAAQ,uBAAuB;CACvC;CAEA,SAASyB,EAAkB,GAAwB;EACjD,EAAQ,wBAAwBnL,CAAI;CACtC;CAEA,eAAeoL,EAAkB,GAA2B;EAC1D,MAAM1B,EAAQ,YAAY3C,CAAI;CAChC;CAEA,SAASsE,IAAgB;EAYvB,AAXA3B,EAAQ,eAAe,GACvBA,EAAQ,aAAa,GACrBA,EAAQ,cAAc,GACtB,EAAY,QAAQ,IAGpB,EAAQ,YAAY,QAAQ,IAC5B,EAAQ,eAAe,QAAQ,MAC/B,EAAQ,WAAW,QAAQ,UAC3B,EAAQ,SAAS,QAAQ,SACzB,EAAY,QAAQ,MACpB,EAAmB,QAAQ;CAC7B;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;ACtNA,SAAgB4B,GAAgB,GAAiC;CAC/D,IAAM,EAAE,gBAAa1N,GAEf4H,IAA2BvO,EAAI,CAAC,CAAC,GACjCsE,IAA8BtE,EAAI,CAAC,CAAC,GACpCsU,IAAsCtU,EAAI,IAAI,GAC9CuU,IAA+BvU,EAAI,OAAO,GAC1CwU,IAA2BxU,EAAI,EAAE,GACjCyU,IAA4CzU,EAAI,IAAI,GACpD0U,IAA0B1U,EAAI,QAAQ,GACtC2U,IAAY3U,EAAI,EAAK,GACrB4U,IAAc5U,EAAI,EAAK,GACvB6U,IAAU7U,EAAI,EAAK,GACnB8U,IAAiC9U,EAAI,IAAI,GACzC+U,IACJ/U,EAAI,IAAI,GACJgV,IAAkChV,kBAAI,IAAI,IAAI,CAAC,GAC/CiV,IAAsCjV,EAAI,IAAI,GAC9CkV,IAAyClV,EAAI,CAAC,CAAC,GAC/CmV,IAAuDnV,EAAI,CAAC,CAAC,GAC7DoV,IAAoBpV,EAAI,EAAK,GAC7BqV,IAAkCrV,EAAI,CAAC,CAAC,GACxCsV,IAAqBtV,EAAI,EAAK,GAC9BuV,IAAyCvV,EAAI,IAAI,GACjDwV,IAAcxV,EAAI,EAAK,GACvByV,IAAmCzV,EAAI,IAAI,GAC3C0V,IAAqB1V,EAAI,EAAK,GAC9B2V,IAA6C3V,EAAI,IAAI,GACrD4V,IAA+C5V,EAAI,IAAI,GACvD6V,IAA4C7V,EAAI,IAAI,GAKtD8V,IAAkB;CAMtB,SAASC,EAAO,GAAuB;EACrC,IAAI;GACF,EAAI;EACN,SAASvH,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,SAASwH,IAA+C;EACtD,OAAOC,EAAS,YAAY,KAAQ,OAAOA,EAAS;CACtD;CAOA,SAASC,IAAyC;EAChD,OAAO5V,GAAQqG,EAAQ,UAAU;CACnC;CAEA,SAASwP,EAAkB,GAAkC;EAC3D,IAAMC,IAA0B,CAAC;EASjC,AARI5B,EAAY,QACd,EAAO,SAASA,EAAY,QACnBF,EAAgB,SAAS,SAClC,EAAO,WAAWA,EAAgB,QAEhCG,EAAe,UACjB,EAAO,WAAWA,EAAe,QAE/B4B,MACF,EAAO,SAASA;EAElB,IAAMC,IAAaJ,EAAmB;EAItC,OAHII,MACF,EAAO,aAAaA,IAEfF;CACT;CAEA,eAAeG,IAA2B;EACxC,IAAMC,IAAY,EAAEV;EACpB,EAAU,QAAQ;EAClB,IAAMM,IAASD,EAAkB;EACjC,IAAI;GACF,IAAMM,IAAO,MAAMR,EAAS,KAAKG,CAAM;GACvC,IAAII,MAAcV,GAAiB;GAGnC,AAFA,EAAM,QAAQW,EAAK,OACnB,EAAW,QAAQA,EAAK,cAAc,MACtC,EAAQ,QAAQ,CAAC,CAACA,EAAK;EACzB,SAASjI,GAAO;GACd,IAAIgI,MAAcV,GAAiB;GACnC,EAAQ,UAAUtH,CAAc;EAClC,UAAU;GACR,AAAIgI,MAAcV,MAChB,EAAU,QAAQ;EAEtB;CACF;CAEA,eAAeY,IAA0B;EACvC,IAAI,CAAC7B,EAAQ,SAAS,CAACC,EAAW,SAASH,EAAU,OAAO;EAE5D,IAAM6B,IAAY,EAAEV;EACpB,EAAU,QAAQ;EAClB,IAAMM,IAASD,EAAkBrB,EAAW,KAAK;EACjD,IAAI;GACF,IAAM2B,IAAO,MAAMR,EAAS,KAAKG,CAAM;GACvC,IAAII,MAAcV,GAAiB;GAGnC,AAFA,EAAM,QAAQ,CAAC,GAAGvH,EAAM,OAAO,GAAGkI,EAAK,KAAK,GAC5C,EAAW,QAAQA,EAAK,cAAc,MACtC,EAAQ,QAAQ,CAAC,CAACA,EAAK;EACzB,SAASjI,GAAO;GACd,IAAIgI,MAAcV,GAAiB;GACnC,EAAQ,UAAUtH,CAAc;EAClC,UAAU;GACR,AAAIgI,MAAcV,MAChB,EAAU,QAAQ;EAEtB;CACF;CAEA,eAAea,EAAO,GAA8B;EAElD,AADA,EAAY,QAAQC,GACpB,MAAML,EAAU;CAClB;CAEA,eAAeM,EACb,GACe;EAEf,AADA,EAAe,QAAQvH,GACvB,MAAMiH,EAAU;CAClB;CAEA,eAAeO,EAAO,GAA+B;EAEnD,AADA,EAAW,QAAQC,GACnB,MAAMR,EAAU;CAClB;CAEA,eAAeS,GAAiB,GAAwC;EAMtE,AALA,EAAS,QAAQ,SACjB,EAAgB,QAAQxD,GACxB,EAAY,QAAQ,IACpB,EAAc,wBAAQ,IAAI,IAAI,GAC9B,EAAY,QAAQ,MACpB,MAAM+C,EAAU;CAClB;CAEA,eAAeU,KAAoC;EAMjD,AALA,EAAS,QAAQ,mBACjB,EAAgB,QAAQ,MACxB,EAAY,QAAQ,IACpB,EAAc,wBAAQ,IAAI,IAAI,GAC9B,EAAY,QAAQ,MACpB,MAAMC,EAAmB;CAC3B;CAEA,eAAeC,GAAW,GAAwC;EAChE,IAAM,EAAE,cAAWlB;EACnB,IAAI,OAAOmB,KAAW,YACpB,OAAO;EAGT,EAAY,QAAQ;EACpB,IAAI;GACF,IAAMhJ,IAA0B,EAAE,QAAK;GACvC,AAAIkG,EAAgB,SAAS,SAC3B,EAAM,WAAWA,EAAgB;GAEnC,IAAMgC,IAAaJ,EAAmB;GACtC,AAAII,MACF,EAAM,aAAaA;GAErB,IAAMe,IAAQ,MAAMD,EAAOhJ,CAAK;GAGhC,OAFA,EAAM,QAAQ,CAACiJ,GAAO,GAAG9I,EAAM,KAAK,GACpCwH,QAAaE,EAAS,YAAYoB,CAAK,CAAC,GACjCA;EACT,SAAS7I,GAAO;GAEd,OADA7H,EAAQ,UAAU6H,CAAc,GACzB;EACT,UAAU;GACR,EAAY,QAAQ;EACtB;CACF;CAEA,eAAe8I,EAAY,GAA8B;EACvD,IAAM,EAAE,cAAWrB;EACnB,IAAI,OAAOmB,KAAW,YAKtB;GADA,EAAY,QAAQ,IACpB,EAAe,QAAQ;IAAE,SAAS;IAAG,OAAOnH,EAAM;GAAO;GACzD,IAAI;IACF,KAAK,IAAIlG,IAAI,GAAGA,IAAIkG,EAAM,QAAQ,KAAK;KACrC,EAAe,QAAQ;MAAE,SAASlG,IAAI;MAAG,OAAOkG,EAAM;KAAO;KAC7D,IAAI;MACF,IAAM7B,IAA0B,EAAE,MAAM6B,EAAMlG,GAAG;MACjD,AAAIuK,EAAgB,SAAS,SAC3B,EAAM,WAAWA,EAAgB;MAEnC,IAAMgC,IAAaJ,EAAmB;MACtC,AAAII,MACF,EAAM,aAAaA;MAErB,IAAMe,IAAQ,MAAMD,EAAOhJ,CAAK;MAEhC,AADA,EAAM,QAAQ,CAACiJ,GAAO,GAAG9I,EAAM,KAAK,GACpCwH,QAAaE,EAAS,YAAYoB,CAAK,CAAC;KAC1C,SAAS7I,GAAO;MACd,EAAQ,UAAUA,CAAc;KAClC;IACF;GACF,UAAU;IAER,AADA,EAAY,QAAQ,IACpB,EAAe,QAAQ;GACzB;EAvByD;CAwB3D;CAEA,eAAe+I,GAAa,GAA8C;EACxE,IAAIvC,EAAc,MAAM,SAAS,GAC/B;EAIF,IAAM,EAAE,YADWgB,EACF,KAAc,EAAE,MAAM,GAAM;EAC7C,IAAI,OAAOwB,KAAS,YAIpB,IAAI;GACF,IAAMC,IAAa,MAAMD,EAAK,CAAC,GAAGxC,EAAc,KAAK,GAAG0C,CAAc;GACtE,IAAIpD,EAAgB,UAAU,MAAM;IAClC,IAAMqD,IAAW,IAAI,IAAIF,EAAW,KAAK,MAAS,CAAC1O,EAAK,IAAIA,CAAI,CAAC,CAAC;IAClE,EAAM,QAAQwF,EAAM,MAAM,KAAK,MAASoJ,EAAS,IAAI5O,EAAK,EAAE,KAAKA,CAAI;GACvE,OACE,EAAM,QAAQwF,EAAM,MAAM,QACvB,MAAS,CAACyG,EAAc,MAAM,IAAIjM,EAAK,EAAE,CAC5C;GAGF,AADA,EAAc,wBAAQ,IAAI,IAAI,GAC9B,EAAY,QAAQ;EACtB,SAASyF,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,eAAeoJ,EACb,GACA,GACA,GACkB;EAClB,IAAM,EAAE,cAAW3B;EACnB,IAAI,OAAO4B,KAAW,YACpB,OAAO;EAGT,IAAI;GACF,IAAMC,IAAyB,EAAE,YAAS;GAC1C,AAAIhE,MAAY,KAAA,MACd,EAAM,MAAMA;GAEd,IAAMiE,IAAU,MAAMF,EAAOhE,GAASiE,CAAK;GAQ3C,OAPA,EAAM,QAAQvJ,EAAM,MAAM,KAAK,MAC7BxF,EAAK,OAAO8K,IAAUkE,IAAUhP,CAClC,GACIkM,EAAY,OAAO,OAAOpB,MAC5B,EAAY,QAAQkE,IAEtBhC,QAAaE,EAAS,YAAY8B,CAAO,CAAC,GACnC;EACT,SAASvJ,GAAO;GAEd,OADA7H,EAAQ,UAAU6H,CAAc,GACzB;EACT;CACF;CAEA,SAASwJ,EAAmB,GAA6B;EACvD,IAAMC,oBAAW,IAAI,IAAwB;EAC7C,KAAK,IAAMlP,KAAQwF,EAAM,OACvB,AAAI2J,EAAI,SAASnP,EAAK,EAAE,KACtBkP,EAAS,IAAIlP,EAAK,IAAIA,CAAI;EAG9B,KAAK,IAAMA,KAAQmM,EAAoB,OACrC,AAAIgD,EAAI,SAASnP,EAAK,EAAE,KAAK,CAACkP,EAAS,IAAIlP,EAAK,EAAE,KAChDkP,EAAS,IAAIlP,EAAK,IAAIA,CAAI;EAG9B,OAAO,CAAC,GAAGkP,EAAS,OAAO,CAAC;CAC9B;CAEA,SAASE,EAAgB,GAAqB;EAM5C,AALA,EAAM,QAAQ5J,EAAM,MAAM,QAAQ,MAAS,CAAC2J,EAAI,SAASnP,EAAK,EAAE,CAAC,GACjE,EAAoB,QAAQmM,EAAoB,MAAM,QACnD,MAAS,CAACgD,EAAI,SAASnP,EAAK,EAAE,CACjC,GACA,EAAc,wBAAQ,IAAI,IAAI,GAC9B,EAAY,QAAQ;CACtB;CAEA,eAAeqP,IAAgC;EAC7C,IAAIpD,EAAc,MAAM,SAAS,GAAG;EAEpC,IAAM,EAAE,QAAQqD,MAAmBpC;EACnC,IAAI,OAAOoC,KAAmB,YAC5B;EAGF,IAAMH,IAAM,CAAC,GAAGlD,EAAc,KAAK,GAC7BiD,IAAWD,EAAmBE,CAAG;EAEvC,IAAI;GAEF,AADA,MAAMG,EAAeH,CAAG,GACxBC,EAAgBD,CAAG;GACnB,KAAK,IAAMI,KAASL,GAClB,QAAahC,EAAS,YAAYqC,CAAK,CAAC;EAE5C,SAAS9J,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,eAAe0I,IAAoC;EACjD,IAAM,EAAE,sBAAmBjB;EAC3B,IAAI,OAAOsC,KAAmB,YAI9B,IAAI;GACF,EAAoB,QAAQ,MAAMA,EAAe;EACnD,SAAS/J,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,eAAegK,IAA2C;EACxD,IAAIxD,EAAc,MAAM,SAAS,GAC/B,OAAO;EAGT,EAAiB,QAAQ,CAAC,GAAGA,EAAc,KAAK;EAEhD,IAAM,EAAE,kBAAeiB;EACvB,IAAI,OAAOwC,KAAe,YAGxB,OAFA,EAAgB,QAAQ,CAAC,GACzB,EAAkB,QAAQ,IACnB;EAGT,IAAI;GACF,IAAMC,IAAQ,MAAMD,EAAWpD,EAAiB,KAAK;GACrD,EAAgB,QAAQqD;GAExB,IAAMxK,IAAW,OAAO,OAAOwK,CAAK,CAAC,CAAC,MACnC,MAAS7F,EAAK,gBAAgB,CACjC;GAGA,OADA,EAAkB,QAAQ,IACnB3E;EACT,SAASM,GAAO;GAEd,OADA7H,EAAQ,UAAU6H,CAAc,GACzB;EACT;CACF;CAEA,eAAezK,KAA+B;EAC5C,IAAM,EAAE,QAAQsU,MAAmBpC;EAOnC,IANI,OAAOoC,KAAmB,eAI9B,EAAkB,QAAQ,IAEtBhD,EAAiB,MAAM,WAAW,IACpC;EAGF,IAAM6C,IAAM7C,EAAiB,OACvB4C,IAAWD,EAAmBE,CAAG;EAEvC,IAAI;GAIF,AAHA,MAAMG,EAAeH,CAAG,GACxBC,EAAgBD,CAAG,GACnB,EAAiB,QAAQ,CAAC,GAC1B,EAAgB,QAAQ,CAAC;GACzB,KAAK,IAAMI,KAASL,GAClB,QAAahC,EAAS,YAAYqC,CAAK,CAAC;EAE5C,SAAS9J,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,SAASmK,KAAqB;EAG5B,AAFA,EAAkB,QAAQ,IAC1B,EAAiB,QAAQ,CAAC,GAC1B,EAAgB,QAAQ,CAAC;CAC3B;CAEA,eAAeC,GAAc,GAAyC;EACpE,IAAMC,IAAY5C,EAAS;EAC3B,IAAI,OAAO4C,KAAc,YACvB,OAAO;EAIT,AADA,EAAmB,QAAQ,IAC3B,EAAmB,QAAQ;EAC3B,IAAI;GACF,IAAMxB,IAAQ,MAAMwB,EAClB7E,GACAM,EAAgB,OAChB4B,EAAmB,CACrB;GAGA,OAFA,EAAM,QAAQ,CAACmB,GAAO,GAAG9I,EAAM,KAAK,GACpCwH,QAAaE,EAAS,YAAYoB,CAAK,CAAC,GACjCA;EACT,SAAS7I,GAAO;GAId,OAHA,EAAmB,QACjBA,aAAiB,QAAQA,EAAM,UAAU,iBAC3C7H,EAAQ,UAAU6H,CAAc,GACzB;EACT,UAAU;GACR,EAAmB,QAAQ;EAC7B;CACF;CAEA,SAASsK,EAAgB,GAAkB;EACzC,IAAM5T,IAAO,IAAI,IAAI8P,EAAc,KAAK;EAMxC,AALI9P,EAAK,IAAIX,CAAE,IACbW,EAAK,OAAOX,CAAE,IAEdW,EAAK,IAAIX,CAAE,GAEb,EAAc,QAAQW;CACxB;CAEA,SAAS6T,IAAuB;EAE9B,AADA,EAAc,wBAAQ,IAAI,IAAI,GAC9B,EAAY,QAAQ;CACtB;CAEA,SAASC,GAAW,GAAwB;EAE1C,AADA,EAAY,QAAQjQ,GACpB,EAAc,wBAAQ,IAAI,IAAI,CAACA,EAAK,EAAE,CAAC;CACzC;CAEA,eAAekQ,IAA6B;EAC1C,IAAM,EAAE,eAAYhD;EACpB,IAAI,OAAOiD,KAAY,YAAY;GACjC,EAAY,QAAQ;GACpB;EACF;EAEA,IAAI;GACF,EAAY,QAAQ,MAAMA,EAAQ;EACpC,SAAS1K,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,eAAe2K,IAA6B;EAC1C,IAAMC,IAAapD,EAAgB;EACnC,IAAKoD,GAIL,IAAI;GACF,EAAQ,QAAQ,MAAMA,EAAW,KAAK;EACxC,SAAS5K,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,eAAe6K,GACb,GACA,GAC6B;EAE7B,IAAM,EAAE,cADWrD,EACA,KAAc,EAAE,QAAQ,GAAM;EACjD,IAAI,OAAOoB,KAAW,YACpB,OAAO;EAGT,IAAI;GACF,IAAMhJ,IAA0B,EAAE,QAAK;GACvC,AAAI5I,MAAa,KAAA,MACf,EAAM,WAAWA;GAEnB,IAAM/C,IAAS,MAAM2U,EAAOhJ,CAAK;GAEjC,OADA,MAAM+K,EAAY,GACX1W;EACT,SAAS+L,GAAO;GAEd,OADA7H,EAAQ,UAAU6H,CAAc,GACzB;EACT;CACF;CAEA,SAAS8K,EACP,GACA,GACoB;EACpB,KAAK,IAAM7W,KAAUC,GACnB,IAAID,EAAO,OAAO8B,GAAI,OAAO9B;EAE/B,OAAO;CACT;CAEA,eAAe8W,GAAa,GAAkB,GAA6B;EAEzE,IAAM,EAAE,cADWvD,EACA,KAAc,EAAE,QAAQ,GAAM;EACjD,IAAI,OAAO6B,KAAW,YAItB,IAAI;GAEF,AADA,MAAMA,EAAOrE,GAAU,EAAE,QAAK,CAAC,GAC/B,MAAM2F,EAAY;EACpB,SAAS3K,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,eAAegL,GAAa,GAAiC;EAE3D,IAAM,EAAE,QAAQC,MADGzD,EACU,KAAc,EAAE,QAAQ,GAAM;EAC3D,IAAI,OAAOyD,KAAa,YAIxB,IAAI;GAEF,IAAMjU,IADS8T,EAAiBhV,EAAQ,OAAOkP,CAC9B,CAAA,EAAQ,YAAY;GASrC,AAPA,MAAMiG,EAASjG,CAAQ,GAEnBc,EAAgB,UAAUd,MAC5B,EAAgB,QAAQhO,IAG1B,MAAM2T,EAAY,GAClB,MAAM5C,EAAU;EAClB,SAAS/H,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,eAAekL,GAAwB,GAAiC;EAEtE,AADA,EAAmB,QAAQ3Q,GAC3B,EAAa,QAAQ;EAErB,IAAM,EAAE,kBAAekN;EACvB,IAAI,OAAOwC,KAAe,YAAY;GAEpC,AADA,EAAiB,QAAQ,MACzB,EAAmB,QAAQ;GAC3B;EACF;EAEA,IAAI;GACF,IAAMC,IAAQ,MAAMD,EAAW,CAAC1P,EAAK,EAAE,CAAC;GAExC,AADA,EAAiB,QAAQ2P,EAAM3P,EAAK,OAAO,MAC3C,EAAmB,QAAQ;EAC7B,SAASyF,GAAO;GACd,EAAQ,UAAUA,CAAc;EAClC;CACF;CAEA,SAASmL,KAAsB;EAI7B,AAHA,EAAmB,QAAQ,IAC3B,EAAmB,QAAQ,MAC3B,EAAiB,QAAQ,MACzB,EAAa,QAAQ;CACvB;CAEA,SAASC,EAAc,GAA2B;EAahD,AAZA,EAAM,QAAQrL,EAAM,MAAM,KAAK,MAC7BxF,EAAK,OAAOgP,EAAQ,KAAKA,IAAUhP,CACrC,GAEA,EAAoB,QAAQmM,EAAoB,MAAM,KAAK,MACzDnM,EAAK,OAAOgP,EAAQ,KAAKA,IAAUhP,CACrC,GAEIkM,EAAY,OAAO,OAAO8C,EAAQ,OACpC,EAAY,QAAQA,IAGtBhC,QAAaE,EAAS,YAAY8B,CAAO,CAAC;CAC5C;CAEA,eAAe8B,GAAY,GAAwC;EACjE,IAAM,EAAE,eAAY5D;EACpB,IAAI,OAAO6D,KAAY,cAAc,CAACnE,EAAmB,OACvD,OAAO;EAIT,AADA,EAAY,QAAQ,IACpB,EAAa,QAAQ;EAErB,IAAI;GACF,IAAMoC,IAAU,MAAM+B,EAAQnE,EAAmB,MAAM,IAAI7F,CAAI;GAO/D,OANA8J,EAAc7B,CAAO,GAErB,EAAmB,QAAQ,IAC3B,EAAmB,QAAQ,MAC3B,EAAiB,QAAQ,MAElBA;EACT,SAASvJ,GAAO;GAId,OAHA,EAAa,QACXA,aAAiB,QAAQA,EAAM,UAAU,kBAC3C7H,EAAQ,UAAU6H,CAAc,GACzB;EACT,UAAU;GACR,EAAY,QAAQ;EACtB;CACF;CAEA,eAAeuL,GACb,GACA,GAC4B;EAC5B,IAAM,EAAE,eAAY9D;EACpB,IAAI,OAAO6D,KAAY,YACrB,OAAO;EAGT,IAAI;GACF,IAAM/B,IAAU,MAAM+B,EAAQjG,GAAS/D,CAAI;GAE3C,OADA8J,EAAc7B,CAAO,GACdA;EACT,SAASvJ,GAAO;GAEd,OADA7H,EAAQ,UAAU6H,CAAc,GACzB;EACT;CACF;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECtrBA,IAAMrM,IAAQC,GAWRC,IAAOH,GAMPqa,IAAiB9d,QACf,OAAO,sCACf,GAEMqE,IAAI/E,QAAeoE,EAAM,YAAY,GACrCqa,IAAwBze,QACtBoE,EAAM,iBAAiB,eAC/B,GAEMsa,IAAc;GAClB,IAAI,cAAc;IAChB,OAAOta,EAAM,SAAS;GACxB;GACA,IAAI,YAAY;IACd,OAAOA,EAAM,SAAS;GACxB;GACA,IAAI,SAAS;IACX,OAAOA,EAAM;GACf;EACF;EACA,EAAQnB,IAAkByb,CAAW;EAErC,IAAM,EAAE,uBAAoB,2BAC1Brb,EAAmBqb,CAAW,GAE1BhK,IAAU4B,GAAgB;GAC9B,UAAUlS,EAAM;GAChB,kBAAkBA,EAAM;GACxB,UAAU,MAAUA,EAAM,UAAUqM,CAAK;EAC3C,CAAC,GAEKkO,IAAK3K,GAAkB;GAC3B;GACA,cAAcjP;EAChB,CAAC,GAEK6Z,IAAY5e,QAAe,OAAOoE,EAAM,SAAS,UAAW,UAAU,GACtEya,IAAa7e,QAAeoE,EAAM,SAAS,YAAY,EAAK,GAC5D0a,IAAY9e,QACV,OAAOoE,EAAM,SAAS,iBAAkB,UAChD,GACM2a,KAAY/e,QAAe,OAAOoE,EAAM,SAAS,UAAW,UAAU,GACtE4a,KAAYhf,QAAe,OAAOoE,EAAM,SAAS,UAAW,UAAU,GACtE6a,IAAajf,QAAe,OAAOoE,EAAM,SAAS,WAAY,UAAU,GACxE8a,KAAoBlf,QAClB,OAAOoE,EAAM,SAAS,kBAAmB,UACjD,GACM+a,IAAUnf,QAAe;GAC7B,IAAMuG,IAAUnC,EAAM,SAAS;GAC/B,OAAOmC,MAAY,MAAS,OAAOA,EAAQ,QAAS;EACtD,CAAC,GACK6Y,IAAkBpf,QAAe;GACrC,IAAMuG,IAAUnC,EAAM,SAAS;GAC/B,OAAOmC,MAAY,MAAS,OAAOA,EAAQ,UAAW;EACxD,CAAC,GACK8Y,KAAkBrf,QAAe;GACrC,IAAMuG,IAAUnC,EAAM,SAAS;GAC/B,OAAOmC,MAAY,MAAS,OAAOA,EAAQ,UAAW;EACxD,CAAC,GACK+Y,KAAkBtf,QAAe;GACrC,IAAMuG,IAAUnC,EAAM,SAAS;GAC/B,OAAOmC,MAAY,MAAS,OAAOA,EAAQ,UAAW;EACxD,CAAC,GAEKgZ,IAAgBvf,QACd0U,EAAQ,UAAU,SAASiK,EAAG,aAAa,MAAM,WAAW,CACpE,GAEMa,IAAgBxf,QAAe;GACnC,IAAMgL,IAAO0J,EAAQ,YAAY;GAOjC,OANK1J,IAGL,CAAK5G,EAAM,QAAQ,UAGZ+G,EAAmBH,EAAK,YAAY,IAAI5G,EAAM,MAAM,IALlD;EAMX,CAAC,GAEKqb,KAAyBzf,QAAe;GAC5C,IAAI,CAAC+e,GAAU,OACb,OAAO;GAET,IAAIW,IAAQ;GACZ,KAAK,IAAMlZ,KAAMkO,EAAQ,cAAc,OAErC,AADaiK,EAAG,aAAa,MAAM,MAAM,MAAUgB,EAAM,OAAOnZ,CAC5D,CAAA,EAAM,cAAc,OACtB,KAAS;GAGb,OAAOkZ;EACT,CAAC,GAEKE,IAAmB5f,QAErB2e,EAAG,mBAAmB,SACtBA,EAAG,YAAY,UAAU,QACzBjK,EAAQ,mBAAmB,KAC/B;EAEA,SAASmL,KAAwB;GAY/B,OAXID,EAAiB,QACZ,KAELjB,EAAG,eAAe,SACpB,EAAG,eAAe,QAAQ,IACnB,MAELjK,EAAQ,kBAAkB,SAC5BA,EAAQ,aAAa,GACd,MAEF;EACT;EAEA,SAASoL,IAAyB;GAChC,AAAKN,EAAc,SAAU9K,EAAQ,YAAY,SAGjDpQ,EAAK,WAAWoQ,EAAQ,YAAY,KAAK;EAC3C;EAEA,SAASqL,GAAqB,GAAoB;GAEhD,EAAQ,iBADO3Y,EAAM,OAA6B,SACU,IAAI;EAClE;EAEA,eAAeH,KAAmC;GAChD,IAAME,oBAAO,IAAI,IAAY;GAC7B,KAAK,IAAMX,KAAMkO,EAAQ,cAAc,OAErC,AADaiK,EAAG,aAAa,MAAM,MAAM,MAAUgB,EAAM,OAAOnZ,CAC5D,CAAA,EAAM,cAAc,MACtBW,EAAK,IAAIX,CAAE;GAIf,AADA,EAAQ,cAAc,QAAQW,GAC9B,MAAMwX,EAAG,kBAAkB;EAC7B;SAEAve,QACQgE,EAAM,SACX,MAAW;GACV,AAAI4b,GAAQ,WAAW,MACrB,EAAQ,eAAe,QAAQA,EAAO;EAE1C,GACA,EAAE,WAAW,GAAK,CACpB,GAEAtL,EAAQ,UAAU,GAClBA,EAAQ,mBAAmB,GAC3BA,EAAQ,YAAY,GACpBpQ,EAAK,OAAO,GAEZxE,QAAsB;GACpB,EAAG,QAAQ;EACb,CAAC,GAEDye,EAAa;GAAE;GAAkB;GAAc;GAAS;EAAG,CAAC,cAI1D1gB,EAAA,GAAAC,EA+fM,OA/fNme,IA+fM;GA3fJle,EAqDM,OArDNme,IAqDM,CAjDJne,EAMK,MANLoe,IAMKne,EADA+G,EAAA,MAAE,aAAa,KAAK,GAAA,CAAA,GAEzBhH,EAyCM,OAzCNqe,IAyCM;IAvCIne,EAAAyW,CAAA,CAAO,CAAC,YAAY,SAD5B7W,EAAA,GAAAK,EAKEC,IAAA;;KAHC,cAAYF,EAAAyW,CAAA,CAAO,CAAC,YAAY,MAAM;KACtC,eAAazW,EAAAyW,CAAA,CAAO,CAAC,YAAY,MAAM;KACvC,MAAM;;IAET3W,EAuBM,OAvBNse,IAuBM,CAtBJte,EAeE,SAAA;KAdC,OAAOE,EAAA0gB,CAAA,CAAE,CAAC,YAAY;KACvB,MAAK;KACJ,UAAUY,EAAA;KACV,cAAYxa,EAAA,MAAE,aAAa;KAC5B,OAAM;KACN,OAAA;MAAA,gBAAA;MAAA,oBAAA;MAAAhB,OAAA;KAAA;KAKC,aAAagB,EAAA,MAAE,aAAa;KAC5B,SAAK,AAAAD,EAAA,QAAA,MAAiB7G,EAAA0gB,CAAA,CAAE,CAAC,kBAAmBrX,EAAO,OAA4B,KAAK;IAIvFjJ,GAAAA,MAAAA,IAAAA,EAAAA,GAAAA,EAKEJ,EAAAiD,CAAA,GAAA;KAJA,OAAM;KACL,MAAM;KACN,gBAAc;KACf,OAAA,EAAA6C,OAAA,sBAAA;;IAIIM,EAAA,aADRxG,EAAA,GAAAC,EASS,UAAA;;KAPP,MAAK;KACJ,cAAYiH,EAAA,MAAE,aAAa;KAC5B,OAAM;KACN,OAAA,EAAAhB,OAAA,wBAAA;KACC,SAAK,AAAAe,EAAA,QAAA,MAAER,EAAI,OAAA;IAEZjG,GAAAA,CAAAA,EAAkCJ,EAAAuE,EAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;;;GAMnCzE,EA0NM,OA1NNye,IA0NM,CAzNJne,EAiCaC,GAAA;IAhCX,sBAAmB;IACnB,oBAAiB;IACjB,kBAAe;IACf,sBAAmB;IACnB,oBAAiB;IACjB,kBAAe;;IAEf,SAAAC,QAwBM,CAvBEsgB,EAAA,SAAc5gB,EAAA0gB,CAAA,CAAE,CAAC,YAAY,SADrC9gB,EAAA,GAAAC,EAwBM,OAxBN2e,IAwBM,CAhBJpe,EAeEG,IAAA;KAdC,SAASP,EAAA0gB,CAAA,CAAE,CAAC,WAAW;KACvB,qBAAmB1gB,EAAAyW,CAAA,CAAO,CAAC,gBAAgB;KAC3C,aAAWzW,EAAAyW,CAAA,CAAO,CAAC,SAAS;KAC5B,uBAAoCwK,GAAA,SAAqBjhB,EAAA0gB,CAAA,CAAE,CAAC,kBAAkB;KAG9E,qBAAmBS,EAAA;KACnB,qBAAmBC,GAAA;KACnB,qBAAmBC,GAAA;KACnB,YAAUrhB,EAAAyW,CAAA,CAAO,CAAC;KAClB,gBAAezW,EAAA0gB,CAAA,CAAE,CAAC;KAClB,gBAAe1gB,EAAA0gB,CAAA,CAAE,CAAC;KAClB,gBAAe1gB,EAAA0gB,CAAA,CAAE,CAAC;KAClB,sBAAsB1gB,EAAAyW,CAAA,CAAO,CAAC;;;;;;;;;;;;;;;;GAMrC3W,CAAAA,GAAAA,EAoLM,OApLN2e,IAoLM,CAnLJ3e,EAgIM,OAhIN4e,IAgIM,CA5HJ5e,EAiGM,OAjGN6e,IAiGM;IA/FIiC,EAAA,SADRhhB,EAAA,GAAAC,EA8BS,UAAA;;KA5BP,MAAK;KACL,eAAY;KACZ,OAAM;KACL,OAAKW,EAAA;MAA2B,OAAAR,EAAA0gB,CAAA,CAAE,CAAC,YAAY,QAAA,uBAAA;MAA6H,iBAAA1gB,EAAA0gB,CAAA,CAAE,CAAC,YAAY,QAAA,kBAAA;MAAqG,QAAA1gB,EAAA0gB,CAAA,CAAE,CAAC,YAAY,QAAA,gCAAA;;KAW/S,OAAwB1gB,EAAA0gB,CAAA,CAAE,CAAC,YAAY,QAA0B5Z,EAAA,MAAE,aAAa,cAAgCA,EAAA,MAAE,aAAa;KAK/H,cAA6B9G,EAAA0gB,CAAA,CAAE,CAAC,YAAY,QAA0B5Z,EAAA,MAAE,aAAa,cAAgCA,EAAA,MAAE,aAAa;KAKpI,gBAAc9G,EAAA0gB,CAAA,CAAE,CAAC,YAAY;KAC7B,SAAK,AAAA7Z,EAAA,QAAA,MAAE,EAAA6Z,CAAA,CAAE,CAAC,YAAY,QAAK,CAAI1gB,EAAA0gB,CAAA,CAAE,CAAC,YAAY;IAE/CtgB,GAAAA,CAAAA,EAA0CJ,EAAA4D,CAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;;IAGvB5D,EAAAyW,CAAA,CAAO,CAAC,SAAS,UAAK,qBACpC7W,EAAA,GAAAC,EAKO,QALPgf,IAKO9e,EADF+G,EAAA,MAAE,aAAa,cAAc,GAAA,CAAA,MAIlClH,EAAA,GAAAK,EAIEQ,IAAA;;KAHC,SAAST,EAAA0gB,CAAA,CAAE,CAAC,WAAW;KACvB,qBAAmB1gB,EAAAyW,CAAA,CAAO,CAAC,gBAAgB;KAC3C,YAAUzW,EAAAyW,CAAA,CAAO,CAAC;;;;;;IAIvB3W,EA+CM,OA/CNgf,IA+CM,CAxCJhf,EAmBS,UAAA;KAlBP,MAAK;KACL,OAAM;KACL,OAAKU,EAAA;MAAiDR,OAAAA,EAAA0gB,CAAA,CAAE,CAAC,WAAW,UAAK,SAAA,uBAAA;MAAiK1gB,iBAAAA,EAAA0gB,CAAA,CAAE,CAAC,WAAW,UAAK,SAAA,2BAAA;;KAU7P,OAAO5Z,EAAA,MAAE,aAAa;KACtB,cAAYA,EAAA,MAAE,aAAa;KAC3B,gBAAc9G,EAAA0gB,CAAA,CAAE,CAAC,WAAW,UAAK;KACjC,SAAK,AAAA7Z,EAAA,QAAA,MAAE,EAAA6Z,CAAA,CAAE,CAAC,WAAW,QAAK;IAE3BtgB,GAAAA,CAAAA,EAAwCJ,EAAAmE,EAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;IAErCrE,CAAAA,CAAAA,GAAAA,IAAAA,EAAAA,GAAAA,EAmBS,UAAA;KAlBP,MAAK;KACL,OAAM;KACL,OAAKU,EAAA;MAAiDR,OAAAA,EAAA0gB,CAAA,CAAE,CAAC,WAAW,UAAK,SAAA,uBAAA;MAAiK1gB,iBAAAA,EAAA0gB,CAAA,CAAE,CAAC,WAAW,UAAK,SAAA,2BAAA;;KAU7P,OAAO5Z,EAAA,MAAE,aAAa;KACtB,cAAYA,EAAA,MAAE,aAAa;KAC3B,gBAAc9G,EAAA0gB,CAAA,CAAE,CAAC,WAAW,UAAK;KACjC,SAAK,AAAA7Z,EAAA,QAAA,MAAE,EAAA6Z,CAAA,CAAE,CAAC,WAAW,QAAK;IAE3BtgB,GAAAA,CAAAA,EAAqCJ,EAAAqD,CAAA,GAAA;KAA9B,MAAM;KAAK,gBAAc;;GAKtCvD,CAAAA,GAAAA,EAwBM,OAxBNmf,IAwBM,CAtBIjf,EAAAqT,CAAA,CAAmB,CAAC,SAAM,KADlCzT,EAAA,GAAAC,EAsBS,UAAA;;IApBN,cAAYiH,EAAA,MAAE,aAAa;IAC5B,OAAM;IACN,OAAA;KAAA,gBAAA;KAAA,oBAAA;KAAAhB,OAAA;IAAA;IAKC,OAAO9F,EAAAyW,CAAA,CAAO,CAAC,eAAe,SAAK;IACnC,UAAQqL;GAEThiB,GAAAA,CAAAA,EAES,UAFTqf,IAESpf,EADJ+G,EAAA,MAAE,aAAa,SAAS,GAAA,CAAA,IAE7BlH,EAAA,EAAA,GAAAC,EAMSa,GAAA,MAAAC,EALYX,EAAAqT,CAAA,IAAZ,OADTzT,EAAA,GAAAC,EAMS,UAAA;IAJN,KAAKyT;IACL,OAAOA;GAEL,GAAAvT,EAAAC,EAAA0gB,CAAA,CAAE,CAAC,iBAAiBpN,CAAQ,CAAA,GAAA,GAAA8L,EAAA,EAMvCtf,GAAAA,GAAAA,EAAAA,GAAAA,IAAAA,EAAAA,KAAAA,EAAAA,IAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,GAAAA,EAgDM,OAhDNuf,IAgDM,CA9CmBrf,EAAAyW,CAAA,CAAO,CAAC,SAAS,UAAK,YAAiBkK,EAAA,SAAaE,EAAA,UAD3EjhB,EAAA,GAAAC,EA0BM,OA1BNyf,IA0BM,CAnBIqB,EAAA,SADR/gB,EAAA,GAAAK,EAKEW,IAAA;;IAHC,gBAAcZ,EAAAyW,CAAA,CAAO,CAAC,YAAY;IAClC,mBAAiBzW,EAAAyW,CAAA,CAAO,CAAC,eAAe;IACxC,UAAQzW,EAAA0gB,CAAA,CAAE,CAAC;;;;;GAGN,CAAA,KAAA9e,EAAA,IAAA,EAAA,GAAAif,EAAA,SADRjhB,EAAA,GAAAC,EAaS,UAAA;;IAXP,eAAY;IACZ,OAAM;IACN,OAAA;KAAA,gBAAA;KAAAiG,OAAA;KAAA,oBAAA;IAAA;IAKC,SAAK,AAAAe,EAAA,QAAA,MAAE,EAAA6Z,CAAA,CAAE,CAAC,mBAAmB,QAAK;GAEnCtgB,GAAAA,CAAAA,EAAqCJ,EAAAuD,CAAA,GAAA;IAA9B,MAAM;IAAK,gBAAc;GAAK1C,CAAAA,GAAAA,EAAA,MACrCd,EAAG+G,EAAA,MAAE,aAAa,aAAa,GAAA,CAAA,CAAA,CAAA,KAAAlF,EAAA,IAAA,EAAA,CAAA,CAAA,KAAAA,EAAA,IAAA,EAAA,GAInCxB,EAkBEU,IAAA;IAjBC,OAAOd,EAAA0gB,CAAA,CAAE,CAAC,aAAa;IACvB,gBAAc1gB,EAAAyW,CAAA,CAAO,CAAC,cAAc;IACpC,cAAYzW,EAAAyW,CAAA,CAAO,CAAC,UAAU;IAC9B,YAAyBzW,EAAAyW,CAAA,CAAO,CAAC,SAAS,UAAK,WAAgBzW,EAAAyW,CAAA,CAAO,CAAC,QAAQ;IAG/E,QAAQrQ,EAAA;IACR,QAAQpG,EAAA0gB,CAAA,CAAE,CAAC,WAAW;IACtB,gBAAc1gB,EAAAyW,CAAA,CAAO,CAAC,YAAY;IAClC,cAAYsK,GAAA;IACZ,eAAaC,EAAA;IACb,UAAQhhB,EAAA0gB,CAAA,CAAE,CAAC;IACX,UAAQ1gB,EAAAyW,CAAA,CAAO,CAAC;IAChB,YAAWzW,EAAAyW,CAAA,CAAO,CAAC;IACnB,QAAMzW,EAAA0gB,CAAA,CAAE,CAAC;IACT,WAAS1gB,EAAA0gB,CAAA,CAAE,CAAC;IACZ,WAASmB;;;;;;;;;;;;;;;;;GAMlBzhB,EAMEW,IAAA;IALC,SAASf,EAAA0gB,CAAA,CAAE,CAAC,mBAAmB;IAC/B,gBAAc1gB,EAAAyW,CAAA,CAAO,CAAC,mBAAmB;IACzC,OAAOzW,EAAAyW,CAAA,CAAO,CAAC,mBAAmB;IAClC,UAAQzW,EAAA0gB,CAAA,CAAE,CAAC;IACX,SAAK,AAAA7Z,EAAA,QAAA,MAAE,EAAA6Z,CAAA,CAAE,CAAC,mBAAmB,QAAK;;;;;;;GAI7B1gB,EAAA0gB,CAAA,CAAE,CAAC,YAAY,SADvB9gB,EAAA,GAAAK,EAOED,EAAAugB,CAAA,GAAA;;IALC,SAAS;IACT,MAAMvgB,EAAA0gB,CAAA,CAAE,CAAC,YAAY;IACrB,MAAM1gB,EAAA0gB,CAAA,CAAE,CAAC;IACT,YAAUM,EAAA,SAAchhB,EAAA0gB,CAAA,CAAE,CAAC,YAAY,MAAM,cAAS;IACtD,SAAK,AAAA7Z,EAAA,QAAA,MAAE,EAAA6Z,CAAA,CAAE,CAAC,YAAY,QAAK;;;;;;GAG9BtgB,EAQEY,IAAA;IAPC,SAAShB,EAAAyW,CAAA,CAAO,CAAC,mBAAmB;IACpC,MAAMzW,EAAAyW,CAAA,CAAO,CAAC,mBAAmB;IACjC,cAAYzW,EAAAyW,CAAA,CAAO,CAAC,iBAAiB;IACrC,gBAAczW,EAAAyW,CAAA,CAAO,CAAC,YAAY;IAClC,OAAOzW,EAAAyW,CAAA,CAAO,CAAC,aAAa;IAC5B,WAASzW,EAAA0gB,CAAA,CAAE,CAAC;IACZ,SAAO1gB,EAAAyW,CAAA,CAAO,CAAC;;;;;;;;;;GAGlBrW,EAgHaC,GAAA;IA/GX,sBAAmB;IACnB,oBAAiB;IACjB,kBAAe;IACf,sBAAmB;IACnB,oBAAiB;IACjB,kBAAe;;IAEf,SAAAC,QAuGM,CAtGEN,EAAAyW,CAAA,CAAO,CAAC,kBAAkB,SADlC7W,EAAA,GAAAC,EAuGM,OAAA;;KArGJ,MAAK;KACL,cAAW;KACX,mBAAgB;KAChB,OAAM;KACN,OAAA;MAAA,oBAAA;MAAA,mBAAA;MAAA,2BAAA;KAAA;KAKC,SAAK,AAAAgH,EAAA,QAAA5F,GAAO,GAAA+gB,MAAAhiB,EAAAyW,CAAA,CAAO,CAAC,gBAARzW,EAAAyW,CAAA,CAAO,CAAC,aAAY,GAAAuL,CAAA,GAAA,CAAA,MAAA,CAAA;IAEjCliB,GAAAA,CAAAA,EAyFM,OAzFNyf,IAyFM;KAlFJzf,EAMK,MANL0f,IAMKzf,EADA+G,EAAA,MAAE,aAAa,kBAAkB,GAAA,CAAA;KAEtChH,EAMI,KAAA;MALF,OAAKoB,EAAA,CAAC,eACElB,EAAA0gB,CAAA,CAAE,CAAC,aAAa,QAAK,aAAA,UAAA,CAAA;MAC7B,OAAA,EAAA5a,OAAA,wBAAA;KAEG,GAAA/F,EAAA+G,EAAA,MAAE,aAAa,oBAAoB,GAAA,CAAA;KAGhC9G,EAAA0gB,CAAA,CAAE,CAAC,aAAa,SADxB9gB,EAAA,GAAAC,EAOI,KAPJ4f,IAOI1f,EADC+G,EAAA,MAAE,aAAa,sBAAsB,GAAA,CAAA,KAAAlF,EAAA,IAAA,EAAA;KAIlC5B,EAAA0gB,CAAA,CAAE,CAAC,aAAa,SADxB9gB,EAAA,GAAAC,EA6BM,OA7BN6f,IA6BM,EAxBJ9f,EAAA,EAAA,GAAAC,EAuBMa,GAAA,MAAAC,EAtBsBX,EAAAyW,CAAA,CAAO,CAAC,gBAAgB,QAA1C,GAAM,OADhB7W,EAAA,GAAAC,EAuBM,OAAA;MArBH,KAAKgY;MACN,OAAM;MACN,OAAA,EAAA/R,OAAA,kBAAA;KAEgB,GAAA,CAAA+Q,EAAK,gBAAa,KAAlCjX,EAAA,GAAAC,EAgBWa,GAAA,EAAA,KAAA,EAAA,GAAA,CAfTZ,EAKO,QALP6f,IAKO5f,EAHHC,EAAA0gB,CAAA,CAAE,CAAC,aAAa,MAAM,MAAM,MAAM3S,EAAE,OAAO8J,CAAO,CAAA,EAA0B,YAAYA,CAAO,GAAA,CAAA,GAInG/X,EAQO,QARP8f,IAA2C,QAEzC7f,EACE+G,EAAA,MAAE,aAAa,gBAAgB,QAAA,WAAgE+P,EAAK,cAAc,SAAQ,CAAA,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,KAAAjV,EAAA,IAAA,EAAA,CAAA,CAAA;KAUpI9B,EA2BM,OA3BN+f,IA2BM,CA1BJ/f,EAUS,UAAA;MATP,OAAM;MACN,OAAA;OAAA,gBAAA;OAAAgG,OAAA;OAAA,oBAAA;MAAA;MAKC,SAAK,AAAAe,EAAA,QAAE,GAAAmb,MAAAhiB,EAAAyW,CAAA,CAAO,CAAC,gBAARzW,EAAAyW,CAAA,CAAO,CAAC,aAAY,GAAAuL,CAAA;KAEzB,GAAAjiB,EAAA+G,EAAA,MAAE,aAAa,MAAM,GAAA,CAAA,GAE1BhH,EAcS,UAAA;MAbP,OAAM;MACN,OAAA;OAAA,gBAAA;OAAAgG,OAAA;OAAA,oBAAA;MAAA;MAKC,SAAK,AAAAe,EAAA,QAAE,GAAAmb,MAAAhiB,EAAAyW,CAAA,CAAO,CAAC,iBAARzW,EAAAyW,CAAA,CAAO,CAAC,cAAa,GAAAuL,CAAA;KAG3B,GAAAjiB,EAAAC,EAAA0gB,CAAA,CAAE,CAAC,aAAa,QAA0B5Z,EAAA,MAAE,aAAa,eAAiCA,EAAA,MAAE,aAAa,aAAa,GAAA,CAAA,CAAA,CAAA;;;;GAWlIhH,EAwFM,OAxFNggB,IAwFM,CApFJhgB,EAMM,OANNigB,IAMM,CAJI/f,EAAAyW,CAAA,CAAO,CAAC,YAAY,SAD5B7W,EAAA,GAAAK,EAIEkB,IAAA;;IAFC,MAAMnB,EAAAyW,CAAA,CAAO,CAAC,YAAY;IAC1B,SAASzW,EAAA0gB,CAAA,CAAE,CAAC,WAAW;GAG5B5gB,GAAAA,MAAAA,GAAAA,CAAAA,QAAAA,SAAAA,CAAAA,KAAAA,EAAAA,IAAAA,EAAAA,CAAAA,CAAAA,GAAAA,EA4EM,OA5ENkgB,IA4EM,CA1EIhgB,EAAAyW,CAAA,CAAO,CAAC,cAAc,MAAM,OAAI,KADxC7W,EAAA,GAAAC,EA0CM,OA1CNogB,IA0CM,CArCIjgB,EAAAyW,CAAA,CAAO,CAAC,YAAY,SAD5B7W,EAAA,GAAAC,EAiBS,UAAA;;IAfP,OAAM;IACL,OAAKW,EAAA;KAA+B,aAAAR,EAAA0gB,CAAA,CAAE,CAAC,OAAO,QAAA,uBAAA;KAAyG,OAAA1gB,EAAA0gB,CAAA,CAAE,CAAC,OAAO,QAAK,uBAAA;;;IAOtK,SAAK,AAAA7Z,EAAA,SAAA,MAAE7G,EAAA0gB,CAAA,CAAE,CAAC,KAAK1gB,EAAA0gB,CAAA,CAAE,CAAC,YAAY,KAAK;GAEvB,GAAA,CAAA1gB,EAAA0gB,CAAA,CAAE,CAAC,OAAO,SACvB9gB,EAAA,GAAAK,EAA6CD,EAAA0E,CAAA,GAAA;;IAA9B,MAAM;IAAK,gBAAc;GAAK7D,CAAAA,MAD7CjB,EAAA,GAAAK,EAA6DD,EAAAwD,CAAA,GAAA;;IAA9B,MAAM;IAAK,gBAAc;GACxD5D,CAAAA,IAA6CiB,EAAA,MAC7Cd,EACEC,EAAA0gB,CAAA,CAAE,CAAC,OAAO,QAAQ5Z,EAAA,MAAE,aAAa,SAASA,EAAA,MAAE,aAAa,OAAO,GAAA,CAAA,CAAA,GAAA,CAAA,KAAAlF,EAAA,IAAA,EAAA,GAGzDsf,EAAA,SAAXthB,EAAA,GAAAC,EAmBM,OAnBNqgB,IAmBM,CAlBJpgB,EAUS,UAAA;IATP,OAAM;IACN,OAAA;KAAA,gBAAA;KAAAgG,OAAA;KAAA,oBAAA;IAAA;IAKC,SAAK,AAAAe,EAAA,SAAA,MAAE,EAAA6Z,CAAA,CAAE,CAAC,eAAe,QAAK,CAAI1gB,EAAA0gB,CAAA,CAAE,CAAC,eAAe;GAElD,GAAA3gB,EAAA+G,EAAA,MAAE,aAAa,YAAY,GAAA,CAAA,GAGxB9G,EAAA0gB,CAAA,CAAE,CAAC,eAAe,SAD1B9gB,EAAA,GAAAK,EAMEmB,IAAA;;IAJC,SAASpB,EAAA0gB,CAAA,CAAE,CAAC,WAAW;IACvB,qBAAmB1gB,EAAAyW,CAAA,CAAO,CAAC,gBAAgB;IAC3C,UAAQzW,EAAA0gB,CAAA,CAAE,CAAC;IACX,SAAK,AAAA7Z,EAAA,SAAA,MAAE,EAAA6Z,CAAA,CAAE,CAAC,eAAe,QAAK;;;;;GAIrC5gB,CAAAA,KAAAA,EAAAA,IAAAA,EAAAA,CAAAA,CAAAA,KAAAA,EAAAA,IAAAA,EAAAA,CAAAA,CAAAA,KAAAA,EAAAA,IAAAA,EAAAA,GAAAA,EA+BM,OA/BNqgB,IA+BM,CA7BIqB,GAAA,QAAsB,KAD9B5hB,EAAA,GAAAC,EAYS,UAAA;;IAVP,eAAY;IACZ,OAAM;IACN,OAAA;KAAA,gBAAA;KAAAiG,OAAA;KAAA,oBAAA;IAAA;IAKC,SAAOkD;GAEL,GAAAjJ,EAAA+G,EAAA,MAAE,aAAa,cAAc,GAAA,CAAA,KAAAlF,EAAA,IAAA,EAAA,GAG1BwE,EAAA,eADRxG,EAAA,GAAAC,EAgBS,UAAA;;IAdN,eAAa2gB,EAAA;IACd,OAAM;IACN,OAAA;KAAA,oBAAA;KAAA1a,OAAA;IAAA;IAIC,UAAQ,CAAGyb,EAAA;IACX,SAAOM;GAGN,GAAA9hB,EAAAqG,EAAA,QAAQ,SAAyBU,EAAA,MAAE,aAAa,cAA8BA,EAAA,MAAE,aAAa,UAAU,GAAA,GAAAsZ,EAAA,KAAAxe,EAAA,IAAA,EAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;;EErsBrH,IAAMuE,IAAQC,GAaRC,IAAOH,GAIPY,IAAI/E,QAAeoE,EAAM,YAAY;EAO3C,AAFAvD,EAAQ4C,IAAkBxB,EAAImC,EAAM,YAAY,CAAC,GAEjDvD,EACEyC,GACAnB,SAAYiC,EAAM,MAAM,CAC1B;EAEA,IAAM+b,IAAcle,EAAwB,IAAI;EAChD,EAAQiB,GAAoBid,CAAW;EAEvC,SAASC,EAAc,GAAwB;GAC7C,EAAM,WAAWpV,CAAI;EACvB;oBAIEzL,EAAA,GAAAC,EAmBM,OAAA;GAlBA,SAAA;GAAJ,KAAI2gB;GACJ,OAAM;GACN,OAAA;IAAAE,OAAA;IAAAC,QAAA;IAAA,oBAAA;IAAAnS,QAAA;GAAA;EAOA1O,GAAAA,CAAAA,EAQEC,IAAA;GAPC,UAAU2E,EAAA;GACV,QAAQA,EAAA;GACR,cAAcU,EAAA;GACd,gBAAY,CAAA,CAAIV,EAAA;GACjB,mBAAgB;GACf,WAAS+b;GACT,SAAK,AAAAtb,EAAA,QAAA,MAAER,EAAI,OAAA;;;;;;;;IE1DZic,KAAgB,uBAAA,OAAA;CAAA,yBAAA,OAAA;CAAA,yBAAA,OAAA;CAAA,yBAAA,OAAA,0BAAA,CAAA,MAAA,MAAAC,EAAA,CAAA;CAAA,yBAAA,OAAA;CAAA,yBAAA,OAAA;CAAA,yBAAA,OAAA;CAAA,yBAAA,OAAA;CAAA,4BAAA,OAAA;AAAA,CAAA;AAItB,SAASC,GAAgB,GAA4C;CACnE,OAAO,OAAO,KAAKC,CAAO,CAAC,CACxB,KAAK,MAASlc,EAAK,MAAM,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAChD,QAAQ,MAA6B,EAAQmE,CAAO;AACzD;AAEA,IAAMgY,KAAmBF,GAAgBF,EAAa;AAEtD,SAASK,GAAa,GAAwB;CAC5C,OAAOjY,EAAO,KAAK,CAAC,CAAC,QAAQ,MAAM,GAAG,CAAC,CAAC,YAAY;AACtD;AAEA,SAASkY,GAAoB,GAAoC;CAC/D,IAAMC,IAAYF,GAAajY,CAAM;CACrC,OAAOgY,GAAiB,MAAM,MAAMC,GAAaG,CAAC,MAAMD,CAAS;AACnE;AAEA,SAASE,GAAc,GAAwB;CAC7C,OAAOJ,GAAajY,CAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AACzC;AAEA,SAASsY,GAAc,GAAwB;CAC7C,OACEJ,GAAoBlY,CAAM,KAC1BkY,GAAoBG,GAAcrY,CAAM,CAAC,KACzC;AAEJ;AAEA,eAAsBuY,GACpB,GAC4B;CAE5B,IAAMC,IAASZ,GAAc,aADdU,GAActY,CACa,EAAO;CAEjD,QAAO,MADcwY,EAAO,EAAA,CACd;AAChB;;;ACtCA,IAAIC,IAA0B,MACxBC,KACJpf,EAAI,IAAI;AAEV,eAAeqf,GAAK,GAA2D;CAC7E,IAAMC,IACJ,OAAOjZ,EAAO,aAAc,WACxB,SAAS,cAAcA,EAAO,SAAS,IACvCA,EAAO;CAEb,IAAI,CAACiZ,GACH,MAAU,MAAM,gCAAgCjZ,EAAO,WAAW;CAGpE,IAAM4L,IAAkC,MAAMgN,GAC5C5Y,EAAO,UAAU,IACnB;CAWA,OATAkZ,GAAWD,GAA0BjZ,EAAO,KAAK,GAK7C8Y,KACFK,GAAQ,GAGH,IAAI,SAAS,GAAS,MAAW;EACtC,IAAI;GAyBF,AAxBA,IAActgB,EAAU,EACtB,QAAQ;IACN,IAAMugB,UAAgB;KAMpB,EAAQ;MAJN,WAAW,MAAUF,GAAWD,GAA0BI,CAAK;MAC/D;KAGM,CAAQ;IAClB;IAEA,aACErhB,EAAEV,IAAc;KACd,UAAU0I,EAAO;KACjB;KACA,UAAUA,EAAO;KACjB,QAAQA,EAAO;KACf,QAAQA,EAAO;KACf,KAAK+Y;KACL;IACF,CAAC;GACL,EACF,CAAC,GAEDD,EAAY,MAAMG,CAAS;EAC7B,SAAS9Q,GAAO;GACd,EAAOA,CAAK;EACd;CACF,CAAC;AACH;AAEA,SAASgR,KAAgB;CACvB,AAAIL,MACFA,EAAY,QAAQ,GACpB,IAAc,MACd,GAAgB,QAAQ;AAE5B;AAEA,SAASI,GACP,GACA,GACM;CACN,AAAKG,MAIDA,EAAM,gBAGRJ,EAAU,MAAM,YAAY,sBAAsBI,EAAM,YAAY,GAGlEA,EAAM,iBAAiB,KAAA,MACzBJ,EAAU,MAAM,YAAY,qBAAqB,GAAGI,EAAM,aAAa,GAAG,GAC1EJ,EAAU,MAAM,YACd,wBACA,GAAG,KAAK,IAAI,GAAGI,EAAM,eAAe,CAAC,EAAE,GACzC,GACAJ,EAAU,MAAM,YACd,wBACA,GAAGI,EAAM,eAAe,EAAE,GAC5B;AAEJ;AAEA,IAAMC,KAAmB;CACvB;CACA;AACF;AAEI,OAAO,SAAW,QACpB,OAEE,mBAAmBA"}
|
|
1
|
+
{"version":3,"file":"media-library.js","names":["_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_unref","_Fragment","_renderList","_createVNode","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_resolveComponent","_openBlock","_createElementBlock","_createElementVNode","_normalizeStyle","_unref","_withModifiers","_createVNode","_normalizeClass","_toDisplayString","_createCommentVNode","_withDirectives","_withKeys","_vModelText","Trash2","_Fragment","_renderList","_createBlock","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_createElementVNode","_normalizeStyle","_createVNode","_unref","_createTextVNode","_toDisplayString","_Fragment","_renderList","_createBlock","MediaFolderTreeNode","_withDirectives","_withKeys","_withModifiers","_vModelText","_createCommentVNode","_sfc_main","_openBlock","_createElementBlock","_normalizeStyle","_createElementVNode","_toDisplayString","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_openBlock","_createElementBlock","_unref","_normalizeClass","_Fragment","_renderList","_createElementVNode","_createVNode","_toDisplayString","_normalizeStyle","_createBlock","MediaFileIcon","_createCommentVNode","_withModifiers","_createTextVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_openBlock","_createBlock","_Teleport","_unref","_createVNode","_Transition","_withCtx","_createElementBlock","_normalizeClass","_withModifiers","_createElementVNode","_toDisplayString","_withDirectives","_vModelText","_createCommentVNode","_createTextVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_unref","_createTextVNode","_toDisplayString","_createCommentVNode","_Fragment","_renderList","_normalizeStyle","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_createVNode","MediaFileIcon","_createElementVNode","_toDisplayString","_Fragment","_createTextVNode","_unref","_createCommentVNode","_normalizeClass","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_openBlock","_createBlock","_Teleport","_unref","_createVNode","_Transition","_withCtx","_createElementBlock","_normalizeClass","_withModifiers","_createElementVNode","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_Fragment","_createElementVNode","_normalizeClass","_unref","_createVNode","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_Transition","_withCtx","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_unref","_createBlock","StorageProgressRing","_createCommentVNode","_createVNode","_Transition","_withCtx","MediaFolderTree","_normalizeStyle","MediaBreadcrumb","_Fragment","_renderList","MediaUploadZone","_createTextVNode","MediaGrid","MediaImportUrlModal","MediaReplaceModal","_withModifiers","_normalizeClass","MediaPreviewPanel","MediaMovePicker","_sfc_main","_openBlock","_createElementBlock","_createVNode","MediaLibraryChrome","_sfc_main","MediaLibrary","createCommentVNode","onBeforeUnmount","withKeys","computed","Teleport","defineComponent","openBlock","watch","resolveComponent","h","createTextVNode","createVNode","createBaseVNode","defineAsyncComponent","inject","Fragment","provide","renderList","withCtx","withDirectives","ChevronRight","Search","createApp","Pencil","unref","List","toDisplayString","Link","Copy","File","Star","createBlock","PanelLeft","normalizeClass","Folder","Upload","ref","RefreshCw","toRef","Grid2x2","Trash","Plus","toValue","X","LoaderCircle","normalizeStyle","Check","withModifiers","vModelText","Transition","useDropZone","useDebounceFn","MEDIA_LIMITS_KEY","POPOVER_TARGET_KEY","UI_THEME_KEY","useClipboard","useMediaCategories","UI_LOCALE_KEY","_plugin_vue_export_helper_default","useFileDialog","TRANSLATIONS_KEY","onClickOutside","useIntersectionObserver","useFocusTrap","useI18n","_hoisted_1$10","\"color\"","_hoisted_2$10","_hoisted_3$10","MediaBreadcrumb_default","__emit","props","__props","emit","breadcrumbPath","path","buildPath","folder","folderList","targetId","createElementBlock","_cache","t","index","_hoisted_1$9","_hoisted_2$9","_hoisted_3$9","_hoisted_4$8","_hoisted_5$7","_hoisted_6$6","_hoisted_7$4","_hoisted_8$4","_hoisted_9$4","MediaFolderTreeNode_default","isExpanded","isRenaming","renameValue","isCreatingSubfolder","subfolderName","confirmDelete","hasChildren","canCreateSubfolder","isActive","isDescendantActive","containsFolder","shouldExpand","folders","id","toggleExpand","startRename","confirmRename","trimmed","cancelRename","startCreateSubfolder","confirmCreateSubfolder","cancelCreateSubfolder","handleDeleteClick","onRowFocusOut","next","event","_component_MediaFolderTreeNode","$event","child","name","parentId","_hoisted_1$8","_hoisted_2$8","_hoisted_3$8","MediaFolderTree_default","isCreating","newFolderName","startCreateFolder","confirmCreateFolder","cancelCreate","MediaFileIcon_default","FILE_TYPE_MAP","DEFAULT_CONFIG","config","formatAbsoluteDate","parsed","iso","tag","locale","options","UNITS","formatFileSize","value","bytes","unit","_hoisted_1$7","_hoisted_2$7","_hoisted_3$7","_hoisted_4$7","_hoisted_5$6","_hoisted_6$5","_hoisted_7$3","_hoisted_8$3","_hoisted_9$3","_hoisted_10$3","_hoisted_11$2","_hoisted_12$2","_hoisted_13$2","_hoisted_14$1","_hoisted_15$1","_hoisted_16$1","_hoisted_17$1","_hoisted_18$1","_hoisted_19$1","_hoisted_20$1","_hoisted_21$1","_hoisted_22$1","_hoisted_23$1","_hoisted_24$1","_hoisted_25$1","_hoisted_26$1","_hoisted_27$1","_hoisted_28$1","MediaGrid_default","mimeOf","item","thumbOf","isSelectable","isAcceptedMimeType","showEdit","showReplace","handleItemClick","handleItemDblClick","handleItemKeydown","isSelected","actionChromeClass","uiLocale","sentinelRef","isIntersecting","formatDate","dateStr","i","isImageMimeType","_hoisted_1$6","_hoisted_2$6","_hoisted_3$6","_hoisted_4$6","_hoisted_5$5","_hoisted_6$4","_hoisted_7$2","_hoisted_8$2","_hoisted_9$2","_hoisted_10$2","MediaImportUrlModal_default","tplUiTheme","popoverTarget","urlValue","visible","handleImport","trimmedUrl","handleClose","dialogRef","trapActive","handleKeydown","_hoisted_1$5","_hoisted_2$5","_hoisted_3$5","_hoisted_4$5","MediaMovePicker_default","pickerRef","flattenFolders","result","depth","_hoisted_1$4","_hoisted_2$4","_hoisted_3$4","\"border\"","_hoisted_4$4","_hoisted_5$4","_hoisted_6$3","MediaPreviewPanel_default","buildFolderPath","newPath","currentPath","found","folderPath","mimeType","isImage","filename","_hoisted_1$3","_hoisted_2$3","_hoisted_3$3","_hoisted_4$3","_hoisted_5$3","_hoisted_6$2","_hoisted_7$1","_hoisted_8$1","_hoisted_9$1","_hoisted_10$1","_hoisted_11$1","_hoisted_12$1","_hoisted_13$1","MediaReplaceModal_default","fileInputRef","selectedFile","extension","parts","acceptPattern","hasUsage","handleFileChange","input","handleReplace","formatList","items","error","_hoisted_1$2","_hoisted_2$2","_hoisted_3$2","_hoisted_4$2","_hoisted_5$2","_hoisted_6$1","MediaUploadZone_default","CATEGORY_LABEL_KEYS","acceptedHint","labels","formats","restrictsMimeTypes","availableCategories","category","maxFileSize","format","rejectedNames","dropZoneRef","emitValidFiles","valid","rejected","file","fileList","isAcceptedFile","files","openFilePicker","allAcceptedInputString","onChange","isOverDropZone","_hoisted_1$1","_hoisted_2$1","_hoisted_3$1","_hoisted_4$1","_hoisted_5$1","StorageProgressRing_default","showTooltip","size","strokeWidth","radius","circumference","percentage","strokeDashoffset","progressColor","usedFormatted","limitFormatted","remainingBytes","remainingFormatted","tooltipText","\"stroke\"","treeFolders","nodes","roots","node","parent","useMediaLibraryUI","getTranslations","translations","layoutMode","showSidebar","searchInput","editingItem","showImportUrlModal","showMovePicker","selectedUrl","library","hasFrequentlyUsed","displayItems","hasUsedFiles","info","folderTree","categoryLabels","getCategoryLabel","show","debouncedSearch","handleSearchInput","handleUpload","handleSelect","handleCreateFolder","handleRenameFolder","folderId","handleDeleteFolder","handleEditItem","handleEditSave","cropData","mediaId","altText","handleImportFromUrl","url","handleMoveToFolder","handleReplaceItem","handleReplaceFile","resetUI","useMediaLibrary","currentFolderId","viewMode","searchQuery","categoryFilter","sortOption","isLoading","isUploading","hasMore","nextCursor","uploadProgress","selectedItems","previewItem","frequentlyUsedItems","deleteUsageInfo","showDeleteWarning","pendingDeleteIds","isImportingFromUrl","importFromUrlError","isReplacing","replaceError","showReplaceWarning","pendingReplaceItem","replaceUsageInfo","storageInfo","browseRequestId","notify","foldersProvider","provider","resolvedTemplateId","currentListParams","params","cursor","templateId","loadItems","requestId","page","loadMore","search","query","filterByCategory","sortBy","option","navigateToFolder","showFrequentlyUsed","loadFrequentlyUsed","uploadFile","create","media","uploadFiles","moveSelected","move","movedItems","targetFolderId","movedMap","updateFile","update","patch","updated","captureLocalAssets","captured","ids","dropLocalAssets","deleteSelected","providerDelete","asset","frequentlyUsed","checkUsageBeforeDelete","checkUsage","usage","cancelDelete","importFromUrl","importUrl","toggleSelection","clearSelection","selectItem","loadStorage","storage","loadFolders","foldersApi","createFolder","findFolderInTree","renameFolder","deleteFolder","deleteFn","checkUsageBeforeReplace","cancelReplace","applyReplaced","replaceFile","replace","replaceMediaDirectly","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_11","_hoisted_12","_hoisted_13","_hoisted_14","_hoisted_15","_hoisted_16","_hoisted_17","_hoisted_18","_hoisted_19","_hoisted_20","_hoisted_21","_hoisted_22","_hoisted_23","_hoisted_24","_hoisted_25","_hoisted_26","_hoisted_27","_hoisted_28","_hoisted_29","_hoisted_30","_hoisted_31","_hoisted_32","_hoisted_33","_hoisted_34","_hoisted_35","_hoisted_36","_hoisted_37","MediaLibraryChrome_default","__expose","MediaEditModal","resolvedConfirmTestId","mediaLimits","ui","canCreate","canFolders","canImport","canDelete","canUpdate","canReplace","canFrequentlyUsed","canMove","canCreateFolder","canRenameFolder","canDeleteFolder","isInitialLoad","isConfirmable","selectedDeletableCount","count","entry","nestedDialogOpen","handleEscape","confirmSelection","handleCategoryChange","accept","args","MediaLibrary_default","popoverRoot","handleConfirm","\"width\"","\"height\"","localeModules","n","localesFromGlob","modules","supportedLocales","canonicalize","findSupportedLocale","canonical","s","getBaseLocale","resolveLocale","loadMediaTranslations","loader","appInstance","mediaLibraryRef","init","container","applyTheme","unmount","onReady","theme","TemplaticalMedia"],"sources":["../../src/components/media/MediaBreadcrumb.vue","../../src/components/media/MediaBreadcrumb.vue","../../src/components/media/MediaFolderTreeNode.vue","../../src/components/media/MediaFolderTreeNode.vue","../../src/components/media/MediaFolderTree.vue","../../src/components/media/MediaFolderTree.vue","../../src/components/media/MediaFileIcon.vue","../../src/components/media/MediaFileIcon.vue","../../src/utils/formatAbsoluteDate.ts","../../src/utils/formatFileSize.ts","../../src/components/media/MediaGrid.vue","../../src/components/media/MediaGrid.vue","../../src/components/media/MediaImportUrlModal.vue","../../src/components/media/MediaImportUrlModal.vue","../../src/components/media/MediaMovePicker.vue","../../src/components/media/MediaMovePicker.vue","../../src/components/media/MediaPreviewPanel.vue","../../src/components/media/MediaPreviewPanel.vue","../../src/components/media/MediaReplaceModal.vue","../../src/components/media/MediaReplaceModal.vue","../../src/utils/formatList.ts","../../src/components/media/MediaUploadZone.vue","../../src/components/media/MediaUploadZone.vue","../../src/components/media/StorageProgressRing.vue","../../src/components/media/StorageProgressRing.vue","../../src/utils/treeFolders.ts","../../src/composables/useMediaLibraryUI.ts","../../src/composable.ts","../../src/components/MediaLibraryChrome.vue","../../src/components/MediaLibraryChrome.vue","../../src/standalone/MediaLibrary.vue","../../src/standalone/MediaLibrary.vue","../../src/i18n/index.ts","../../src/standalone/visual.ts"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n}>();\n\nconst { t } = useI18n();\n\nconst breadcrumbPath = computed(() => {\n if (!props.currentFolderId) return [];\n const path: MediaFolderNode[] = [];\n buildPath(props.folders, props.currentFolderId, path);\n return path;\n});\n\nfunction buildPath(\n folderList: MediaFolderNode[],\n targetId: string,\n path: MediaFolderNode[],\n): boolean {\n for (const folder of folderList) {\n if (folder.id === targetId) {\n path.push(folder);\n return true;\n }\n if (buildPath(folder.children, targetId, path)) {\n path.unshift(folder);\n return true;\n }\n }\n return false;\n}\n</script>\n\n<template>\n <div\n v-if=\"breadcrumbPath.length > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-1 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <button\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', null)\"\n >\n {{ t.mediaLibrary.allFiles }}\n </button>\n <template v-for=\"(folder, index) in breadcrumbPath\" :key=\"folder.id\">\n <ChevronRight :size=\"12\" :stroke-width=\"2\" />\n <button\n v-if=\"index < breadcrumbPath.length - 1\"\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', folder.id)\"\n >\n {{ folder.name }}\n </button>\n <span v-else style=\"color: var(--tpl-text)\">{{ folder.name }}</span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight } from \"@lucide/vue\";\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n}>();\n\nconst { t } = useI18n();\n\nconst breadcrumbPath = computed(() => {\n if (!props.currentFolderId) return [];\n const path: MediaFolderNode[] = [];\n buildPath(props.folders, props.currentFolderId, path);\n return path;\n});\n\nfunction buildPath(\n folderList: MediaFolderNode[],\n targetId: string,\n path: MediaFolderNode[],\n): boolean {\n for (const folder of folderList) {\n if (folder.id === targetId) {\n path.push(folder);\n return true;\n }\n if (buildPath(folder.children, targetId, path)) {\n path.unshift(folder);\n return true;\n }\n }\n return false;\n}\n</script>\n\n<template>\n <div\n v-if=\"breadcrumbPath.length > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-1 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <button\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', null)\"\n >\n {{ t.mediaLibrary.allFiles }}\n </button>\n <template v-for=\"(folder, index) in breadcrumbPath\" :key=\"folder.id\">\n <ChevronRight :size=\"12\" :stroke-width=\"2\" />\n <button\n v-if=\"index < breadcrumbPath.length - 1\"\n class=\"tpl:transition-colors tpl:duration-150 tpl:hover:underline\"\n style=\"color: var(--tpl-primary)\"\n @click=\"emit('navigate', folder.id)\"\n >\n {{ folder.name }}\n </button>\n <span v-else style=\"color: var(--tpl-text)\">{{ folder.name }}</span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight, Folder, Pencil, Plus, Trash2 } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n folder: MediaFolderNode;\n currentFolderId: string | null;\n depth: number;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isExpanded = ref(false);\nconst isRenaming = ref(false);\nconst renameValue = ref(\"\");\nconst isCreatingSubfolder = ref(false);\nconst subfolderName = ref(\"\");\nconst confirmDelete = ref(false);\n\nconst MAX_DEPTH = 5;\n\nconst hasChildren = computed(() => props.folder.children.length > 0);\n\nconst canCreateSubfolder = computed(() => props.depth < MAX_DEPTH - 1);\n\nconst isActive = computed(() => props.currentFolderId === props.folder.id);\n\nconst isDescendantActive = computed(() => {\n if (!props.currentFolderId) return false;\n return containsFolder(props.folder.children, props.currentFolderId);\n});\n\nconst shouldExpand = computed(\n () => isExpanded.value || isDescendantActive.value,\n);\n\nfunction containsFolder(folders: MediaFolderNode[], id: string): boolean {\n for (const folder of folders) {\n if (folder.id === id) return true;\n if (containsFolder(folder.children, id)) return true;\n }\n return false;\n}\n\nfunction toggleExpand(): void {\n isExpanded.value = !isExpanded.value;\n}\n\nfunction startRename(): void {\n isRenaming.value = true;\n renameValue.value = props.folder.name;\n}\n\nfunction confirmRename(): void {\n const trimmed = renameValue.value.trim();\n if (trimmed && trimmed !== props.folder.name) {\n emit(\"renameFolder\", props.folder.id, trimmed);\n }\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction cancelRename(): void {\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction startCreateSubfolder(): void {\n isCreatingSubfolder.value = true;\n subfolderName.value = \"\";\n isExpanded.value = true;\n}\n\nfunction confirmCreateSubfolder(): void {\n if (subfolderName.value.trim()) {\n emit(\"createFolder\", subfolderName.value.trim(), props.folder.id);\n }\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction cancelCreateSubfolder(): void {\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction handleDeleteClick(): void {\n if (confirmDelete.value) {\n emit(\"deleteFolder\", props.folder.id);\n confirmDelete.value = false;\n return;\n }\n confirmDelete.value = true;\n}\n\nfunction onRowFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (next && (event.currentTarget as HTMLElement).contains(next)) {\n return;\n }\n confirmDelete.value = false;\n}\n</script>\n\n<template>\n <div>\n <!-- Folder row -->\n <div\n class=\"tpl:group tpl:flex tpl:w-full tpl:items-center tpl:gap-1 tpl:py-1.5 tpl:pr-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n :style=\"{\n paddingLeft: `${depth * 16 + 8}px`,\n backgroundColor: isActive ? 'var(--tpl-bg-active)' : 'transparent',\n color: isActive ? 'var(--tpl-primary)' : 'var(--tpl-text)',\n }\"\n @focusout=\"onRowFocusOut\"\n >\n <!-- Expand/collapse chevron -->\n <button\n v-if=\"hasChildren || isCreatingSubfolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-4 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :aria-expanded=\"shouldExpand\"\n :aria-label=\"\n shouldExpand\n ? t.mediaLibrary.collapseFolder\n : t.mediaLibrary.expandFolder\n \"\n @click.stop=\"toggleExpand\"\n >\n <ChevronRight\n class=\"tpl:transition-transform tpl:duration-150\"\n :class=\"{ 'tpl:rotate-90': shouldExpand }\"\n :size=\"10\"\n :stroke-width=\"2\"\n />\n </button>\n <span v-else class=\"tpl:size-4 tpl:shrink-0\" />\n\n <!-- Folder icon + name (clickable) -->\n <button\n type=\"button\"\n class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-1.5\"\n @click=\"emit('navigate', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <template v-if=\"!isRenaming\">\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n </template>\n </button>\n\n <!-- Inline rename input -->\n <input\n v-if=\"isRenaming\"\n v-model=\"renameValue\"\n type=\"text\"\n class=\"tpl:min-w-0 tpl:flex-1 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n autofocus\n @keydown.enter=\"confirmRename\"\n @keydown.escape.stop=\"cancelRename\"\n @blur=\"confirmRename\"\n @click.stop\n />\n\n <span\n v-if=\"!isRenaming\"\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-0.5 tpl:transition-opacity\"\n :class=\"\n isActive || confirmDelete\n ? 'tpl:opacity-100'\n : 'tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100'\n \"\n >\n <button\n v-if=\"canCreateSubfolder && canCreateFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.addSubfolder\"\n :aria-label=\"t.mediaLibrary.addSubfolder\"\n @click.stop=\"startCreateSubfolder\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canRenameFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.renameFolder\"\n :aria-label=\"t.mediaLibrary.renameFolder\"\n @click.stop=\"startRename\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canDeleteFolder\"\n type=\"button\"\n data-testid=\"media-folder-delete\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n :aria-label=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n @click.stop=\"handleDeleteClick\"\n >\n <Trash2\n :size=\"12\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-danger)\"\n />\n </button>\n </span>\n </div>\n\n <!-- Children (expanded) -->\n <div v-if=\"shouldExpand\">\n <MediaFolderTreeNode\n v-for=\"child in folder.children\"\n :key=\"child.id\"\n :folder=\"child\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"depth + 1\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"\n (name, parentId) => emit('createFolder', name, parentId)\n \"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- Subfolder creation input -->\n <div\n v-if=\"isCreatingSubfolder\"\n :style=\"{ paddingLeft: `${(depth + 1) * 16 + 8}px` }\"\n class=\"tpl:py-1.5 tpl:pr-2\"\n >\n <input\n v-model=\"subfolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.subfolderName\"\n autofocus\n @keydown.enter=\"confirmCreateSubfolder\"\n @keydown.escape.stop=\"cancelCreateSubfolder\"\n @blur=\"confirmCreateSubfolder\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { ChevronRight, Folder, Pencil, Plus, Trash2 } from \"@lucide/vue\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n folder: MediaFolderNode;\n currentFolderId: string | null;\n depth: number;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isExpanded = ref(false);\nconst isRenaming = ref(false);\nconst renameValue = ref(\"\");\nconst isCreatingSubfolder = ref(false);\nconst subfolderName = ref(\"\");\nconst confirmDelete = ref(false);\n\nconst MAX_DEPTH = 5;\n\nconst hasChildren = computed(() => props.folder.children.length > 0);\n\nconst canCreateSubfolder = computed(() => props.depth < MAX_DEPTH - 1);\n\nconst isActive = computed(() => props.currentFolderId === props.folder.id);\n\nconst isDescendantActive = computed(() => {\n if (!props.currentFolderId) return false;\n return containsFolder(props.folder.children, props.currentFolderId);\n});\n\nconst shouldExpand = computed(\n () => isExpanded.value || isDescendantActive.value,\n);\n\nfunction containsFolder(folders: MediaFolderNode[], id: string): boolean {\n for (const folder of folders) {\n if (folder.id === id) return true;\n if (containsFolder(folder.children, id)) return true;\n }\n return false;\n}\n\nfunction toggleExpand(): void {\n isExpanded.value = !isExpanded.value;\n}\n\nfunction startRename(): void {\n isRenaming.value = true;\n renameValue.value = props.folder.name;\n}\n\nfunction confirmRename(): void {\n const trimmed = renameValue.value.trim();\n if (trimmed && trimmed !== props.folder.name) {\n emit(\"renameFolder\", props.folder.id, trimmed);\n }\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction cancelRename(): void {\n isRenaming.value = false;\n renameValue.value = \"\";\n}\n\nfunction startCreateSubfolder(): void {\n isCreatingSubfolder.value = true;\n subfolderName.value = \"\";\n isExpanded.value = true;\n}\n\nfunction confirmCreateSubfolder(): void {\n if (subfolderName.value.trim()) {\n emit(\"createFolder\", subfolderName.value.trim(), props.folder.id);\n }\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction cancelCreateSubfolder(): void {\n isCreatingSubfolder.value = false;\n subfolderName.value = \"\";\n}\n\nfunction handleDeleteClick(): void {\n if (confirmDelete.value) {\n emit(\"deleteFolder\", props.folder.id);\n confirmDelete.value = false;\n return;\n }\n confirmDelete.value = true;\n}\n\nfunction onRowFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (next && (event.currentTarget as HTMLElement).contains(next)) {\n return;\n }\n confirmDelete.value = false;\n}\n</script>\n\n<template>\n <div>\n <!-- Folder row -->\n <div\n class=\"tpl:group tpl:flex tpl:w-full tpl:items-center tpl:gap-1 tpl:py-1.5 tpl:pr-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n :style=\"{\n paddingLeft: `${depth * 16 + 8}px`,\n backgroundColor: isActive ? 'var(--tpl-bg-active)' : 'transparent',\n color: isActive ? 'var(--tpl-primary)' : 'var(--tpl-text)',\n }\"\n @focusout=\"onRowFocusOut\"\n >\n <!-- Expand/collapse chevron -->\n <button\n v-if=\"hasChildren || isCreatingSubfolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-4 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :aria-expanded=\"shouldExpand\"\n :aria-label=\"\n shouldExpand\n ? t.mediaLibrary.collapseFolder\n : t.mediaLibrary.expandFolder\n \"\n @click.stop=\"toggleExpand\"\n >\n <ChevronRight\n class=\"tpl:transition-transform tpl:duration-150\"\n :class=\"{ 'tpl:rotate-90': shouldExpand }\"\n :size=\"10\"\n :stroke-width=\"2\"\n />\n </button>\n <span v-else class=\"tpl:size-4 tpl:shrink-0\" />\n\n <!-- Folder icon + name (clickable) -->\n <button\n type=\"button\"\n class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-1.5\"\n @click=\"emit('navigate', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <template v-if=\"!isRenaming\">\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n </template>\n </button>\n\n <!-- Inline rename input -->\n <input\n v-if=\"isRenaming\"\n v-model=\"renameValue\"\n type=\"text\"\n class=\"tpl:min-w-0 tpl:flex-1 tpl:rounded tpl:border tpl:px-1.5 tpl:py-0.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n autofocus\n @keydown.enter=\"confirmRename\"\n @keydown.escape.stop=\"cancelRename\"\n @blur=\"confirmRename\"\n @click.stop\n />\n\n <span\n v-if=\"!isRenaming\"\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:gap-0.5 tpl:transition-opacity\"\n :class=\"\n isActive || confirmDelete\n ? 'tpl:opacity-100'\n : 'tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100'\n \"\n >\n <button\n v-if=\"canCreateSubfolder && canCreateFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.addSubfolder\"\n :aria-label=\"t.mediaLibrary.addSubfolder\"\n @click.stop=\"startCreateSubfolder\"\n >\n <Plus :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canRenameFolder\"\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"t.mediaLibrary.renameFolder\"\n :aria-label=\"t.mediaLibrary.renameFolder\"\n @click.stop=\"startRename\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"canDeleteFolder\"\n type=\"button\"\n data-testid=\"media-folder-delete\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded tpl:transition-colors\"\n :title=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n :aria-label=\"\n confirmDelete\n ? t.mediaLibrary.deleteFolderConfirm\n : t.mediaLibrary.deleteFolder\n \"\n @click.stop=\"handleDeleteClick\"\n >\n <Trash2\n :size=\"12\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-danger)\"\n />\n </button>\n </span>\n </div>\n\n <!-- Children (expanded) -->\n <div v-if=\"shouldExpand\">\n <MediaFolderTreeNode\n v-for=\"child in folder.children\"\n :key=\"child.id\"\n :folder=\"child\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"depth + 1\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"\n (name, parentId) => emit('createFolder', name, parentId)\n \"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- Subfolder creation input -->\n <div\n v-if=\"isCreatingSubfolder\"\n :style=\"{ paddingLeft: `${(depth + 1) * 16 + 8}px` }\"\n class=\"tpl:py-1.5 tpl:pr-2\"\n >\n <input\n v-model=\"subfolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.subfolderName\"\n autofocus\n @keydown.enter=\"confirmCreateSubfolder\"\n @keydown.escape.stop=\"cancelCreateSubfolder\"\n @blur=\"confirmCreateSubfolder\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFolderTreeNode from \"./MediaFolderTreeNode.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaViewMode } from \"../../composable\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { File, Plus, Star } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n viewMode: MediaViewMode;\n hasFrequentlyUsed: boolean;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n (e: \"showFrequentlyUsed\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isCreating = ref(false);\nconst newFolderName = ref(\"\");\n\nfunction startCreateFolder(): void {\n isCreating.value = true;\n newFolderName.value = \"\";\n}\n\nfunction confirmCreateFolder(): void {\n if (newFolderName.value.trim()) {\n emit(\"createFolder\", newFolderName.value.trim());\n }\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n\nfunction cancelCreate(): void {\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n</script>\n\n<template>\n <div\n data-testid=\"media-folder-tree\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:overflow-y-auto\"\n >\n <!-- All Images root -->\n <button\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n backgroundColor:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('navigate', null)\"\n >\n <File :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.allFiles }}\n </button>\n\n <!-- Recursive folder tree -->\n <MediaFolderTreeNode\n v-for=\"folder in folders\"\n :key=\"folder.id\"\n :folder=\"folder\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"0\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"(name, parentId) => emit('createFolder', name, parentId)\"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- New folder input -->\n <div v-if=\"isCreating\" class=\"tpl:px-3 tpl:py-2\">\n <input\n v-model=\"newFolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.folderName\"\n autofocus\n @keydown.enter=\"confirmCreateFolder\"\n @keydown.escape.stop=\"cancelCreate\"\n @blur=\"confirmCreateFolder\"\n />\n </div>\n\n <!-- New folder button -->\n <button\n v-if=\"!isCreating && canCreateFolder\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"startCreateFolder\"\n >\n <Plus :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.newFolder }}\n </button>\n\n <!-- Spacer to push frequently used to bottom -->\n <div class=\"tpl:flex-1\" />\n\n <!-- Frequently Used -->\n <button\n v-if=\"hasFrequentlyUsed\"\n data-testid=\"media-frequently-used\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:border-t tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: 'var(--tpl-border)',\n backgroundColor:\n viewMode === 'frequently-used'\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'frequently-used'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('showFrequentlyUsed')\"\n >\n <Star :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.frequentlyUsed }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFolderTreeNode from \"./MediaFolderTreeNode.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaViewMode } from \"../../composable\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { File, Plus, Star } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n viewMode: MediaViewMode;\n hasFrequentlyUsed: boolean;\n canCreateFolder?: boolean;\n canRenameFolder?: boolean;\n canDeleteFolder?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"navigate\", folderId: string | null): void;\n (e: \"createFolder\", name: string, parentId?: string | null): void;\n (e: \"renameFolder\", folderId: string, name: string): void;\n (e: \"deleteFolder\", folderId: string): void;\n (e: \"showFrequentlyUsed\"): void;\n}>();\n\nconst { t } = useI18n();\n\nconst isCreating = ref(false);\nconst newFolderName = ref(\"\");\n\nfunction startCreateFolder(): void {\n isCreating.value = true;\n newFolderName.value = \"\";\n}\n\nfunction confirmCreateFolder(): void {\n if (newFolderName.value.trim()) {\n emit(\"createFolder\", newFolderName.value.trim());\n }\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n\nfunction cancelCreate(): void {\n isCreating.value = false;\n newFolderName.value = \"\";\n}\n</script>\n\n<template>\n <div\n data-testid=\"media-folder-tree\"\n class=\"tpl:flex tpl:h-full tpl:flex-col tpl:overflow-y-auto\"\n >\n <!-- All Images root -->\n <button\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n backgroundColor:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'files' && currentFolderId === null\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('navigate', null)\"\n >\n <File :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.allFiles }}\n </button>\n\n <!-- Recursive folder tree -->\n <MediaFolderTreeNode\n v-for=\"folder in folders\"\n :key=\"folder.id\"\n :folder=\"folder\"\n :current-folder-id=\"currentFolderId\"\n :depth=\"0\"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"emit('navigate', $event)\"\n @create-folder=\"(name, parentId) => emit('createFolder', name, parentId)\"\n @rename-folder=\"(id, name) => emit('renameFolder', id, name)\"\n @delete-folder=\"emit('deleteFolder', $event)\"\n />\n\n <!-- New folder input -->\n <div v-if=\"isCreating\" class=\"tpl:px-3 tpl:py-2\">\n <input\n v-model=\"newFolderName\"\n type=\"text\"\n class=\"tpl:w-full tpl:rounded tpl:border tpl:px-2 tpl:py-1 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-primary);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.folderName\"\n autofocus\n @keydown.enter=\"confirmCreateFolder\"\n @keydown.escape.stop=\"cancelCreate\"\n @blur=\"confirmCreateFolder\"\n />\n </div>\n\n <!-- New folder button -->\n <button\n v-if=\"!isCreating && canCreateFolder\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"startCreateFolder\"\n >\n <Plus :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.newFolder }}\n </button>\n\n <!-- Spacer to push frequently used to bottom -->\n <div class=\"tpl:flex-1\" />\n\n <!-- Frequently Used -->\n <button\n v-if=\"hasFrequentlyUsed\"\n data-testid=\"media-frequently-used\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-2 tpl:border-t tpl:px-3 tpl:py-2 tpl:text-left tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: 'var(--tpl-border)',\n backgroundColor:\n viewMode === 'frequently-used'\n ? 'var(--tpl-bg-active)'\n : 'transparent',\n color:\n viewMode === 'frequently-used'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text)',\n }\"\n @click=\"emit('showFrequentlyUsed')\"\n >\n <Star :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.frequentlyUsed }}\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n mimeType: string;\n}>();\n\ninterface FileTypeConfig {\n label: string;\n color: string;\n bgColor: string;\n icon: \"document\" | \"video\" | \"audio\";\n}\n\nconst FILE_TYPE_MAP: Record<string, FileTypeConfig> = {\n \"application/pdf\": {\n label: \"PDF\",\n color: \"var(--tpl-danger)\",\n bgColor: \"var(--tpl-danger-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/msword\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-excel\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-powerpoint\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"text/csv\": {\n label: \"CSV\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"text/plain\": {\n label: \"TXT\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n },\n \"video/mp4\": {\n label: \"MP4\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/quicktime\": {\n label: \"MOV\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/webm\": {\n label: \"WEBM\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"audio/mpeg\": {\n label: \"MP3\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/wav\": {\n label: \"WAV\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/ogg\": {\n label: \"OGG\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n};\n\nconst DEFAULT_CONFIG: FileTypeConfig = {\n label: \"FILE\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n};\n\nconst config = computed(\n (): FileTypeConfig => FILE_TYPE_MAP[props.mimeType] ?? DEFAULT_CONFIG,\n);\n</script>\n\n<template>\n <div\n class=\"tpl:flex tpl:aspect-square tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-2\"\n :style=\"{ backgroundColor: config.bgColor }\"\n >\n <svg\n v-if=\"config.icon === 'document'\"\n width=\"60\"\n height=\"60\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14 2 14 8 20 8\" />\n <line x1=\"8\" y1=\"13\" x2=\"16\" y2=\"13\" />\n <line x1=\"8\" y1=\"17\" x2=\"12\" y2=\"17\" />\n </svg>\n\n <svg\n v-else-if=\"config.icon === 'video'\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\" />\n <polygon points=\"10,8 16,12 10,16\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n\n <svg\n v-else\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M9 18V5l12-2v13\" />\n <circle cx=\"6\" cy=\"18\" r=\"3\" />\n <circle cx=\"18\" cy=\"16\" r=\"3\" />\n </svg>\n\n <span\n class=\"tpl:text-xs tpl:font-bold tpl:tracking-wider\"\n :style=\"{ color: config.color }\"\n >\n {{ config.label }}\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from \"vue\";\n\nconst props = defineProps<{\n mimeType: string;\n}>();\n\ninterface FileTypeConfig {\n label: string;\n color: string;\n bgColor: string;\n icon: \"document\" | \"video\" | \"audio\";\n}\n\nconst FILE_TYPE_MAP: Record<string, FileTypeConfig> = {\n \"application/pdf\": {\n label: \"PDF\",\n color: \"var(--tpl-danger)\",\n bgColor: \"var(--tpl-danger-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/msword\": {\n label: \"DOC\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-excel\": {\n label: \"XLS\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"application/vnd.openxmlformats-officedocument.presentationml.presentation\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"application/vnd.ms-powerpoint\": {\n label: \"PPT\",\n color: \"var(--tpl-warning)\",\n bgColor: \"var(--tpl-warning-light)\",\n icon: \"document\",\n },\n \"text/csv\": {\n label: \"CSV\",\n color: \"var(--tpl-success)\",\n bgColor: \"var(--tpl-success-light)\",\n icon: \"document\",\n },\n \"text/plain\": {\n label: \"TXT\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n },\n \"video/mp4\": {\n label: \"MP4\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/quicktime\": {\n label: \"MOV\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"video/webm\": {\n label: \"WEBM\",\n color: \"var(--tpl-primary)\",\n bgColor: \"var(--tpl-primary-light)\",\n icon: \"video\",\n },\n \"audio/mpeg\": {\n label: \"MP3\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/wav\": {\n label: \"WAV\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n \"audio/ogg\": {\n label: \"OGG\",\n color: \"var(--tpl-secondary)\",\n bgColor: \"var(--tpl-secondary-light)\",\n icon: \"audio\",\n },\n};\n\nconst DEFAULT_CONFIG: FileTypeConfig = {\n label: \"FILE\",\n color: \"var(--tpl-text-muted)\",\n bgColor: \"var(--tpl-bg-hover)\",\n icon: \"document\",\n};\n\nconst config = computed(\n (): FileTypeConfig => FILE_TYPE_MAP[props.mimeType] ?? DEFAULT_CONFIG,\n);\n</script>\n\n<template>\n <div\n class=\"tpl:flex tpl:aspect-square tpl:flex-col tpl:items-center tpl:justify-center tpl:gap-2\"\n :style=\"{ backgroundColor: config.bgColor }\"\n >\n <svg\n v-if=\"config.icon === 'document'\"\n width=\"60\"\n height=\"60\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n <polyline points=\"14 2 14 8 20 8\" />\n <line x1=\"8\" y1=\"13\" x2=\"16\" y2=\"13\" />\n <line x1=\"8\" y1=\"17\" x2=\"12\" y2=\"17\" />\n </svg>\n\n <svg\n v-else-if=\"config.icon === 'video'\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\" />\n <polygon points=\"10,8 16,12 10,16\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n\n <svg\n v-else\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n :style=\"{ color: config.color }\"\n >\n <path d=\"M9 18V5l12-2v13\" />\n <circle cx=\"6\" cy=\"18\" r=\"3\" />\n <circle cx=\"18\" cy=\"16\" r=\"3\" />\n </svg>\n\n <span\n class=\"tpl:text-xs tpl:font-bold tpl:tracking-wider\"\n :style=\"{ color: config.color }\"\n >\n {{ config.label }}\n </span>\n </div>\n</template>\n","/**\n * An absolute date label in the **host's** locale.\n *\n * `toLocaleDateString(undefined, …)` formats in the *runtime's* locale — the\n * browser's or the OS's — not the one that reached this package as\n * `MediaLibraryModal`'s `locale` prop. So the library's captions read in the\n * browser's language while every string around them was translated from the\n * editor's `init({ locale })`.\n *\n * Two guards, both reachable from consumer input:\n *\n * - **A malformed tag makes `Intl` throw `RangeError`.** The locale originates\n * in the host's free-text config, so a bad value would take down the grid\n * for a caption. Falls back to the runtime locale — the behaviour before a\n * locale was threaded through at all — rather than to a hardcoded language.\n * A well-formed but *unregistered* tag (`\"zz\"`) is not caught: `Intl`\n * resolves its own fallback.\n * - **An unparseable date yields `\"\"`**, never a literal \"Invalid Date\".\n *\n * A deliberate twin of `formatAbsoluteDateTime` in `@templatical/editor`. They\n * are copies, not a shared module: this package cannot import from the editor\n * (the dependency runs the other way) and `@templatical/types` is the\n * dependency-free leaf rather than a home for display helpers. Behavioural\n * drift between them fails `tests/formatAbsoluteDate.test.ts`, which carries\n * the editor suite's own cases.\n */\nexport function formatAbsoluteDate(\n iso: string,\n locale: string | undefined,\n options?: Intl.DateTimeFormatOptions,\n): string {\n const parsed = new Date(iso);\n if (Number.isNaN(parsed.getTime())) return \"\";\n\n const tag = locale?.trim().replace(/_/g, \"-\") || undefined;\n\n try {\n return parsed.toLocaleDateString(tag, options);\n } catch {\n return parsed.toLocaleDateString(undefined, options);\n }\n}\n","const UNITS = [\"B\", \"KB\", \"MB\", \"GB\"];\n\n/** `10485760` → `\"10 MB\"`, `1572864` → `\"1.5 MB\"`: whole units drop the `.0`. */\nexport function formatFileSize(bytes: number): string {\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < UNITS.length - 1) {\n value /= 1024;\n unit += 1;\n }\n const rounded = Math.round(value * 10) / 10;\n return `${rounded} ${UNITS[unit]}`;\n}\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset, MediaCategory } from \"@templatical/types\";\nimport { useIntersectionObserver } from \"@vueuse/core\";\nimport { Check, File, LoaderCircle, Pencil, RefreshCw } from \"@lucide/vue\";\nimport { inject, ref } from \"vue\";\n\nconst props = defineProps<{\n items: MediaAsset[];\n selectedIds: Set<string>;\n isLoading: boolean;\n hasMore: boolean;\n accept?: MediaCategory[];\n layout?: \"grid\" | \"list\";\n searchQuery?: string;\n canUpdate?: boolean;\n canReplace?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", item: MediaAsset): void;\n (e: \"toggle\", id: string): void;\n (e: \"loadMore\"): void;\n (e: \"edit\", item: MediaAsset): void;\n (e: \"replace\", item: MediaAsset): void;\n (e: \"confirm\", item: MediaAsset): void;\n}>();\n\nconst { isAcceptedMimeType, isImageMimeType } = useMediaCategories();\n\nfunction mimeOf(item: MediaAsset): string {\n return item.mimeType ?? \"\";\n}\n\nfunction thumbOf(item: MediaAsset): string {\n return item.thumbnailUrl ?? item.url;\n}\n\nfunction isSelectable(item: MediaAsset): boolean {\n if (!props.accept || props.accept.length === 0) {\n return true;\n }\n\n return isAcceptedMimeType(mimeOf(item), props.accept);\n}\n\nfunction showEdit(item: MediaAsset): boolean {\n return props.canUpdate === true && item.canUpdate !== false;\n}\n\nfunction showReplace(item: MediaAsset): boolean {\n return props.canReplace === true && item.canUpdate !== false;\n}\n\nfunction handleItemClick(item: MediaAsset, event: MouseEvent): void {\n if (event.metaKey || event.ctrlKey) {\n emit(\"toggle\", item.id);\n return;\n }\n emit(\"select\", item);\n}\n\nfunction handleItemDblClick(item: MediaAsset): void {\n if (isSelectable(item)) {\n emit(\"confirm\", item);\n }\n}\n\nfunction handleItemKeydown(item: MediaAsset, event: KeyboardEvent): void {\n if (event.key !== \"Enter\" && event.key !== \" \") {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n emit(\"select\", item);\n}\n\nfunction isSelected(item: MediaAsset): boolean {\n return props.selectedIds.has(item.id);\n}\n\nfunction actionChromeClass(item: MediaAsset): string {\n return isSelected(item)\n ? \"tpl:opacity-100\"\n : \"tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100\";\n}\n\nconst { t } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst sentinelRef = ref<HTMLElement | null>(null);\n\nuseIntersectionObserver(\n sentinelRef,\n ([{ isIntersecting }]) => {\n if (isIntersecting && props.hasMore && !props.isLoading) {\n emit(\"loadMore\");\n }\n },\n { threshold: 0.1 },\n);\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n</script>\n\n<template>\n <div class=\"tpl:p-4\">\n <div\n v-if=\"isLoading && items.length === 0\"\n role=\"status\"\n aria-busy=\"true\"\n :aria-label=\"t.mediaLibrary.loading\"\n :class=\"\n layout === 'list'\n ? 'tpl:flex tpl:flex-col tpl:gap-1'\n : 'tpl:grid tpl:grid-cols-4 tpl:gap-3'\n \"\n >\n <div\n v-for=\"i in 8\"\n :key=\"i\"\n class=\"tpl-pulse tpl:rounded-lg\"\n :class=\"layout === 'list' ? 'tpl:h-12' : 'tpl:aspect-square'\"\n style=\"background-color: var(--tpl-bg-hover)\"\n aria-hidden=\"true\"\n />\n </div>\n\n <div\n v-else-if=\"items.length === 0\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:py-16\"\n >\n <File\n class=\"tpl:mb-3\"\n :size=\"40\"\n :stroke-width=\"1\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n searchQuery ? t.mediaLibrary.noSearchResults : t.mediaLibrary.noFiles\n }}\n </p>\n </div>\n\n <div\n v-else-if=\"layout !== 'list'\"\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:grid tpl:grid-cols-4 tpl:gap-3\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-item tpl:group tpl:relative tpl:overflow-hidden tpl:rounded-lg tpl:border-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n 'tpl:cursor-pointer',\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n isSelected(item) ? 'tpl-media-item--selected' : '',\n ]\"\n :style=\"{\n borderColor: isSelected(item) ? 'var(--tpl-primary)' : 'transparent',\n backgroundColor:\n !isSelectable(item) && !isSelected(item)\n ? 'var(--tpl-bg)'\n : 'var(--tpl-bg-hover)',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div class=\"tpl:aspect-square\">\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <MediaFileIcon v-else :mime-type=\"mimeOf(item)\" />\n </div>\n <div class=\"tpl:px-2 tpl:py-1.5\">\n <p\n class=\"tpl:truncate tpl:text-[10px] tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p\n class=\"tpl:flex tpl:justify-between tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <span v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</span>\n <span\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n {{ item.width }}×{{ item.height }}\n </span>\n </p>\n </div>\n <div\n class=\"tpl:absolute tpl:top-1.5 tpl:left-1.5 tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:absolute tpl:top-1.5 tpl:right-1.5 tpl:flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div\n v-else\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-list-item tpl:group tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-3 tpl:rounded-lg tpl:px-3 tpl:py-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n ]\"\n :style=\"{\n backgroundColor: isSelected(item)\n ? 'var(--tpl-bg-hover)'\n : 'transparent',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div\n class=\"tpl:size-10 tpl:shrink-0 tpl:overflow-hidden tpl:rounded\"\n style=\"background-color: var(--tpl-bg-hover)\"\n >\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <div v-else class=\"tpl-list-icon tpl:size-full\">\n <MediaFileIcon :mime-type=\"mimeOf(item)\" />\n </div>\n </div>\n\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p class=\"tpl:text-[10px]\" style=\"color: var(--tpl-text-muted)\">\n <template v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> · </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n ·\n {{ item.width }}×{{ item.height }}\n </template>\n </p>\n </div>\n\n <div\n class=\"tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:flex tpl:size-5 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div ref=\"sentinelRef\" class=\"tpl:h-4\" />\n\n <div\n v-if=\"isLoading && items.length > 0\"\n class=\"tpl:flex tpl:justify-center tpl:py-4\"\n >\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-media-item:not(.tpl-media-item--selected):hover {\n border-color: var(--tpl-border) !important;\n}\n\n.tpl-media-list-item:hover {\n background-color: var(--tpl-bg-hover) !important;\n}\n\n.tpl-list-icon {\n transform: scale(0.35);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { useI18n } from \"../../composables/useI18n\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset, MediaCategory } from \"@templatical/types\";\nimport { useIntersectionObserver } from \"@vueuse/core\";\nimport { Check, File, LoaderCircle, Pencil, RefreshCw } from \"@lucide/vue\";\nimport { inject, ref } from \"vue\";\n\nconst props = defineProps<{\n items: MediaAsset[];\n selectedIds: Set<string>;\n isLoading: boolean;\n hasMore: boolean;\n accept?: MediaCategory[];\n layout?: \"grid\" | \"list\";\n searchQuery?: string;\n canUpdate?: boolean;\n canReplace?: boolean;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", item: MediaAsset): void;\n (e: \"toggle\", id: string): void;\n (e: \"loadMore\"): void;\n (e: \"edit\", item: MediaAsset): void;\n (e: \"replace\", item: MediaAsset): void;\n (e: \"confirm\", item: MediaAsset): void;\n}>();\n\nconst { isAcceptedMimeType, isImageMimeType } = useMediaCategories();\n\nfunction mimeOf(item: MediaAsset): string {\n return item.mimeType ?? \"\";\n}\n\nfunction thumbOf(item: MediaAsset): string {\n return item.thumbnailUrl ?? item.url;\n}\n\nfunction isSelectable(item: MediaAsset): boolean {\n if (!props.accept || props.accept.length === 0) {\n return true;\n }\n\n return isAcceptedMimeType(mimeOf(item), props.accept);\n}\n\nfunction showEdit(item: MediaAsset): boolean {\n return props.canUpdate === true && item.canUpdate !== false;\n}\n\nfunction showReplace(item: MediaAsset): boolean {\n return props.canReplace === true && item.canUpdate !== false;\n}\n\nfunction handleItemClick(item: MediaAsset, event: MouseEvent): void {\n if (event.metaKey || event.ctrlKey) {\n emit(\"toggle\", item.id);\n return;\n }\n emit(\"select\", item);\n}\n\nfunction handleItemDblClick(item: MediaAsset): void {\n if (isSelectable(item)) {\n emit(\"confirm\", item);\n }\n}\n\nfunction handleItemKeydown(item: MediaAsset, event: KeyboardEvent): void {\n if (event.key !== \"Enter\" && event.key !== \" \") {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n emit(\"select\", item);\n}\n\nfunction isSelected(item: MediaAsset): boolean {\n return props.selectedIds.has(item.id);\n}\n\nfunction actionChromeClass(item: MediaAsset): string {\n return isSelected(item)\n ? \"tpl:opacity-100\"\n : \"tpl:opacity-0 tpl:group-hover:opacity-100 tpl:group-focus-within:opacity-100\";\n}\n\nconst { t } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst sentinelRef = ref<HTMLElement | null>(null);\n\nuseIntersectionObserver(\n sentinelRef,\n ([{ isIntersecting }]) => {\n if (isIntersecting && props.hasMore && !props.isLoading) {\n emit(\"loadMore\");\n }\n },\n { threshold: 0.1 },\n);\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n</script>\n\n<template>\n <div class=\"tpl:p-4\">\n <div\n v-if=\"isLoading && items.length === 0\"\n role=\"status\"\n aria-busy=\"true\"\n :aria-label=\"t.mediaLibrary.loading\"\n :class=\"\n layout === 'list'\n ? 'tpl:flex tpl:flex-col tpl:gap-1'\n : 'tpl:grid tpl:grid-cols-4 tpl:gap-3'\n \"\n >\n <div\n v-for=\"i in 8\"\n :key=\"i\"\n class=\"tpl-pulse tpl:rounded-lg\"\n :class=\"layout === 'list' ? 'tpl:h-12' : 'tpl:aspect-square'\"\n style=\"background-color: var(--tpl-bg-hover)\"\n aria-hidden=\"true\"\n />\n </div>\n\n <div\n v-else-if=\"items.length === 0\"\n class=\"tpl:flex tpl:flex-col tpl:items-center tpl:justify-center tpl:py-16\"\n >\n <File\n class=\"tpl:mb-3\"\n :size=\"40\"\n :stroke-width=\"1\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n searchQuery ? t.mediaLibrary.noSearchResults : t.mediaLibrary.noFiles\n }}\n </p>\n </div>\n\n <div\n v-else-if=\"layout !== 'list'\"\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:grid tpl:grid-cols-4 tpl:gap-3\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-item tpl:group tpl:relative tpl:overflow-hidden tpl:rounded-lg tpl:border-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n 'tpl:cursor-pointer',\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n isSelected(item) ? 'tpl-media-item--selected' : '',\n ]\"\n :style=\"{\n borderColor: isSelected(item) ? 'var(--tpl-primary)' : 'transparent',\n backgroundColor:\n !isSelectable(item) && !isSelected(item)\n ? 'var(--tpl-bg)'\n : 'var(--tpl-bg-hover)',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div class=\"tpl:aspect-square\">\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <MediaFileIcon v-else :mime-type=\"mimeOf(item)\" />\n </div>\n <div class=\"tpl:px-2 tpl:py-1.5\">\n <p\n class=\"tpl:truncate tpl:text-[10px] tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p\n class=\"tpl:flex tpl:justify-between tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <span v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</span>\n <span\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n {{ item.width }}×{{ item.height }}\n </span>\n </p>\n </div>\n <div\n class=\"tpl:absolute tpl:top-1.5 tpl:left-1.5 tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"11\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-overlay);\n color: var(--tpl-on-primary);\n \"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"11\" :stroke-width=\"2\" />\n </button>\n </div>\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:absolute tpl:top-1.5 tpl:right-1.5 tpl:flex tpl:size-5 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div\n v-else\n role=\"listbox\"\n :aria-label=\"t.mediaLibrary.filesListAriaLabel\"\n aria-multiselectable=\"true\"\n class=\"tpl:flex tpl:flex-col tpl:gap-1\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n role=\"option\"\n tabindex=\"0\"\n data-testid=\"media-library-item\"\n class=\"tpl-media-list-item tpl:group tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-3 tpl:rounded-lg tpl:px-3 tpl:py-2 tpl:transition-all tpl:duration-150\"\n :data-media-id=\"item.id\"\n :aria-selected=\"isSelected(item)\"\n :aria-label=\"item.filename || item.url\"\n :class=\"[\n !isSelectable(item) && !isSelected(item) ? 'tpl:opacity-60' : '',\n ]\"\n :style=\"{\n backgroundColor: isSelected(item)\n ? 'var(--tpl-bg-hover)'\n : 'transparent',\n }\"\n @click=\"handleItemClick(item, $event)\"\n @dblclick=\"handleItemDblClick(item)\"\n @keydown=\"handleItemKeydown(item, $event)\"\n >\n <div\n class=\"tpl:size-10 tpl:shrink-0 tpl:overflow-hidden tpl:rounded\"\n style=\"background-color: var(--tpl-bg-hover)\"\n >\n <img\n v-if=\"isImageMimeType(mimeOf(item))\"\n :src=\"thumbOf(item)\"\n :alt=\"item.filename || item.url\"\n class=\"tpl:size-full tpl:object-cover\"\n loading=\"lazy\"\n />\n <div v-else class=\"tpl-list-icon tpl:size-full\">\n <MediaFileIcon :mime-type=\"mimeOf(item)\" />\n </div>\n </div>\n\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename || item.url }}\n </p>\n <p class=\"tpl:text-[10px]\" style=\"color: var(--tpl-text-muted)\">\n <template v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> · </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template\n v-if=\"isImageMimeType(mimeOf(item)) && item.width && item.height\"\n >\n ·\n {{ item.width }}×{{ item.height }}\n </template>\n </p>\n </div>\n\n <div\n class=\"tpl:flex tpl:gap-1 tpl:transition-opacity tpl:duration-150\"\n :class=\"actionChromeClass(item)\"\n >\n <button\n v-if=\"showEdit(item)\"\n type=\"button\"\n data-testid=\"media-edit\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.editFile\"\n :aria-label=\"t.mediaLibrary.editFile\"\n @click.stop=\"emit('edit', item)\"\n >\n <Pencil :size=\"12\" :stroke-width=\"2\" />\n </button>\n <button\n v-if=\"showReplace(item)\"\n type=\"button\"\n data-testid=\"media-replace\"\n class=\"tpl:flex tpl:size-6 tpl:shrink-0 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded\"\n style=\"color: var(--tpl-text-muted)\"\n :title=\"t.mediaLibrary.replaceFile\"\n :aria-label=\"t.mediaLibrary.replaceFile\"\n @click.stop=\"emit('replace', item)\"\n >\n <RefreshCw :size=\"12\" :stroke-width=\"2\" />\n </button>\n </div>\n\n <div\n v-if=\"isSelected(item)\"\n aria-hidden=\"true\"\n class=\"tpl:flex tpl:size-5 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:rounded-full\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n >\n <Check :size=\"12\" :stroke-width=\"3\" />\n </div>\n </div>\n </div>\n\n <div ref=\"sentinelRef\" class=\"tpl:h-4\" />\n\n <div\n v-if=\"isLoading && items.length > 0\"\n class=\"tpl:flex tpl:justify-center tpl:py-4\"\n >\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-media-item:not(.tpl-media-item--selected):hover {\n border-color: var(--tpl-border) !important;\n}\n\n.tpl-media-list-item:hover {\n background-color: var(--tpl-bg-hover) !important;\n}\n\n.tpl-list-icon {\n transform: scale(0.35);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport { LoaderCircle } from \"@lucide/vue\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n isImporting: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"import\", url: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst urlValue = ref(\"\");\n\nwatch(\n () => props.visible,\n (visible) => {\n if (visible) {\n urlValue.value = \"\";\n }\n },\n);\n\nfunction handleImport(): void {\n const trimmedUrl = urlValue.value.trim();\n if (!trimmedUrl || props.isImporting) {\n return;\n }\n\n emit(\"import\", trimmedUrl);\n}\n\nfunction handleClose(): void {\n if (!props.isImporting) {\n emit(\"close\");\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleImport();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"handleClose\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-import-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-import-title\"\n class=\"tpl:mb-4 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.importFromUrl }}\n </h3>\n\n <!-- URL Input -->\n <div class=\"tpl:mb-3\">\n <input\n v-model=\"urlValue\"\n type=\"url\"\n :aria-label=\"t.mediaLibrary.importFromUrl\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-import-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.importUrlPlaceholder\"\n :disabled=\"isImporting\"\n autofocus\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-import-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isImporting\"\n :class=\"{\n 'tpl:cursor-not-allowed tpl:opacity-50': isImporting,\n }\"\n @click=\"handleClose\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!urlValue.trim() || isImporting\"\n @click=\"handleImport\"\n >\n <span\n v-if=\"isImporting\"\n class=\"tpl:flex tpl:items-center tpl:gap-1.5\"\n >\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.mediaLibrary.importing }}\n </span>\n <span v-else>\n {{ t.mediaLibrary.import }}\n </span>\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport { LoaderCircle } from \"@lucide/vue\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n isImporting: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"import\", url: string): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst urlValue = ref(\"\");\n\nwatch(\n () => props.visible,\n (visible) => {\n if (visible) {\n urlValue.value = \"\";\n }\n },\n);\n\nfunction handleImport(): void {\n const trimmedUrl = urlValue.value.trim();\n if (!trimmedUrl || props.isImporting) {\n return;\n }\n\n emit(\"import\", trimmedUrl);\n}\n\nfunction handleClose(): void {\n if (!props.isImporting) {\n emit(\"close\");\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleImport();\n }\n if (event.key === \"Escape\") {\n handleClose();\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"handleClose\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-import-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-import-title\"\n class=\"tpl:mb-4 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.importFromUrl }}\n </h3>\n\n <!-- URL Input -->\n <div class=\"tpl:mb-3\">\n <input\n v-model=\"urlValue\"\n type=\"url\"\n :aria-label=\"t.mediaLibrary.importFromUrl\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-import-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.importUrlPlaceholder\"\n :disabled=\"isImporting\"\n autofocus\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-import-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isImporting\"\n :class=\"{\n 'tpl:cursor-not-allowed tpl:opacity-50': isImporting,\n }\"\n @click=\"handleClose\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!urlValue.trim() || isImporting\"\n @click=\"handleImport\"\n >\n <span\n v-if=\"isImporting\"\n class=\"tpl:flex tpl:items-center tpl:gap-1.5\"\n >\n <LoaderCircle\n class=\"tpl:animate-spin\"\n :size=\"12\"\n :stroke-width=\"2\"\n />\n {{ t.mediaLibrary.importing }}\n </span>\n <span v-else>\n {{ t.mediaLibrary.import }}\n </span>\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { File, Folder } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", folderId: string | null): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst pickerRef = ref<HTMLDivElement | null>(null);\n\ninterface FlatFolder {\n id: string;\n name: string;\n depth: number;\n}\n\nfunction flattenFolders(\n folders: MediaFolderNode[],\n depth: number = 0,\n): FlatFolder[] {\n const result: FlatFolder[] = [];\n for (const folder of folders) {\n result.push({ id: folder.id, name: folder.name, depth });\n if (folder.children.length) {\n result.push(...flattenFolders(folder.children, depth + 1));\n }\n }\n return result;\n}\n\nonClickOutside(pickerRef, () => {\n emit(\"close\");\n});\n</script>\n\n<template>\n <div\n ref=\"pickerRef\"\n data-testid=\"media-move-picker\"\n class=\"tpl:absolute tpl:bottom-full tpl:left-0 tpl:z-10 tpl:mb-2 tpl:w-56 tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:shadow-lg\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg-elevated);\n \"\n >\n <div class=\"tpl:max-h-56 tpl:overflow-y-auto tpl:py-1\">\n <button\n v-if=\"currentFolderId !== null\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n style=\"color: var(--tpl-text)\"\n @click=\"emit('select', null)\"\n >\n <File class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.moveToRoot }}\n </button>\n <button\n v-for=\"folder in flattenFolders(folders)\"\n :key=\"folder.id\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:py-1.5 tpl:pr-3 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n :style=\"{\n paddingLeft: `${folder.depth * 16 + 12}px`,\n color:\n folder.id === currentFolderId\n ? 'var(--tpl-text-dim)'\n : 'var(--tpl-text)',\n opacity: folder.id === currentFolderId ? 0.5 : 1,\n }\"\n :disabled=\"folder.id === currentFolderId\"\n @click=\"emit('select', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n <span\n v-if=\"folder.id === currentFolderId\"\n class=\"tpl:shrink-0\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ t.mediaLibrary.currentFolder }}\n </span>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { onClickOutside } from \"@vueuse/core\";\nimport { File, Folder } from \"@lucide/vue\";\nimport { ref } from \"vue\";\n\ndefineProps<{\n folders: MediaFolderNode[];\n currentFolderId: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"select\", folderId: string | null): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst pickerRef = ref<HTMLDivElement | null>(null);\n\ninterface FlatFolder {\n id: string;\n name: string;\n depth: number;\n}\n\nfunction flattenFolders(\n folders: MediaFolderNode[],\n depth: number = 0,\n): FlatFolder[] {\n const result: FlatFolder[] = [];\n for (const folder of folders) {\n result.push({ id: folder.id, name: folder.name, depth });\n if (folder.children.length) {\n result.push(...flattenFolders(folder.children, depth + 1));\n }\n }\n return result;\n}\n\nonClickOutside(pickerRef, () => {\n emit(\"close\");\n});\n</script>\n\n<template>\n <div\n ref=\"pickerRef\"\n data-testid=\"media-move-picker\"\n class=\"tpl:absolute tpl:bottom-full tpl:left-0 tpl:z-10 tpl:mb-2 tpl:w-56 tpl:overflow-hidden tpl:rounded-lg tpl:border tpl:shadow-lg\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg-elevated);\n \"\n >\n <div class=\"tpl:max-h-56 tpl:overflow-y-auto tpl:py-1\">\n <button\n v-if=\"currentFolderId !== null\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:px-3 tpl:py-1.5 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n style=\"color: var(--tpl-text)\"\n @click=\"emit('select', null)\"\n >\n <File class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n {{ t.mediaLibrary.moveToRoot }}\n </button>\n <button\n v-for=\"folder in flattenFolders(folders)\"\n :key=\"folder.id\"\n class=\"tpl:flex tpl:w-full tpl:items-center tpl:gap-1.5 tpl:py-1.5 tpl:pr-3 tpl:text-left tpl:text-xs tpl:transition-colors tpl:duration-100\"\n :style=\"{\n paddingLeft: `${folder.depth * 16 + 12}px`,\n color:\n folder.id === currentFolderId\n ? 'var(--tpl-text-dim)'\n : 'var(--tpl-text)',\n opacity: folder.id === currentFolderId ? 0.5 : 1,\n }\"\n :disabled=\"folder.id === currentFolderId\"\n @click=\"emit('select', folder.id)\"\n >\n <Folder class=\"tpl:shrink-0\" :size=\"14\" :stroke-width=\"1.5\" />\n <span class=\"tpl:truncate\">{{ folder.name }}</span>\n <span\n v-if=\"folder.id === currentFolderId\"\n class=\"tpl:shrink-0\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ t.mediaLibrary.currentFolder }}\n </span>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { Folder } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\n\nconst props = defineProps<{\n item: MediaAsset;\n folders?: MediaFolderNode[];\n}>();\n\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst { isImageMimeType } = useMediaCategories();\n\nfunction buildFolderPath(\n folderList: MediaFolderNode[],\n targetId: string,\n currentPath: string[] = [],\n): string[] | null {\n for (const folder of folderList) {\n const newPath = [...currentPath, folder.name];\n if (folder.id === targetId) {\n return newPath;\n }\n if (folder.children.length) {\n const found = buildFolderPath(folder.children, targetId, newPath);\n if (found) return found;\n }\n }\n return null;\n}\n\nconst folderPath = computed(() => {\n if (!props.item.folderId || !props.folders) {\n return null;\n }\n const path = buildFolderPath(props.folders, props.item.folderId);\n return path ? path.join(\"/\") : null;\n});\n\nconst mimeType = computed(() => props.item.mimeType ?? \"\");\nconst isImage = computed(() => isImageMimeType(mimeType.value));\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nconst filename = computed(() => props.item.filename || props.item.url);\n</script>\n\n<template>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <img\n v-if=\"isImage\"\n :src=\"item.url\"\n :alt=\"item.alt || filename\"\n class=\"tpl:size-10 tpl:shrink-0 tpl:rounded tpl:object-cover\"\n style=\"border: 1px solid var(--tpl-border)\"\n />\n <div\n v-else\n class=\"tpl:flex tpl:size-10 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:overflow-hidden tpl:rounded\"\n style=\"border: 1px solid var(--tpl-border)\"\n >\n <MediaFileIcon :mime-type=\"mimeType\" class=\"tpl-preview-icon\" />\n </div>\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ filename }}\n </p>\n <p\n class=\"tpl:mt-0.5 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <template v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> · </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template v-if=\"isImage && item.width && item.height\">\n · {{ item.width }}×{{ item.height }}px\n </template>\n <template v-if=\"folderPath\">\n ·\n <Folder class=\"tpl:mb-px tpl:inline\" :size=\"9\" :stroke-width=\"2\" />\n {{ folderPath }}\n </template>\n </p>\n <p\n v-if=\"isImage\"\n class=\"tpl:mt-0.5 tpl:truncate tpl:text-[10px] tpl:italic\"\n :class=\"{ 'tpl:invisible': !item.alt }\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ item.alt || \" \" }}\n </p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-preview-icon {\n transform: scale(0.45);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport MediaFileIcon from \"./MediaFileIcon.vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport { formatAbsoluteDate } from \"../../utils/formatAbsoluteDate\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { MediaFolderNode } from \"../../utils/treeFolders\";\nimport { Folder } from \"@lucide/vue\";\nimport { computed, inject } from \"vue\";\n\nconst props = defineProps<{\n item: MediaAsset;\n folders?: MediaFolderNode[];\n}>();\n\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst { isImageMimeType } = useMediaCategories();\n\nfunction buildFolderPath(\n folderList: MediaFolderNode[],\n targetId: string,\n currentPath: string[] = [],\n): string[] | null {\n for (const folder of folderList) {\n const newPath = [...currentPath, folder.name];\n if (folder.id === targetId) {\n return newPath;\n }\n if (folder.children.length) {\n const found = buildFolderPath(folder.children, targetId, newPath);\n if (found) return found;\n }\n }\n return null;\n}\n\nconst folderPath = computed(() => {\n if (!props.item.folderId || !props.folders) {\n return null;\n }\n const path = buildFolderPath(props.folders, props.item.folderId);\n return path ? path.join(\"/\") : null;\n});\n\nconst mimeType = computed(() => props.item.mimeType ?? \"\");\nconst isImage = computed(() => isImageMimeType(mimeType.value));\n\nfunction formatDate(dateStr: string): string {\n return formatAbsoluteDate(dateStr, uiLocale?.value, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nconst filename = computed(() => props.item.filename || props.item.url);\n</script>\n\n<template>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <img\n v-if=\"isImage\"\n :src=\"item.url\"\n :alt=\"item.alt || filename\"\n class=\"tpl:size-10 tpl:shrink-0 tpl:rounded tpl:object-cover\"\n style=\"border: 1px solid var(--tpl-border)\"\n />\n <div\n v-else\n class=\"tpl:flex tpl:size-10 tpl:shrink-0 tpl:items-center tpl:justify-center tpl:overflow-hidden tpl:rounded\"\n style=\"border: 1px solid var(--tpl-border)\"\n >\n <MediaFileIcon :mime-type=\"mimeType\" class=\"tpl-preview-icon\" />\n </div>\n <div class=\"tpl:min-w-0 tpl:flex-1\">\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ filename }}\n </p>\n <p\n class=\"tpl:mt-0.5 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-muted)\"\n >\n <template v-if=\"item.size != null\">{{\n formatFileSize(item.size)\n }}</template>\n <template v-if=\"item.createdAt\">\n <template v-if=\"item.size != null\"> · </template>\n {{ formatDate(item.createdAt) }}\n </template>\n <template v-if=\"isImage && item.width && item.height\">\n · {{ item.width }}×{{ item.height }}px\n </template>\n <template v-if=\"folderPath\">\n ·\n <Folder class=\"tpl:mb-px tpl:inline\" :size=\"9\" :stroke-width=\"2\" />\n {{ folderPath }}\n </template>\n </p>\n <p\n v-if=\"isImage\"\n class=\"tpl:mt-0.5 tpl:truncate tpl:text-[10px] tpl:italic\"\n :class=\"{ 'tpl:invisible': !item.alt }\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ item.alt || \" \" }}\n </p>\n </div>\n </div>\n</template>\n\n<style scoped>\n.tpl-preview-icon {\n transform: scale(0.45);\n transform-origin: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport type { MediaAsset, MediaUsageInfo } from \"@templatical/types\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n item: MediaAsset | null;\n usageInfo: MediaUsageInfo | null;\n isReplacing: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"replace\", file: File): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst fileInputRef = ref<HTMLInputElement | null>(null);\nconst selectedFile = ref<File | null>(null);\n\nconst extension = computed(() => {\n if (!props.item) {\n return \"\";\n }\n\n const parts = (props.item.filename ?? \"\").split(\".\");\n return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : \"\";\n});\n\nconst acceptPattern = computed(() => {\n return extension.value ? `.${extension.value}` : \"*\";\n});\n\nconst hasUsage = computed(() => {\n return (props.usageInfo?.templateCount ?? 0) > 0;\n});\n\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) {\n selectedFile.value = null;\n if (fileInputRef.value) {\n fileInputRef.value.value = \"\";\n }\n }\n },\n);\n\nfunction handleFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files && input.files.length > 0) {\n selectedFile.value = input.files[0];\n }\n}\n\nfunction handleReplace(): void {\n if (selectedFile.value) {\n emit(\"replace\", selectedFile.value);\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible && props.item !== null);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Escape\") {\n emit(\"close\");\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible && item\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"emit('close')\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-replace-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-replace-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.replaceWarningTitle }}\n </h3>\n\n <!-- Warning message -->\n <p class=\"tpl:mb-2 tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n t.mediaLibrary.replaceWarningMessage.replace(\n \"{extension}\",\n `.${extension}`,\n )\n }}\n </p>\n\n <!-- Usage warning -->\n <p\n v-if=\"hasUsage\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{\n t.mediaLibrary.replaceWarningUsageNote.replace(\n \"{count}\",\n usageInfo!.templateCount.toString(),\n )\n }}\n </p>\n\n <!-- Current file info -->\n <div\n class=\"tpl:mb-3 tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename }}\n </p>\n </div>\n\n <!-- File input -->\n <div class=\"tpl:mb-4\">\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.replaceSelectFile }}\n </label>\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :accept=\"acceptPattern\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-replace-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n @change=\"handleFileChange\"\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-replace-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isReplacing\"\n @click=\"emit('close')\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!selectedFile || isReplacing\"\n @click=\"handleReplace\"\n >\n {{\n isReplacing ? t.mediaLibrary.replacing : t.mediaLibrary.replace\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useFocusTrap } from \"../../composables/useFocusTrap\";\nimport { POPOVER_TARGET_KEY, UI_THEME_KEY } from \"../../keys\";\nimport type { MediaAsset, MediaUsageInfo } from \"@templatical/types\";\nimport { computed, inject, ref, watch } from \"vue\";\n\nconst props = defineProps<{\n visible: boolean;\n item: MediaAsset | null;\n usageInfo: MediaUsageInfo | null;\n isReplacing: boolean;\n error: string | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"replace\", file: File): void;\n (e: \"close\"): void;\n}>();\n\nconst { t } = useI18n();\nconst tplUiTheme = inject(UI_THEME_KEY, null);\nconst popoverTarget = inject(POPOVER_TARGET_KEY, ref<HTMLElement | null>(null));\n\nconst fileInputRef = ref<HTMLInputElement | null>(null);\nconst selectedFile = ref<File | null>(null);\n\nconst extension = computed(() => {\n if (!props.item) {\n return \"\";\n }\n\n const parts = (props.item.filename ?? \"\").split(\".\");\n return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : \"\";\n});\n\nconst acceptPattern = computed(() => {\n return extension.value ? `.${extension.value}` : \"*\";\n});\n\nconst hasUsage = computed(() => {\n return (props.usageInfo?.templateCount ?? 0) > 0;\n});\n\nwatch(\n () => props.visible,\n (visible) => {\n if (!visible) {\n selectedFile.value = null;\n if (fileInputRef.value) {\n fileInputRef.value.value = \"\";\n }\n }\n },\n);\n\nfunction handleFileChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n if (input.files && input.files.length > 0) {\n selectedFile.value = input.files[0];\n }\n}\n\nfunction handleReplace(): void {\n if (selectedFile.value) {\n emit(\"replace\", selectedFile.value);\n }\n}\n\nconst dialogRef = ref<HTMLElement | null>(null);\nconst trapActive = computed(() => props.visible && props.item !== null);\nuseFocusTrap(dialogRef, trapActive);\n\nfunction handleKeydown(event: KeyboardEvent): void {\n event.stopPropagation();\n if (event.key === \"Escape\") {\n emit(\"close\");\n }\n}\n</script>\n\n<template>\n <Teleport :to=\"popoverTarget || 'body'\">\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"visible && item\"\n :data-tpl-theme=\"tplUiTheme\"\n :class=\"[\n popoverTarget ? undefined : 'tpl',\n 'tpl:fixed tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:p-4',\n ]\"\n style=\"background-color: var(--tpl-overlay)\"\n @click.self=\"emit('close')\"\n @keydown=\"handleKeydown\"\n >\n <div\n ref=\"dialogRef\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-replace-title\"\n class=\"tpl:max-h-[90%] tpl:w-full tpl:max-w-sm tpl:overflow-y-auto tpl:rounded-lg tpl:p-5 tpl:shadow-xl\"\n style=\"background-color: var(--tpl-bg-elevated)\"\n >\n <h3\n id=\"tpl-media-replace-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.replaceWarningTitle }}\n </h3>\n\n <!-- Warning message -->\n <p class=\"tpl:mb-2 tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{\n t.mediaLibrary.replaceWarningMessage.replace(\n \"{extension}\",\n `.${extension}`,\n )\n }}\n </p>\n\n <!-- Usage warning -->\n <p\n v-if=\"hasUsage\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{\n t.mediaLibrary.replaceWarningUsageNote.replace(\n \"{count}\",\n usageInfo!.templateCount.toString(),\n )\n }}\n </p>\n\n <!-- Current file info -->\n <div\n class=\"tpl:mb-3 tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <p\n class=\"tpl:truncate tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ item.filename }}\n </p>\n </div>\n\n <!-- File input -->\n <div class=\"tpl:mb-4\">\n <label\n class=\"tpl:mb-1 tpl:block tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.replaceSelectFile }}\n </label>\n <input\n ref=\"fileInputRef\"\n type=\"file\"\n :accept=\"acceptPattern\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? 'tpl-media-replace-error' : undefined\"\n class=\"tpl:w-full tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n @change=\"handleFileChange\"\n />\n </div>\n\n <p\n v-if=\"error\"\n id=\"tpl-media-replace-error\"\n role=\"alert\"\n class=\"tpl:mb-3 tpl:text-xs\"\n style=\"color: var(--tpl-danger)\"\n >\n {{ error }}\n </p>\n\n <!-- Actions -->\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n :disabled=\"isReplacing\"\n @click=\"emit('close')\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!selectedFile || isReplacing\"\n @click=\"handleReplace\"\n >\n {{\n isReplacing ? t.mediaLibrary.replacing : t.mediaLibrary.replace\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n","/**\n * Joins labels the way the UI locale writes a list (`、` in Japanese, `, `\n * elsewhere). `locale` is free-text consumer config, so a malformed tag falls\n * back to the runtime locale instead of throwing mid-render.\n */\nexport function formatList(items: string[], locale?: string): string {\n // `unit` would be the natural type, but its Japanese form separates with\n // spaces; narrow `conjunction` gives `、` there and `A, B, C` in English.\n const options: Intl.ListFormatOptions = {\n style: \"narrow\",\n type: \"conjunction\",\n };\n try {\n return new Intl.ListFormat(locale, options).format(items);\n } catch (error) {\n if (!(error instanceof RangeError)) {\n throw error;\n }\n return new Intl.ListFormat(undefined, options).format(items);\n }\n}\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport { useDropZone, useFileDialog } from \"@vueuse/core\";\nimport { LoaderCircle, Upload } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport type { MediaCategory } from \"../../types\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { formatList } from \"../../utils/formatList\";\n\ndefineProps<{\n isUploading: boolean;\n uploadProgress: { current: number; total: number } | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"upload\", files: File[]): void;\n}>();\n\nconst { t, format } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst {\n allAcceptedInputString,\n restrictsMimeTypes,\n availableCategories,\n maxFileSize,\n isAcceptedFile,\n} = useMediaCategories();\n\nconst CATEGORY_LABEL_KEYS: Record<MediaCategory, string> = {\n images: \"filterImages\",\n documents: \"filterDocuments\",\n videos: \"filterVideos\",\n audio: \"filterAudio\",\n};\n\n// Read from the provider's limits, never a fixed string: the hint is the only\n// place an author learns what the library will refuse.\nconst acceptedHint = computed(() => {\n const labels = t.mediaLibrary as Record<string, string>;\n const formats = restrictsMimeTypes.value\n ? formatList(\n availableCategories.value.map(\n (category) => labels[CATEGORY_LABEL_KEYS[category]],\n ),\n uiLocale?.value,\n )\n : t.mediaLibrary.acceptedAnyType;\n if (!Number.isFinite(maxFileSize.value)) {\n return formats;\n }\n return format(t.mediaLibrary.acceptedFormatsMaxSize, {\n formats,\n size: formatFileSize(maxFileSize.value),\n });\n});\n\nconst rejectedNames = ref<string[]>([]);\n\nconst dropZoneRef = ref<HTMLElement>();\n\nfunction emitValidFiles(fileList: File[] | FileList): void {\n const valid: File[] = [];\n const rejected: string[] = [];\n for (const file of Array.from(fileList)) {\n if (isAcceptedFile(file)) {\n valid.push(file);\n } else {\n rejected.push(file.name);\n }\n }\n rejectedNames.value = rejected;\n if (valid.length) {\n emit(\"upload\", valid);\n }\n}\n\nconst { isOverDropZone } = useDropZone(dropZoneRef, {\n onDrop: (files) => {\n if (files?.length) {\n emitValidFiles(files);\n }\n },\n});\n\nconst { open: openFilePicker, onChange } = useFileDialog({\n accept: allAcceptedInputString.value,\n multiple: true,\n});\n\nonChange((fileList) => {\n if (fileList?.length) {\n emitValidFiles(fileList);\n }\n});\n</script>\n\n<template>\n <button\n ref=\"dropZoneRef\"\n type=\"button\"\n data-testid=\"media-upload-zone\"\n class=\"tpl-upload-zone tpl:flex tpl:w-full tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:justify-center tpl:rounded-lg tpl:border-2 tpl:border-dashed tpl:p-5 tpl:text-center tpl:transition-all tpl:duration-150\"\n :class=\"isOverDropZone ? 'tpl-upload-zone-active' : ''\"\n :aria-label=\"t.mediaLibrary.dropOrClick\"\n :aria-describedby=\"isUploading ? undefined : 'tpl-media-upload-hint'\"\n :aria-busy=\"isUploading\"\n style=\"\n border-color: var(--tpl-border-light);\n background-color: var(--tpl-bg);\n \"\n @click=\"openFilePicker()\"\n >\n <div v-if=\"isUploading\" class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n <span class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">{{\n uploadProgress && uploadProgress.total > 1\n ? format(t.mediaLibrary.uploadingProgress, {\n current: uploadProgress.current,\n total: uploadProgress.total,\n })\n : t.mediaLibrary.uploading\n }}</span>\n </div>\n <template v-else>\n <Upload\n class=\"tpl:mb-2\"\n :size=\"24\"\n :stroke-width=\"1.5\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{ t.mediaLibrary.dropOrClick }}\n </p>\n <p\n id=\"tpl-media-upload-hint\"\n data-testid=\"media-upload-hint\"\n class=\"tpl:mt-1 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ acceptedHint }}\n </p>\n </template>\n </button>\n <p\n v-if=\"rejectedNames.length\"\n data-testid=\"media-upload-rejected\"\n role=\"alert\"\n class=\"tpl:mt-1.5 tpl:text-xs tpl:break-words\"\n style=\"color: var(--tpl-danger)\"\n >\n {{\n format(t.mediaLibrary.uploadRejected, {\n files: rejectedNames.join(\", \"),\n })\n }}\n </p>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { useMediaCategories } from \"../../composables/useMediaCategories\";\nimport { useDropZone, useFileDialog } from \"@vueuse/core\";\nimport { LoaderCircle, Upload } from \"@lucide/vue\";\nimport { computed, inject, ref } from \"vue\";\nimport { UI_LOCALE_KEY } from \"../../keys\";\nimport type { MediaCategory } from \"../../types\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { formatList } from \"../../utils/formatList\";\n\ndefineProps<{\n isUploading: boolean;\n uploadProgress: { current: number; total: number } | null;\n}>();\n\nconst emit = defineEmits<{\n (e: \"upload\", files: File[]): void;\n}>();\n\nconst { t, format } = useI18n();\nconst uiLocale = inject(UI_LOCALE_KEY, null);\n\nconst {\n allAcceptedInputString,\n restrictsMimeTypes,\n availableCategories,\n maxFileSize,\n isAcceptedFile,\n} = useMediaCategories();\n\nconst CATEGORY_LABEL_KEYS: Record<MediaCategory, string> = {\n images: \"filterImages\",\n documents: \"filterDocuments\",\n videos: \"filterVideos\",\n audio: \"filterAudio\",\n};\n\n// Read from the provider's limits, never a fixed string: the hint is the only\n// place an author learns what the library will refuse.\nconst acceptedHint = computed(() => {\n const labels = t.mediaLibrary as Record<string, string>;\n const formats = restrictsMimeTypes.value\n ? formatList(\n availableCategories.value.map(\n (category) => labels[CATEGORY_LABEL_KEYS[category]],\n ),\n uiLocale?.value,\n )\n : t.mediaLibrary.acceptedAnyType;\n if (!Number.isFinite(maxFileSize.value)) {\n return formats;\n }\n return format(t.mediaLibrary.acceptedFormatsMaxSize, {\n formats,\n size: formatFileSize(maxFileSize.value),\n });\n});\n\nconst rejectedNames = ref<string[]>([]);\n\nconst dropZoneRef = ref<HTMLElement>();\n\nfunction emitValidFiles(fileList: File[] | FileList): void {\n const valid: File[] = [];\n const rejected: string[] = [];\n for (const file of Array.from(fileList)) {\n if (isAcceptedFile(file)) {\n valid.push(file);\n } else {\n rejected.push(file.name);\n }\n }\n rejectedNames.value = rejected;\n if (valid.length) {\n emit(\"upload\", valid);\n }\n}\n\nconst { isOverDropZone } = useDropZone(dropZoneRef, {\n onDrop: (files) => {\n if (files?.length) {\n emitValidFiles(files);\n }\n },\n});\n\nconst { open: openFilePicker, onChange } = useFileDialog({\n accept: allAcceptedInputString.value,\n multiple: true,\n});\n\nonChange((fileList) => {\n if (fileList?.length) {\n emitValidFiles(fileList);\n }\n});\n</script>\n\n<template>\n <button\n ref=\"dropZoneRef\"\n type=\"button\"\n data-testid=\"media-upload-zone\"\n class=\"tpl-upload-zone tpl:flex tpl:w-full tpl:cursor-pointer tpl:flex-col tpl:items-center tpl:justify-center tpl:rounded-lg tpl:border-2 tpl:border-dashed tpl:p-5 tpl:text-center tpl:transition-all tpl:duration-150\"\n :class=\"isOverDropZone ? 'tpl-upload-zone-active' : ''\"\n :aria-label=\"t.mediaLibrary.dropOrClick\"\n :aria-describedby=\"isUploading ? undefined : 'tpl-media-upload-hint'\"\n :aria-busy=\"isUploading\"\n style=\"\n border-color: var(--tpl-border-light);\n background-color: var(--tpl-bg);\n \"\n @click=\"openFilePicker()\"\n >\n <div v-if=\"isUploading\" class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <LoaderCircle\n class=\"tpl-spinner\"\n :size=\"20\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-primary)\"\n />\n <span class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">{{\n uploadProgress && uploadProgress.total > 1\n ? format(t.mediaLibrary.uploadingProgress, {\n current: uploadProgress.current,\n total: uploadProgress.total,\n })\n : t.mediaLibrary.uploading\n }}</span>\n </div>\n <template v-else>\n <Upload\n class=\"tpl:mb-2\"\n :size=\"24\"\n :stroke-width=\"1.5\"\n style=\"color: var(--tpl-text-dim)\"\n />\n <p class=\"tpl:text-xs\" style=\"color: var(--tpl-text-muted)\">\n {{ t.mediaLibrary.dropOrClick }}\n </p>\n <p\n id=\"tpl-media-upload-hint\"\n data-testid=\"media-upload-hint\"\n class=\"tpl:mt-1 tpl:text-[10px]\"\n style=\"color: var(--tpl-text-dim)\"\n >\n {{ acceptedHint }}\n </p>\n </template>\n </button>\n <p\n v-if=\"rejectedNames.length\"\n data-testid=\"media-upload-rejected\"\n role=\"alert\"\n class=\"tpl:mt-1.5 tpl:text-xs tpl:break-words\"\n style=\"color: var(--tpl-danger)\"\n >\n {{\n format(t.mediaLibrary.uploadRejected, {\n files: rejectedNames.join(\", \"),\n })\n }}\n </p>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n usedBytes: number;\n limitBytes: number;\n size?: number;\n}>();\n\nconst { t, format } = useI18n();\nconst showTooltip = ref(false);\n\nconst size = computed(() => props.size ?? 24);\nconst strokeWidth = computed(() => Math.max(2, size.value / 8));\nconst radius = computed(() => (size.value - strokeWidth.value) / 2);\nconst circumference = computed(() => 2 * Math.PI * radius.value);\n\nconst percentage = computed(() => {\n if (props.limitBytes <= 0) return 0;\n return Math.min(100, (props.usedBytes / props.limitBytes) * 100);\n});\n\nconst strokeDashoffset = computed(() => {\n return circumference.value - (percentage.value / 100) * circumference.value;\n});\n\nconst progressColor = computed(() => {\n if (percentage.value >= 95) {\n return \"var(--tpl-danger)\";\n }\n if (percentage.value >= 75) {\n return \"var(--tpl-warning)\";\n }\n return \"var(--tpl-primary)\";\n});\n\nconst usedFormatted = computed(() => formatFileSize(props.usedBytes));\nconst limitFormatted = computed(() => formatFileSize(props.limitBytes));\nconst remainingBytes = computed(() =>\n Math.max(0, props.limitBytes - props.usedBytes),\n);\nconst remainingFormatted = computed(() => formatFileSize(remainingBytes.value));\n\nconst tooltipText = computed(() =>\n format(t.mediaLibrary.storageTooltip, {\n used: usedFormatted.value,\n total: limitFormatted.value,\n remaining: remainingFormatted.value,\n }),\n);\n</script>\n\n<template>\n <div\n data-testid=\"media-storage-ring\"\n class=\"tpl:relative tpl:inline-flex tpl:cursor-help tpl:items-center tpl:justify-center\"\n tabindex=\"0\"\n :aria-label=\"tooltipText\"\n @mouseenter=\"showTooltip = true\"\n @mouseleave=\"showTooltip = false\"\n @focus=\"showTooltip = true\"\n @blur=\"showTooltip = false\"\n >\n <svg\n :width=\"size\"\n :height=\"size\"\n class=\"tpl:-rotate-90\"\n :viewBox=\"`0 0 ${size} ${size}`\"\n >\n <!-- Background circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n style=\"stroke: var(--tpl-border)\"\n />\n <!-- Progress circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n :stroke=\"progressColor\"\n stroke-linecap=\"round\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"strokeDashoffset\"\n class=\"tpl:transition-all tpl:duration-300 tpl:ease-out\"\n />\n </svg>\n\n <!-- Tooltip -->\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0 tpl:translate-y-1\"\n enter-to-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-to-class=\"tpl:opacity-0 tpl:translate-y-1\"\n >\n <div\n v-if=\"showTooltip\"\n class=\"tpl:absolute tpl:top-full tpl:left-1/2 tpl:z-50 tpl:mt-2 tpl:-translate-x-1/2 tpl:rounded-md tpl:px-2.5 tpl:py-1.5 tpl:text-xs tpl:whitespace-nowrap tpl:shadow-lg\"\n style=\"background-color: var(--tpl-text); color: var(--tpl-bg-elevated)\"\n >\n {{ tooltipText }}\n <div\n class=\"tpl:absolute tpl:-top-1 tpl:left-1/2 tpl:size-2 tpl:-translate-x-1/2 tpl:rotate-45\"\n style=\"background-color: var(--tpl-text)\"\n />\n </div>\n </Transition>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useI18n } from \"../../composables/useI18n\";\nimport { formatFileSize } from \"../../utils/formatFileSize\";\nimport { computed, ref } from \"vue\";\n\nconst props = defineProps<{\n usedBytes: number;\n limitBytes: number;\n size?: number;\n}>();\n\nconst { t, format } = useI18n();\nconst showTooltip = ref(false);\n\nconst size = computed(() => props.size ?? 24);\nconst strokeWidth = computed(() => Math.max(2, size.value / 8));\nconst radius = computed(() => (size.value - strokeWidth.value) / 2);\nconst circumference = computed(() => 2 * Math.PI * radius.value);\n\nconst percentage = computed(() => {\n if (props.limitBytes <= 0) return 0;\n return Math.min(100, (props.usedBytes / props.limitBytes) * 100);\n});\n\nconst strokeDashoffset = computed(() => {\n return circumference.value - (percentage.value / 100) * circumference.value;\n});\n\nconst progressColor = computed(() => {\n if (percentage.value >= 95) {\n return \"var(--tpl-danger)\";\n }\n if (percentage.value >= 75) {\n return \"var(--tpl-warning)\";\n }\n return \"var(--tpl-primary)\";\n});\n\nconst usedFormatted = computed(() => formatFileSize(props.usedBytes));\nconst limitFormatted = computed(() => formatFileSize(props.limitBytes));\nconst remainingBytes = computed(() =>\n Math.max(0, props.limitBytes - props.usedBytes),\n);\nconst remainingFormatted = computed(() => formatFileSize(remainingBytes.value));\n\nconst tooltipText = computed(() =>\n format(t.mediaLibrary.storageTooltip, {\n used: usedFormatted.value,\n total: limitFormatted.value,\n remaining: remainingFormatted.value,\n }),\n);\n</script>\n\n<template>\n <div\n data-testid=\"media-storage-ring\"\n class=\"tpl:relative tpl:inline-flex tpl:cursor-help tpl:items-center tpl:justify-center\"\n tabindex=\"0\"\n :aria-label=\"tooltipText\"\n @mouseenter=\"showTooltip = true\"\n @mouseleave=\"showTooltip = false\"\n @focus=\"showTooltip = true\"\n @blur=\"showTooltip = false\"\n >\n <svg\n :width=\"size\"\n :height=\"size\"\n class=\"tpl:-rotate-90\"\n :viewBox=\"`0 0 ${size} ${size}`\"\n >\n <!-- Background circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n style=\"stroke: var(--tpl-border)\"\n />\n <!-- Progress circle -->\n <circle\n :cx=\"size / 2\"\n :cy=\"size / 2\"\n :r=\"radius\"\n fill=\"none\"\n :stroke-width=\"strokeWidth\"\n :stroke=\"progressColor\"\n stroke-linecap=\"round\"\n :stroke-dasharray=\"circumference\"\n :stroke-dashoffset=\"strokeDashoffset\"\n class=\"tpl:transition-all tpl:duration-300 tpl:ease-out\"\n />\n </svg>\n\n <!-- Tooltip -->\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0 tpl:translate-y-1\"\n enter-to-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100 tpl:translate-y-0\"\n leave-to-class=\"tpl:opacity-0 tpl:translate-y-1\"\n >\n <div\n v-if=\"showTooltip\"\n class=\"tpl:absolute tpl:top-full tpl:left-1/2 tpl:z-50 tpl:mt-2 tpl:-translate-x-1/2 tpl:rounded-md tpl:px-2.5 tpl:py-1.5 tpl:text-xs tpl:whitespace-nowrap tpl:shadow-lg\"\n style=\"background-color: var(--tpl-text); color: var(--tpl-bg-elevated)\"\n >\n {{ tooltipText }}\n <div\n class=\"tpl:absolute tpl:-top-1 tpl:left-1/2 tpl:size-2 tpl:-translate-x-1/2 tpl:rotate-45\"\n style=\"background-color: var(--tpl-text)\"\n />\n </div>\n </Transition>\n </div>\n</template>\n","import type { MediaFolder } from \"@templatical/types\";\n\n/**\n * A folder plus the children derived from {@link MediaFolder.parentId}.\n *\n * {@link MediaFoldersProvider.list} is flat; the tree is a UI concern so a\n * store never has to return nested nodes.\n */\nexport interface MediaFolderNode extends MediaFolder {\n children: MediaFolderNode[];\n}\n\n/**\n * Group a flat folder list into a tree via `parentId`.\n *\n * A `parentId` that is null, omitted, or not in the list is a root — a\n * dangling reference must still render rather than vanish.\n */\nexport function treeFolders(folders: MediaFolder[]): MediaFolderNode[] {\n const nodes = new Map<string, MediaFolderNode>();\n for (const folder of folders) {\n nodes.set(folder.id, { ...folder, children: [] });\n }\n\n const roots: MediaFolderNode[] = [];\n for (const node of nodes.values()) {\n const parentId = node.parentId ?? null;\n const parent = parentId ? nodes.get(parentId) : undefined;\n if (parent) {\n parent.children.push(node);\n } else {\n roots.push(node);\n }\n }\n return roots;\n}\n","import type {\n CropData,\n MediaEditSaveResult,\n} from \"../components/media/MediaEditModal.vue\";\nimport type { MediaAsset } from \"@templatical/types\";\nimport type { useMediaLibrary } from \"../composable\";\nimport { treeFolders, type MediaFolderNode } from \"../utils/treeFolders\";\n\ntype UseMediaLibraryReturn = ReturnType<typeof useMediaLibrary>;\nimport { useClipboard, useDebounceFn } from \"@vueuse/core\";\nimport { computed, ref, watch, type ComputedRef, type Ref } from \"vue\";\n\nexport interface UseMediaLibraryUIOptions {\n library: UseMediaLibraryReturn;\n translations:\n | { mediaLibrary: Record<string, string> }\n | Ref<{ mediaLibrary: Record<string, string> }>;\n}\n\nexport interface UseMediaLibraryUIReturn {\n layoutMode: Ref<\"grid\" | \"list\">;\n showSidebar: Ref<boolean>;\n searchInput: Ref<string>;\n editingItem: Ref<MediaAsset | null>;\n showImportUrlModal: Ref<boolean>;\n showMovePicker: Ref<boolean>;\n\n selectedUrl: ComputedRef<string | null>;\n hasFrequentlyUsed: ComputedRef<boolean>;\n displayItems: ComputedRef<MediaAsset[]>;\n hasUsedFiles: ComputedRef<boolean>;\n folderTree: ComputedRef<MediaFolderNode[]>;\n\n copy: (text: string) => Promise<void>;\n copied: Ref<boolean>;\n\n getCategoryLabel: (category: string) => string;\n\n handleSearchInput: (value: string) => void;\n handleUpload: (files: File[]) => Promise<void>;\n handleSelect: (item: MediaAsset) => void;\n handleCreateFolder: (name: string, parentId?: string | null) => Promise<void>;\n handleRenameFolder: (folderId: string, name: string) => Promise<void>;\n handleDeleteFolder: (folderId: string) => Promise<void>;\n handleEditItem: (item: MediaAsset) => void;\n handleEditSave: (\n mediaId: string,\n filename: string,\n altText?: string,\n cropData?: CropData,\n ) => Promise<MediaEditSaveResult>;\n handleImportFromUrl: (url: string) => Promise<void>;\n handleMoveToFolder: (folderId: string | null) => Promise<void>;\n handleDeleteClick: () => Promise<void>;\n handleReplaceItem: (item: MediaAsset) => void;\n handleReplaceFile: (file: File) => Promise<void>;\n\n resetUI: () => void;\n}\n\nexport function useMediaLibraryUI(\n options: UseMediaLibraryUIOptions,\n): UseMediaLibraryUIReturn {\n const { library, translations } = options;\n\n function getTranslations() {\n if (\"value\" in translations && typeof translations.value === \"object\") {\n return (translations as Ref<{ mediaLibrary: Record<string, string> }>)\n .value;\n }\n return translations as { mediaLibrary: Record<string, string> };\n }\n\n const layoutMode = ref<\"grid\" | \"list\">(\"grid\");\n const showSidebar = ref(false);\n const searchInput = ref(\"\");\n const editingItem = ref<MediaAsset | null>(null);\n const showImportUrlModal = ref(false);\n const showMovePicker = ref(false);\n\n const selectedUrl = computed(() => library.previewItem.value?.url ?? null);\n\n const hasFrequentlyUsed = computed(() => {\n return library.frequentlyUsedItems.value.length > 0;\n });\n\n const displayItems = computed(() => {\n if (library.viewMode.value === \"frequently-used\") {\n return library.frequentlyUsedItems.value;\n }\n return library.items.value;\n });\n\n const hasUsedFiles = computed(() => {\n return Object.values(library.deleteUsageInfo.value).some(\n (info) => info.templateCount > 0,\n );\n });\n\n const folderTree = computed(() => treeFolders(library.folders.value));\n\n const categoryLabels: Record<string, () => string> = {\n images: () => getTranslations().mediaLibrary.filterImages,\n documents: () => getTranslations().mediaLibrary.filterDocuments,\n videos: () => getTranslations().mediaLibrary.filterVideos,\n audio: () => getTranslations().mediaLibrary.filterAudio,\n };\n\n function getCategoryLabel(category: string): string {\n return categoryLabels[category]?.() ?? category;\n }\n\n watch(showSidebar, (show) => {\n if (show) {\n library.loadFolders();\n }\n });\n\n const debouncedSearch = useDebounceFn((value: string) => {\n library.search(value);\n }, 300);\n\n function handleSearchInput(value: string): void {\n searchInput.value = value;\n debouncedSearch(value);\n }\n\n const { copy, copied } = useClipboard({ copiedDuring: 2000, legacy: true });\n\n async function handleUpload(files: File[]): Promise<void> {\n await library.uploadFiles(files);\n }\n\n function handleSelect(item: MediaAsset): void {\n library.selectItem(item);\n }\n\n async function handleCreateFolder(\n name: string,\n parentId?: string | null,\n ): Promise<void> {\n await library.createFolder(name, parentId);\n }\n\n async function handleRenameFolder(\n folderId: string,\n name: string,\n ): Promise<void> {\n await library.renameFolder(folderId, name);\n }\n\n async function handleDeleteFolder(folderId: string): Promise<void> {\n await library.deleteFolder(folderId);\n }\n\n function handleEditItem(item: MediaAsset): void {\n editingItem.value = item;\n }\n\n async function handleEditSave(\n mediaId: string,\n filename: string,\n altText?: string,\n cropData?: CropData,\n ): Promise<MediaEditSaveResult> {\n // A failed replace stops here: saving the metadata anyway would close the\n // dialog over an image that never changed.\n if (cropData) {\n const replaced = await library.replaceMediaDirectly(\n mediaId,\n cropData.file,\n );\n if (!replaced) {\n return \"failed\";\n }\n }\n const updated = await library.updateFile(mediaId, filename, altText);\n if (!updated) {\n return cropData ? \"replaced\" : \"failed\";\n }\n editingItem.value = null;\n return \"saved\";\n }\n\n async function handleImportFromUrl(url: string): Promise<void> {\n const result = await library.importFromUrl(url);\n if (result) {\n showImportUrlModal.value = false;\n }\n }\n\n async function handleMoveToFolder(folderId: string | null): Promise<void> {\n showMovePicker.value = false;\n await library.moveSelected(folderId);\n }\n\n async function handleDeleteClick(): Promise<void> {\n await library.checkUsageBeforeDelete();\n }\n\n function handleReplaceItem(item: MediaAsset): void {\n library.checkUsageBeforeReplace(item);\n }\n\n async function handleReplaceFile(file: File): Promise<void> {\n await library.replaceFile(file);\n }\n\n function resetUI(): void {\n library.clearSelection();\n library.cancelDelete();\n library.cancelReplace();\n searchInput.value = \"\";\n // Assign, don't `search(\"\")`: that would list on close. The next open\n // already calls `loadItems()` with whatever `searchQuery` holds.\n library.searchQuery.value = \"\";\n library.categoryFilter.value = null;\n library.sortOption.value = \"newest\";\n library.viewMode.value = \"files\";\n editingItem.value = null;\n showImportUrlModal.value = false;\n }\n\n return {\n layoutMode,\n showSidebar,\n searchInput,\n editingItem,\n showImportUrlModal,\n showMovePicker,\n selectedUrl,\n hasFrequentlyUsed,\n displayItems,\n hasUsedFiles,\n folderTree,\n copy,\n copied,\n getCategoryLabel,\n handleSearchInput,\n handleUpload,\n handleSelect,\n handleCreateFolder,\n handleRenameFolder,\n handleDeleteFolder,\n handleEditItem,\n handleEditSave,\n handleImportFromUrl,\n handleMoveToFolder,\n handleDeleteClick,\n handleReplaceItem,\n handleReplaceFile,\n resetUI,\n };\n}\n","import type {\n MediaAsset,\n MediaAssetPatch,\n MediaCategory,\n MediaCreateInput,\n MediaFolder,\n MediaFoldersProvider,\n MediaFolderInput,\n MediaListParams,\n MediaProvider,\n MediaStorageInfo,\n MediaUsageInfo,\n} from \"@templatical/types\";\nimport { ref, toValue, type MaybeRefOrGetter, type Ref } from \"vue\";\n\nexport type MediaViewMode = \"files\" | \"frequently-used\";\n\nexport interface UseMediaLibraryOptions {\n /**\n * Storage backend. The composable is transport-agnostic and never talks\n * to a network itself.\n */\n provider: MediaProvider;\n onError?: (error: Error) => void;\n /**\n * Forwarded on `list` / `create` / `importFromUrl` when a template is\n * loaded. Read at call time — a ref that fills after setup must still\n * reach the store.\n */\n templateId?: MaybeRefOrGetter<string | undefined>;\n}\n\n/**\n * Reactive state over a {@link MediaProvider}.\n *\n * Owns the listing, selection, preview, sequential upload loop and the\n * delete-usage flow. Chrome that should hide for a `false` method is the\n * modal's job; this composable simply does not call a disabled method.\n */\nexport function useMediaLibrary(options: UseMediaLibraryOptions) {\n const { provider } = options;\n\n const items: Ref<MediaAsset[]> = ref([]);\n const folders: Ref<MediaFolder[]> = ref([]);\n const currentFolderId: Ref<string | null> = ref(null);\n const viewMode: Ref<MediaViewMode> = ref(\"files\");\n const searchQuery: Ref<string> = ref(\"\");\n const categoryFilter: Ref<MediaCategory | null> = ref(null);\n const sortOption: Ref<string> = ref(\"newest\");\n const isLoading = ref(false);\n const isUploading = ref(false);\n const hasMore = ref(false);\n const nextCursor: Ref<string | null> = ref(null);\n const uploadProgress: Ref<{ current: number; total: number } | null> =\n ref(null);\n const selectedItems: Ref<Set<string>> = ref(new Set());\n const previewItem: Ref<MediaAsset | null> = ref(null);\n const frequentlyUsedItems: Ref<MediaAsset[]> = ref([]);\n const deleteUsageInfo: Ref<Record<string, MediaUsageInfo>> = ref({});\n const showDeleteWarning = ref(false);\n const pendingDeleteIds: Ref<string[]> = ref([]);\n const isImportingFromUrl = ref(false);\n const importFromUrlError: Ref<string | null> = ref(null);\n const isReplacing = ref(false);\n const replaceError: Ref<string | null> = ref(null);\n const showReplaceWarning = ref(false);\n const pendingReplaceItem: Ref<MediaAsset | null> = ref(null);\n const replaceUsageInfo: Ref<MediaUsageInfo | null> = ref(null);\n const storageInfo: Ref<MediaStorageInfo | null> = ref(null);\n\n // Monotonic token so an out-of-order list/loadMore response (folder\n // switch / search change before the previous request settled) cannot\n // overwrite the current view with stale data.\n let browseRequestId = 0;\n\n /**\n * A handler that throws must not turn a completed write into a rejected\n * one — the UI would report a failure for an asset that was stored.\n */\n function notify(run: () => void): void {\n try {\n run();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function foldersProvider(): MediaFoldersProvider | null {\n return provider.folders === false ? null : provider.folders;\n }\n\n /**\n * `sort` is absent: {@link MediaListParams} has no sort field.\n * `sortOption` stays as local UI state; changing it reloads the current\n * filters without inventing an order the store did not advertise.\n */\n function resolvedTemplateId(): string | undefined {\n return toValue(options.templateId);\n }\n\n function currentListParams(cursor?: string): MediaListParams {\n const params: MediaListParams = {};\n if (searchQuery.value) {\n params.search = searchQuery.value;\n } else if (currentFolderId.value != null) {\n params.folderId = currentFolderId.value;\n }\n if (categoryFilter.value) {\n params.category = categoryFilter.value;\n }\n if (cursor) {\n params.cursor = cursor;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n params.templateId = templateId;\n }\n return params;\n }\n\n async function loadItems(): Promise<void> {\n const requestId = ++browseRequestId;\n isLoading.value = true;\n const params = currentListParams();\n try {\n const page = await provider.list(params);\n if (requestId !== browseRequestId) return;\n items.value = page.items;\n nextCursor.value = page.nextCursor ?? null;\n hasMore.value = !!page.nextCursor;\n } catch (error) {\n if (requestId !== browseRequestId) return;\n options.onError?.(error as Error);\n } finally {\n if (requestId === browseRequestId) {\n isLoading.value = false;\n }\n }\n }\n\n async function loadMore(): Promise<void> {\n if (!hasMore.value || !nextCursor.value || isLoading.value) return;\n\n const requestId = ++browseRequestId;\n isLoading.value = true;\n const params = currentListParams(nextCursor.value);\n try {\n const page = await provider.list(params);\n if (requestId !== browseRequestId) return;\n items.value = [...items.value, ...page.items];\n nextCursor.value = page.nextCursor ?? null;\n hasMore.value = !!page.nextCursor;\n } catch (error) {\n if (requestId !== browseRequestId) return;\n options.onError?.(error as Error);\n } finally {\n if (requestId === browseRequestId) {\n isLoading.value = false;\n }\n }\n }\n\n async function search(query: string): Promise<void> {\n searchQuery.value = query;\n await loadItems();\n }\n\n async function filterByCategory(\n category: MediaCategory | null,\n ): Promise<void> {\n categoryFilter.value = category;\n await loadItems();\n }\n\n async function sortBy(option: string): Promise<void> {\n sortOption.value = option;\n await loadItems();\n }\n\n async function navigateToFolder(folderId: string | null): Promise<void> {\n viewMode.value = \"files\";\n currentFolderId.value = folderId;\n searchQuery.value = \"\";\n selectedItems.value = new Set();\n previewItem.value = null;\n await loadItems();\n }\n\n async function showFrequentlyUsed(): Promise<void> {\n viewMode.value = \"frequently-used\";\n currentFolderId.value = null;\n searchQuery.value = \"\";\n selectedItems.value = new Set();\n previewItem.value = null;\n await loadFrequentlyUsed();\n }\n\n async function uploadFile(file: File): Promise<MediaAsset | null> {\n const { create } = provider;\n if (typeof create !== \"function\") {\n return null;\n }\n\n isUploading.value = true;\n try {\n const input: MediaCreateInput = { file };\n if (currentFolderId.value != null) {\n input.folderId = currentFolderId.value;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n input.templateId = templateId;\n }\n const media = await create(input);\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n return media;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n } finally {\n isUploading.value = false;\n }\n }\n\n async function uploadFiles(files: File[]): Promise<void> {\n const { create } = provider;\n if (typeof create !== \"function\") {\n return;\n }\n\n isUploading.value = true;\n uploadProgress.value = { current: 0, total: files.length };\n try {\n for (let i = 0; i < files.length; i++) {\n uploadProgress.value = { current: i + 1, total: files.length };\n try {\n const input: MediaCreateInput = { file: files[i] };\n if (currentFolderId.value != null) {\n input.folderId = currentFolderId.value;\n }\n const templateId = resolvedTemplateId();\n if (templateId) {\n input.templateId = templateId;\n }\n const media = await create(input);\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n } finally {\n isUploading.value = false;\n uploadProgress.value = null;\n }\n }\n\n async function moveSelected(targetFolderId: string | null): Promise<void> {\n if (selectedItems.value.size === 0) {\n return;\n }\n\n const foldersApi = foldersProvider();\n const { move } = foldersApi ?? { move: false };\n if (typeof move !== \"function\") {\n return;\n }\n\n try {\n const movedItems = await move([...selectedItems.value], targetFolderId);\n if (currentFolderId.value === null) {\n const movedMap = new Map(movedItems.map((item) => [item.id, item]));\n items.value = items.value.map((item) => movedMap.get(item.id) ?? item);\n } else {\n items.value = items.value.filter(\n (item) => !selectedItems.value.has(item.id),\n );\n }\n selectedItems.value = new Set();\n previewItem.value = null;\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function updateFile(\n mediaId: string,\n filename: string,\n altText?: string,\n ): Promise<boolean> {\n const { update } = provider;\n if (typeof update !== \"function\") {\n return false;\n }\n\n try {\n const patch: MediaAssetPatch = { filename };\n if (altText !== undefined) {\n patch.alt = altText;\n }\n const updated = await update(mediaId, patch);\n items.value = items.value.map((item) =>\n item.id === mediaId ? updated : item,\n );\n if (previewItem.value?.id === mediaId) {\n previewItem.value = updated;\n }\n notify(() => provider.onUpdated?.(updated));\n return true;\n } catch (error) {\n options.onError?.(error as Error);\n return false;\n }\n }\n\n function captureLocalAssets(ids: string[]): MediaAsset[] {\n const captured = new Map<string, MediaAsset>();\n for (const item of items.value) {\n if (ids.includes(item.id)) {\n captured.set(item.id, item);\n }\n }\n for (const item of frequentlyUsedItems.value) {\n if (ids.includes(item.id) && !captured.has(item.id)) {\n captured.set(item.id, item);\n }\n }\n return [...captured.values()];\n }\n\n function dropLocalAssets(ids: string[]): void {\n items.value = items.value.filter((item) => !ids.includes(item.id));\n frequentlyUsedItems.value = frequentlyUsedItems.value.filter(\n (item) => !ids.includes(item.id),\n );\n selectedItems.value = new Set();\n previewItem.value = null;\n }\n\n async function deleteSelected(): Promise<void> {\n if (selectedItems.value.size === 0) return;\n\n const { delete: providerDelete } = provider;\n if (typeof providerDelete !== \"function\") {\n return;\n }\n\n const ids = [...selectedItems.value];\n const captured = captureLocalAssets(ids);\n\n try {\n await providerDelete(ids);\n dropLocalAssets(ids);\n for (const asset of captured) {\n notify(() => provider.onDeleted?.(asset));\n }\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function loadFrequentlyUsed(): Promise<void> {\n const { frequentlyUsed } = provider;\n if (typeof frequentlyUsed !== \"function\") {\n return;\n }\n\n try {\n frequentlyUsedItems.value = await frequentlyUsed();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function checkUsageBeforeDelete(): Promise<boolean> {\n if (selectedItems.value.size === 0) {\n return false;\n }\n\n pendingDeleteIds.value = [...selectedItems.value];\n\n const { checkUsage } = provider;\n if (typeof checkUsage !== \"function\") {\n deleteUsageInfo.value = {};\n showDeleteWarning.value = true;\n return false;\n }\n\n try {\n const usage = await checkUsage(pendingDeleteIds.value);\n deleteUsageInfo.value = usage;\n\n const hasUsage = Object.values(usage).some(\n (info) => info.templateCount > 0,\n );\n\n showDeleteWarning.value = true;\n return hasUsage;\n } catch (error) {\n options.onError?.(error as Error);\n return false;\n }\n }\n\n async function confirmDelete(): Promise<void> {\n const { delete: providerDelete } = provider;\n if (typeof providerDelete !== \"function\") {\n return;\n }\n\n showDeleteWarning.value = false;\n\n if (pendingDeleteIds.value.length === 0) {\n return;\n }\n\n const ids = pendingDeleteIds.value;\n const captured = captureLocalAssets(ids);\n\n try {\n await providerDelete(ids);\n dropLocalAssets(ids);\n pendingDeleteIds.value = [];\n deleteUsageInfo.value = {};\n for (const asset of captured) {\n notify(() => provider.onDeleted?.(asset));\n }\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function cancelDelete(): void {\n showDeleteWarning.value = false;\n pendingDeleteIds.value = [];\n deleteUsageInfo.value = {};\n }\n\n async function importFromUrl(url: string): Promise<MediaAsset | null> {\n const importUrl = provider.importFromUrl;\n if (typeof importUrl !== \"function\") {\n return null;\n }\n\n isImportingFromUrl.value = true;\n importFromUrlError.value = null;\n try {\n const media = await importUrl(\n url,\n currentFolderId.value,\n resolvedTemplateId(),\n );\n items.value = [media, ...items.value];\n notify(() => provider.onCreated?.(media));\n return media;\n } catch (error) {\n importFromUrlError.value =\n error instanceof Error ? error.message : \"Import failed\";\n options.onError?.(error as Error);\n return null;\n } finally {\n isImportingFromUrl.value = false;\n }\n }\n\n function toggleSelection(id: string): void {\n const next = new Set(selectedItems.value);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n selectedItems.value = next;\n }\n\n function clearSelection(): void {\n selectedItems.value = new Set();\n previewItem.value = null;\n }\n\n function selectItem(item: MediaAsset): void {\n previewItem.value = item;\n selectedItems.value = new Set([item.id]);\n }\n\n async function loadStorage(): Promise<void> {\n const { storage } = provider;\n if (typeof storage !== \"function\") {\n storageInfo.value = null;\n return;\n }\n\n try {\n storageInfo.value = await storage();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function loadFolders(): Promise<void> {\n const foldersApi = foldersProvider();\n if (!foldersApi) {\n return;\n }\n\n try {\n folders.value = await foldersApi.list();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function createFolder(\n name: string,\n parentId?: string | null,\n ): Promise<MediaFolder | null> {\n const foldersApi = foldersProvider();\n const { create } = foldersApi ?? { create: false };\n if (typeof create !== \"function\") {\n return null;\n }\n\n try {\n const input: MediaFolderInput = { name };\n if (parentId !== undefined) {\n input.parentId = parentId;\n }\n const folder = await create(input);\n await loadFolders();\n return folder;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n }\n }\n\n function findFolderInTree(\n folderList: MediaFolder[],\n id: string,\n ): MediaFolder | null {\n for (const folder of folderList) {\n if (folder.id === id) return folder;\n }\n return null;\n }\n\n async function renameFolder(folderId: string, name: string): Promise<void> {\n const foldersApi = foldersProvider();\n const { update } = foldersApi ?? { update: false };\n if (typeof update !== \"function\") {\n return;\n }\n\n try {\n await update(folderId, { name });\n await loadFolders();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function deleteFolder(folderId: string): Promise<void> {\n const foldersApi = foldersProvider();\n const { delete: deleteFn } = foldersApi ?? { delete: false };\n if (typeof deleteFn !== \"function\") {\n return;\n }\n\n try {\n const folder = findFolderInTree(folders.value, folderId);\n const parentId = folder?.parentId ?? null;\n\n await deleteFn(folderId);\n\n if (currentFolderId.value === folderId) {\n currentFolderId.value = parentId;\n }\n\n await loadFolders();\n await loadItems();\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n async function checkUsageBeforeReplace(item: MediaAsset): Promise<void> {\n pendingReplaceItem.value = item;\n replaceError.value = null;\n\n const { checkUsage } = provider;\n if (typeof checkUsage !== \"function\") {\n replaceUsageInfo.value = null;\n showReplaceWarning.value = true;\n return;\n }\n\n try {\n const usage = await checkUsage([item.id]);\n replaceUsageInfo.value = usage[item.id] ?? null;\n showReplaceWarning.value = true;\n } catch (error) {\n options.onError?.(error as Error);\n }\n }\n\n function cancelReplace(): void {\n showReplaceWarning.value = false;\n pendingReplaceItem.value = null;\n replaceUsageInfo.value = null;\n replaceError.value = null;\n }\n\n function applyReplaced(updated: MediaAsset): void {\n items.value = items.value.map((item) =>\n item.id === updated.id ? updated : item,\n );\n\n frequentlyUsedItems.value = frequentlyUsedItems.value.map((item) =>\n item.id === updated.id ? updated : item,\n );\n\n if (previewItem.value?.id === updated.id) {\n previewItem.value = updated;\n }\n\n notify(() => provider.onUpdated?.(updated));\n }\n\n async function replaceFile(file: File): Promise<MediaAsset | null> {\n const { replace } = provider;\n if (typeof replace !== \"function\" || !pendingReplaceItem.value) {\n return null;\n }\n\n isReplacing.value = true;\n replaceError.value = null;\n\n try {\n const updated = await replace(pendingReplaceItem.value.id, file);\n applyReplaced(updated);\n\n showReplaceWarning.value = false;\n pendingReplaceItem.value = null;\n replaceUsageInfo.value = null;\n\n return updated;\n } catch (error) {\n replaceError.value =\n error instanceof Error ? error.message : \"Replace failed\";\n options.onError?.(error as Error);\n return null;\n } finally {\n isReplacing.value = false;\n }\n }\n\n async function replaceMediaDirectly(\n mediaId: string,\n file: File,\n ): Promise<MediaAsset | null> {\n const { replace } = provider;\n if (typeof replace !== \"function\") {\n return null;\n }\n\n try {\n const updated = await replace(mediaId, file);\n applyReplaced(updated);\n return updated;\n } catch (error) {\n options.onError?.(error as Error);\n return null;\n }\n }\n\n return {\n items,\n folders,\n currentFolderId,\n viewMode,\n searchQuery,\n categoryFilter,\n sortOption,\n isLoading,\n isUploading,\n uploadProgress,\n hasMore,\n selectedItems,\n previewItem,\n frequentlyUsedItems,\n deleteUsageInfo,\n showDeleteWarning,\n loadItems,\n loadMore,\n search,\n filterByCategory,\n sortBy,\n navigateToFolder,\n showFrequentlyUsed,\n uploadFile,\n uploadFiles,\n moveSelected,\n updateFile,\n deleteSelected,\n isImportingFromUrl,\n importFromUrlError,\n importFromUrl,\n toggleSelection,\n clearSelection,\n selectItem,\n loadFolders,\n createFolder,\n renameFolder,\n deleteFolder,\n findFolderInTree,\n loadFrequentlyUsed,\n checkUsageBeforeDelete,\n confirmDelete,\n cancelDelete,\n isReplacing,\n replaceError,\n showReplaceWarning,\n pendingReplaceItem,\n replaceUsageInfo,\n checkUsageBeforeReplace,\n cancelReplace,\n replaceFile,\n replaceMediaDirectly,\n storageInfo,\n loadStorage,\n };\n}\n","<script setup lang=\"ts\">\nimport MediaBreadcrumb from \"./media/MediaBreadcrumb.vue\";\nimport MediaFolderTree from \"./media/MediaFolderTree.vue\";\nimport MediaGrid from \"./media/MediaGrid.vue\";\nimport MediaImportUrlModal from \"./media/MediaImportUrlModal.vue\";\nimport MediaMovePicker from \"./media/MediaMovePicker.vue\";\nimport MediaPreviewPanel from \"./media/MediaPreviewPanel.vue\";\nimport MediaReplaceModal from \"./media/MediaReplaceModal.vue\";\nimport MediaUploadZone from \"./media/MediaUploadZone.vue\";\nimport StorageProgressRing from \"./media/StorageProgressRing.vue\";\nimport { useMediaCategories } from \"../composables/useMediaCategories\";\nimport { useMediaLibraryUI } from \"../composables/useMediaLibraryUI\";\nimport { useMediaLibrary } from \"../composable\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport {\n Check,\n Copy,\n Grid2x2,\n Link,\n List,\n PanelLeft,\n Search,\n X,\n} from \"@lucide/vue\";\nimport {\n computed,\n defineAsyncComponent,\n onBeforeUnmount,\n provide,\n watch,\n} from \"vue\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { MEDIA_LIMITS_KEY } from \"../keys\";\n\nconst props = defineProps<{\n provider: MediaProvider;\n accept?: MediaCategory[];\n templateId?: string;\n onError?: (error: Error) => void;\n translations: MediaTranslations;\n showClose?: boolean;\n showConfirm?: boolean;\n confirmTestId?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"confirm\", item: MediaAsset): void;\n (e: \"ready\"): void;\n}>();\n\nconst MediaEditModal = defineAsyncComponent(\n () => import(\"./media/MediaEditModal.vue\"),\n);\n\nconst t = computed(() => props.translations);\nconst resolvedConfirmTestId = computed(\n () => props.confirmTestId ?? \"media-confirm\",\n);\n\nconst mediaLimits = {\n get maxFileSize() {\n return props.provider.maxFileSize;\n },\n get mimeTypes() {\n return props.provider.mimeTypes;\n },\n get accept() {\n return props.accept;\n },\n};\nprovide(MEDIA_LIMITS_KEY, mediaLimits);\n\nconst { isAcceptedMimeType, availableCategories } =\n useMediaCategories(mediaLimits);\n\nconst library = useMediaLibrary({\n provider: props.provider,\n templateId: () => props.templateId,\n onError: (error) => props.onError?.(error),\n});\n\nconst ui = useMediaLibraryUI({\n library,\n translations: t,\n});\n\nconst canCreate = computed(() => typeof props.provider.create === \"function\");\nconst canFolders = computed(() => props.provider.folders !== false);\nconst canImport = computed(\n () => typeof props.provider.importFromUrl === \"function\",\n);\nconst canDelete = computed(() => typeof props.provider.delete === \"function\");\nconst canUpdate = computed(() => typeof props.provider.update === \"function\");\nconst canReplace = computed(() => typeof props.provider.replace === \"function\");\nconst canFrequentlyUsed = computed(\n () => typeof props.provider.frequentlyUsed === \"function\",\n);\nconst canMove = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.move === \"function\";\n});\nconst canCreateFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.create === \"function\";\n});\nconst canRenameFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.update === \"function\";\n});\nconst canDeleteFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.delete === \"function\";\n});\n\nconst isInitialLoad = computed(\n () => library.isLoading.value && ui.displayItems.value.length === 0,\n);\n\nconst isConfirmable = computed(() => {\n const item = library.previewItem.value;\n if (!item) {\n return false;\n }\n if (!props.accept?.length) {\n return true;\n }\n return isAcceptedMimeType(item.mimeType ?? \"\", props.accept);\n});\n\nconst selectedDeletableCount = computed(() => {\n if (!canDelete.value) {\n return 0;\n }\n let count = 0;\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n count += 1;\n }\n }\n return count;\n});\n\nconst nestedDialogOpen = computed(\n () =>\n ui.showImportUrlModal.value ||\n ui.editingItem.value !== null ||\n library.showReplaceWarning.value,\n);\n\nfunction handleEscape(): boolean {\n if (nestedDialogOpen.value) {\n return true;\n }\n if (ui.showMovePicker.value) {\n ui.showMovePicker.value = false;\n return true;\n }\n if (library.showDeleteWarning.value) {\n library.cancelDelete();\n return true;\n }\n return false;\n}\n\nfunction confirmSelection(): void {\n if (!isConfirmable.value || !library.previewItem.value) {\n return;\n }\n emit(\"confirm\", library.previewItem.value);\n}\n\nfunction handleCategoryChange(event: Event): void {\n const value = (event.target as HTMLSelectElement).value;\n library.filterByCategory(value ? (value as MediaCategory) : null);\n}\n\nasync function handleDeleteClick(): Promise<void> {\n const next = new Set<string>();\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n next.add(id);\n }\n }\n library.selectedItems.value = next;\n await ui.handleDeleteClick();\n}\n\nwatch(\n () => props.accept,\n (accept) => {\n if (accept?.length === 1) {\n library.categoryFilter.value = accept[0];\n }\n },\n { immediate: true },\n);\n\nlibrary.loadItems();\nlibrary.loadFrequentlyUsed();\nlibrary.loadStorage();\nemit(\"ready\");\n\nonBeforeUnmount(() => {\n ui.resetUI();\n});\n\ndefineExpose({ nestedDialogOpen, handleEscape, library, ui });\n</script>\n\n<template>\n <div\n class=\"tpl:relative tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:overflow-hidden\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-5 tpl:py-3.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <h2\n id=\"tpl-media-library-title\"\n class=\"tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.title }}\n </h2>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <StorageProgressRing\n v-if=\"library.storageInfo.value\"\n :used-bytes=\"library.storageInfo.value.usedBytes\"\n :limit-bytes=\"library.storageInfo.value.limitBytes\"\n :size=\"22\"\n />\n <div class=\"tpl:relative\">\n <input\n :value=\"ui.searchInput.value\"\n type=\"search\"\n :disabled=\"isInitialLoad\"\n :aria-label=\"t.mediaLibrary.searchAriaLabel\"\n class=\"tpl:w-52 tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-3 tpl:pl-8 tpl:text-xs tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:outline-none tpl:focus:shadow-[var(--tpl-ring)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.searchPlaceholder\"\n @input=\"\n ui.handleSearchInput(($event.target as HTMLInputElement).value)\n \"\n />\n <Search\n class=\"tpl:absolute tpl:top-1/2 tpl:left-2.5 tpl:-translate-y-1/2\"\n :size=\"13\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-text-dim)\"\n />\n </div>\n <button\n v-if=\"showClose\"\n type=\"button\"\n :aria-label=\"t.mediaLibrary.close\"\n class=\"tpl:flex tpl:size-7 tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"emit('close')\"\n >\n <X :size=\"18\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Body -->\n <div class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:overflow-hidden\">\n <Transition\n enter-active-class=\"tpl:transition-all tpl:duration-200 tpl:ease-out\"\n enter-from-class=\"tpl:-ml-48 tpl:opacity-0\"\n enter-to-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-150 tpl:ease-in\"\n leave-from-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-to-class=\"tpl:-ml-48 tpl:opacity-0\"\n >\n <div\n v-if=\"canFolders && ui.showSidebar.value\"\n class=\"tpl:flex tpl:w-48 tpl:shrink-0 tpl:flex-col tpl:border-r\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <MediaFolderTree\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n :view-mode=\"library.viewMode.value\"\n :has-frequently-used=\"\n canFrequentlyUsed && ui.hasFrequentlyUsed.value\n \"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"library.navigateToFolder\"\n @create-folder=\"ui.handleCreateFolder\"\n @rename-folder=\"ui.handleRenameFolder\"\n @delete-folder=\"ui.handleDeleteFolder\"\n @show-frequently-used=\"library.showFrequentlyUsed\"\n />\n </div>\n </Transition>\n\n <!-- Content area -->\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:flex-col\">\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-4 tpl:py-2.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"canFolders\"\n type=\"button\"\n data-testid=\"media-folder-toggle\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: ui.showSidebar.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor: ui.showSidebar.value\n ? 'var(--tpl-bg)'\n : 'transparent',\n border: ui.showSidebar.value\n ? '1px solid var(--tpl-border)'\n : '1px solid transparent',\n }\"\n :title=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-label=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-pressed=\"ui.showSidebar.value\"\n @click=\"ui.showSidebar.value = !ui.showSidebar.value\"\n >\n <PanelLeft :size=\"16\" :stroke-width=\"2\" />\n </button>\n\n <template v-if=\"library.viewMode.value === 'frequently-used'\">\n <span\n class=\"tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.frequentlyUsed }}\n </span>\n </template>\n <template v-else>\n <MediaBreadcrumb\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @navigate=\"library.navigateToFolder\"\n />\n </template>\n\n <div\n class=\"tpl:flex tpl:rounded-md tpl:p-0.5\"\n style=\"\n border: 1px solid var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewGrid\"\n :aria-label=\"t.mediaLibrary.viewGrid\"\n :aria-pressed=\"ui.layoutMode.value === 'grid'\"\n @click=\"ui.layoutMode.value = 'grid'\"\n >\n <Grid2x2 :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewList\"\n :aria-label=\"t.mediaLibrary.viewList\"\n :aria-pressed=\"ui.layoutMode.value === 'list'\"\n @click=\"ui.layoutMode.value = 'list'\"\n >\n <List :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <select\n v-if=\"availableCategories.length > 1\"\n :aria-label=\"t.mediaLibrary.filterAriaLabel\"\n class=\"tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-7 tpl:pl-2.5 tpl:text-xs tpl:transition-all tpl:duration-150 tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :value=\"library.categoryFilter.value ?? ''\"\n @change=\"handleCategoryChange\"\n >\n <option value=\"\">\n {{ t.mediaLibrary.filterAll }}\n </option>\n <option\n v-for=\"category in availableCategories\"\n :key=\"category\"\n :value=\"category\"\n >\n {{ ui.getCategoryLabel(category) }}\n </option>\n </select>\n </div>\n </div>\n\n <div class=\"tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto\">\n <div\n v-if=\"\n library.viewMode.value === 'files' && (canCreate || canImport)\n \"\n class=\"tpl:px-4 tpl:pt-3\"\n >\n <MediaUploadZone\n v-if=\"canCreate\"\n :is-uploading=\"library.isUploading.value\"\n :upload-progress=\"library.uploadProgress.value\"\n @upload=\"ui.handleUpload\"\n />\n <button\n v-if=\"canImport\"\n data-testid=\"media-import-url\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-dashed tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text-muted);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showImportUrlModal.value = true\"\n >\n <Link :size=\"14\" :stroke-width=\"2\" />\n {{ t.mediaLibrary.importFromUrl }}\n </button>\n </div>\n\n <MediaGrid\n :items=\"ui.displayItems.value\"\n :selected-ids=\"library.selectedItems.value\"\n :is-loading=\"library.isLoading.value\"\n :has-more=\"\n library.viewMode.value === 'files' && library.hasMore.value\n \"\n :accept=\"accept\"\n :layout=\"ui.layoutMode.value\"\n :search-query=\"library.searchQuery.value\"\n :can-update=\"canUpdate\"\n :can-replace=\"canReplace\"\n @select=\"ui.handleSelect\"\n @toggle=\"library.toggleSelection\"\n @load-more=\"library.loadMore\"\n @edit=\"ui.handleEditItem\"\n @replace=\"ui.handleReplaceItem\"\n @confirm=\"confirmSelection\"\n />\n </div>\n </div>\n </div>\n\n <MediaImportUrlModal\n :visible=\"ui.showImportUrlModal.value\"\n :is-importing=\"library.isImportingFromUrl.value\"\n :error=\"library.importFromUrlError.value\"\n @import=\"ui.handleImportFromUrl\"\n @close=\"ui.showImportUrlModal.value = false\"\n />\n\n <MediaEditModal\n v-if=\"ui.editingItem.value\"\n :visible=\"true\"\n :item=\"ui.editingItem.value\"\n :save=\"ui.handleEditSave\"\n :can-crop=\"canReplace && ui.editingItem.value.canUpdate !== false\"\n @close=\"ui.editingItem.value = null\"\n />\n\n <MediaReplaceModal\n :visible=\"library.showReplaceWarning.value\"\n :item=\"library.pendingReplaceItem.value\"\n :usage-info=\"library.replaceUsageInfo.value\"\n :is-replacing=\"library.isReplacing.value\"\n :error=\"library.replaceError.value\"\n @replace=\"ui.handleReplaceFile\"\n @close=\"library.cancelReplace\"\n />\n\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"library.showDeleteWarning.value\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-delete-title\"\n class=\"tpl:absolute tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n background-color: var(--tpl-overlay);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n @click.self=\"library.cancelDelete\"\n >\n <div\n class=\"tpl-scale-in tpl:mx-4 tpl:w-full tpl:max-w-sm tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-media-delete-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.deleteWarningTitle }}\n </h3>\n <p\n class=\"tpl:text-xs\"\n :class=\"ui.hasUsedFiles.value ? 'tpl:mb-2' : 'tpl:mb-4'\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningMessage }}\n </p>\n <p\n v-if=\"ui.hasUsedFiles.value\"\n data-testid=\"media-delete-usage\"\n class=\"tpl:mb-4 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningUsageNote }}\n </p>\n\n <div\n v-if=\"ui.hasUsedFiles.value\"\n class=\"tpl:mb-4 tpl:max-h-32 tpl:overflow-y-auto tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div\n v-for=\"(info, mediaId) in library.deleteUsageInfo.value\"\n :key=\"mediaId\"\n class=\"tpl:text-xs\"\n style=\"color: var(--tpl-text)\"\n >\n <template v-if=\"info.templateCount > 0\">\n <span class=\"tpl:font-medium\">\n {{\n ui.displayItems.value.find((i) => i.id === mediaId)\n ?.filename || mediaId\n }}\n </span>\n <span style=\"color: var(--tpl-text-muted)\">\n -\n {{\n t.mediaLibrary.usedInTemplates.replace(\n \"{count}\",\n info.templateCount.toString(),\n )\n }}\n </span>\n </template>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.cancelDelete\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.confirmDelete\"\n >\n {{\n ui.hasUsedFiles.value\n ? t.mediaLibrary.deleteAnyway\n : t.mediaLibrary.confirmDelete\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Footer -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-t tpl:px-5 tpl:py-3\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-3\">\n <MediaPreviewPanel\n v-if=\"library.previewItem.value\"\n :item=\"library.previewItem.value\"\n :folders=\"ui.folderTree.value\"\n />\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-5\">\n <div\n v-if=\"library.selectedItems.value.size > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n v-if=\"library.previewItem.value\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: ui.copied.value\n ? 'var(--tpl-success)'\n : 'var(--tpl-border)',\n color: ui.copied.value ? 'var(--tpl-success)' : 'var(--tpl-text)',\n backgroundColor: 'var(--tpl-bg)',\n }\"\n @click=\"ui.copy(ui.selectedUrl.value!)\"\n >\n <Copy v-if=\"!ui.copied.value\" :size=\"12\" :stroke-width=\"2\" />\n <Check v-else :size=\"12\" :stroke-width=\"2\" />\n {{\n ui.copied.value ? t.mediaLibrary.copied : t.mediaLibrary.copyUrl\n }}\n </button>\n <div v-if=\"canMove\" class=\"tpl:relative\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showMovePicker.value = !ui.showMovePicker.value\"\n >\n {{ t.mediaLibrary.moveSelected }}\n </button>\n <MediaMovePicker\n v-if=\"ui.showMovePicker.value\"\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @select=\"ui.handleMoveToFolder\"\n @close=\"ui.showMovePicker.value = false\"\n />\n </div>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"selectedDeletableCount > 0\"\n data-testid=\"media-delete\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"handleDeleteClick\"\n >\n {{ t.mediaLibrary.deleteSelected }}\n </button>\n <button\n v-if=\"showConfirm\"\n :data-testid=\"resolvedConfirmTestId\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-4 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!isConfirmable\"\n @click=\"confirmSelection\"\n >\n {{\n accept?.length\n ? t.mediaLibrary.selectImage\n : t.mediaLibrary.selectFile\n }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaBreadcrumb from \"./media/MediaBreadcrumb.vue\";\nimport MediaFolderTree from \"./media/MediaFolderTree.vue\";\nimport MediaGrid from \"./media/MediaGrid.vue\";\nimport MediaImportUrlModal from \"./media/MediaImportUrlModal.vue\";\nimport MediaMovePicker from \"./media/MediaMovePicker.vue\";\nimport MediaPreviewPanel from \"./media/MediaPreviewPanel.vue\";\nimport MediaReplaceModal from \"./media/MediaReplaceModal.vue\";\nimport MediaUploadZone from \"./media/MediaUploadZone.vue\";\nimport StorageProgressRing from \"./media/StorageProgressRing.vue\";\nimport { useMediaCategories } from \"../composables/useMediaCategories\";\nimport { useMediaLibraryUI } from \"../composables/useMediaLibraryUI\";\nimport { useMediaLibrary } from \"../composable\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport {\n Check,\n Copy,\n Grid2x2,\n Link,\n List,\n PanelLeft,\n Search,\n X,\n} from \"@lucide/vue\";\nimport {\n computed,\n defineAsyncComponent,\n onBeforeUnmount,\n provide,\n watch,\n} from \"vue\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { MEDIA_LIMITS_KEY } from \"../keys\";\n\nconst props = defineProps<{\n provider: MediaProvider;\n accept?: MediaCategory[];\n templateId?: string;\n onError?: (error: Error) => void;\n translations: MediaTranslations;\n showClose?: boolean;\n showConfirm?: boolean;\n confirmTestId?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"close\"): void;\n (e: \"confirm\", item: MediaAsset): void;\n (e: \"ready\"): void;\n}>();\n\nconst MediaEditModal = defineAsyncComponent(\n () => import(\"./media/MediaEditModal.vue\"),\n);\n\nconst t = computed(() => props.translations);\nconst resolvedConfirmTestId = computed(\n () => props.confirmTestId ?? \"media-confirm\",\n);\n\nconst mediaLimits = {\n get maxFileSize() {\n return props.provider.maxFileSize;\n },\n get mimeTypes() {\n return props.provider.mimeTypes;\n },\n get accept() {\n return props.accept;\n },\n};\nprovide(MEDIA_LIMITS_KEY, mediaLimits);\n\nconst { isAcceptedMimeType, availableCategories } =\n useMediaCategories(mediaLimits);\n\nconst library = useMediaLibrary({\n provider: props.provider,\n templateId: () => props.templateId,\n onError: (error) => props.onError?.(error),\n});\n\nconst ui = useMediaLibraryUI({\n library,\n translations: t,\n});\n\nconst canCreate = computed(() => typeof props.provider.create === \"function\");\nconst canFolders = computed(() => props.provider.folders !== false);\nconst canImport = computed(\n () => typeof props.provider.importFromUrl === \"function\",\n);\nconst canDelete = computed(() => typeof props.provider.delete === \"function\");\nconst canUpdate = computed(() => typeof props.provider.update === \"function\");\nconst canReplace = computed(() => typeof props.provider.replace === \"function\");\nconst canFrequentlyUsed = computed(\n () => typeof props.provider.frequentlyUsed === \"function\",\n);\nconst canMove = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.move === \"function\";\n});\nconst canCreateFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.create === \"function\";\n});\nconst canRenameFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.update === \"function\";\n});\nconst canDeleteFolder = computed(() => {\n const folders = props.provider.folders;\n return folders !== false && typeof folders.delete === \"function\";\n});\n\nconst isInitialLoad = computed(\n () => library.isLoading.value && ui.displayItems.value.length === 0,\n);\n\nconst isConfirmable = computed(() => {\n const item = library.previewItem.value;\n if (!item) {\n return false;\n }\n if (!props.accept?.length) {\n return true;\n }\n return isAcceptedMimeType(item.mimeType ?? \"\", props.accept);\n});\n\nconst selectedDeletableCount = computed(() => {\n if (!canDelete.value) {\n return 0;\n }\n let count = 0;\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n count += 1;\n }\n }\n return count;\n});\n\nconst nestedDialogOpen = computed(\n () =>\n ui.showImportUrlModal.value ||\n ui.editingItem.value !== null ||\n library.showReplaceWarning.value,\n);\n\nfunction handleEscape(): boolean {\n if (nestedDialogOpen.value) {\n return true;\n }\n if (ui.showMovePicker.value) {\n ui.showMovePicker.value = false;\n return true;\n }\n if (library.showDeleteWarning.value) {\n library.cancelDelete();\n return true;\n }\n return false;\n}\n\nfunction confirmSelection(): void {\n if (!isConfirmable.value || !library.previewItem.value) {\n return;\n }\n emit(\"confirm\", library.previewItem.value);\n}\n\nfunction handleCategoryChange(event: Event): void {\n const value = (event.target as HTMLSelectElement).value;\n library.filterByCategory(value ? (value as MediaCategory) : null);\n}\n\nasync function handleDeleteClick(): Promise<void> {\n const next = new Set<string>();\n for (const id of library.selectedItems.value) {\n const item = ui.displayItems.value.find((entry) => entry.id === id);\n if (item?.canDelete !== false) {\n next.add(id);\n }\n }\n library.selectedItems.value = next;\n await ui.handleDeleteClick();\n}\n\nwatch(\n () => props.accept,\n (accept) => {\n if (accept?.length === 1) {\n library.categoryFilter.value = accept[0];\n }\n },\n { immediate: true },\n);\n\nlibrary.loadItems();\nlibrary.loadFrequentlyUsed();\nlibrary.loadStorage();\nemit(\"ready\");\n\nonBeforeUnmount(() => {\n ui.resetUI();\n});\n\ndefineExpose({ nestedDialogOpen, handleEscape, library, ui });\n</script>\n\n<template>\n <div\n class=\"tpl:relative tpl:flex tpl:min-h-0 tpl:flex-1 tpl:flex-col tpl:overflow-hidden\"\n >\n <!-- Header -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-5 tpl:py-3.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <h2\n id=\"tpl-media-library-title\"\n class=\"tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.title }}\n </h2>\n <div class=\"tpl:flex tpl:items-center tpl:gap-3\">\n <StorageProgressRing\n v-if=\"library.storageInfo.value\"\n :used-bytes=\"library.storageInfo.value.usedBytes\"\n :limit-bytes=\"library.storageInfo.value.limitBytes\"\n :size=\"22\"\n />\n <div class=\"tpl:relative\">\n <input\n :value=\"ui.searchInput.value\"\n type=\"search\"\n :disabled=\"isInitialLoad\"\n :aria-label=\"t.mediaLibrary.searchAriaLabel\"\n class=\"tpl:w-52 tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-3 tpl:pl-8 tpl:text-xs tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:outline-none tpl:focus:shadow-[var(--tpl-ring)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :placeholder=\"t.mediaLibrary.searchPlaceholder\"\n @input=\"\n ui.handleSearchInput(($event.target as HTMLInputElement).value)\n \"\n />\n <Search\n class=\"tpl:absolute tpl:top-1/2 tpl:left-2.5 tpl:-translate-y-1/2\"\n :size=\"13\"\n :stroke-width=\"2\"\n style=\"color: var(--tpl-text-dim)\"\n />\n </div>\n <button\n v-if=\"showClose\"\n type=\"button\"\n :aria-label=\"t.mediaLibrary.close\"\n class=\"tpl:flex tpl:size-7 tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n style=\"color: var(--tpl-text-muted)\"\n @click=\"emit('close')\"\n >\n <X :size=\"18\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <!-- Body -->\n <div class=\"tpl:flex tpl:min-h-0 tpl:flex-1 tpl:overflow-hidden\">\n <Transition\n enter-active-class=\"tpl:transition-all tpl:duration-200 tpl:ease-out\"\n enter-from-class=\"tpl:-ml-48 tpl:opacity-0\"\n enter-to-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-active-class=\"tpl:transition-all tpl:duration-150 tpl:ease-in\"\n leave-from-class=\"tpl:ml-0 tpl:opacity-100\"\n leave-to-class=\"tpl:-ml-48 tpl:opacity-0\"\n >\n <div\n v-if=\"canFolders && ui.showSidebar.value\"\n class=\"tpl:flex tpl:w-48 tpl:shrink-0 tpl:flex-col tpl:border-r\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <MediaFolderTree\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n :view-mode=\"library.viewMode.value\"\n :has-frequently-used=\"\n canFrequentlyUsed && ui.hasFrequentlyUsed.value\n \"\n :can-create-folder=\"canCreateFolder\"\n :can-rename-folder=\"canRenameFolder\"\n :can-delete-folder=\"canDeleteFolder\"\n @navigate=\"library.navigateToFolder\"\n @create-folder=\"ui.handleCreateFolder\"\n @rename-folder=\"ui.handleRenameFolder\"\n @delete-folder=\"ui.handleDeleteFolder\"\n @show-frequently-used=\"library.showFrequentlyUsed\"\n />\n </div>\n </Transition>\n\n <!-- Content area -->\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:flex-col\">\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-b tpl:px-4 tpl:py-2.5\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"canFolders\"\n type=\"button\"\n data-testid=\"media-folder-toggle\"\n class=\"tpl:flex tpl:size-7 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded-md tpl:transition-all tpl:duration-150\"\n :style=\"{\n color: ui.showSidebar.value\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor: ui.showSidebar.value\n ? 'var(--tpl-bg)'\n : 'transparent',\n border: ui.showSidebar.value\n ? '1px solid var(--tpl-border)'\n : '1px solid transparent',\n }\"\n :title=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-label=\"\n ui.showSidebar.value\n ? t.mediaLibrary.hideFolders\n : t.mediaLibrary.showFolders\n \"\n :aria-pressed=\"ui.showSidebar.value\"\n @click=\"ui.showSidebar.value = !ui.showSidebar.value\"\n >\n <PanelLeft :size=\"16\" :stroke-width=\"2\" />\n </button>\n\n <template v-if=\"library.viewMode.value === 'frequently-used'\">\n <span\n class=\"tpl:text-xs tpl:font-medium\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.frequentlyUsed }}\n </span>\n </template>\n <template v-else>\n <MediaBreadcrumb\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @navigate=\"library.navigateToFolder\"\n />\n </template>\n\n <div\n class=\"tpl:flex tpl:rounded-md tpl:p-0.5\"\n style=\"\n border: 1px solid var(--tpl-border);\n background-color: var(--tpl-bg);\n \"\n >\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'grid'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewGrid\"\n :aria-label=\"t.mediaLibrary.viewGrid\"\n :aria-pressed=\"ui.layoutMode.value === 'grid'\"\n @click=\"ui.layoutMode.value = 'grid'\"\n >\n <Grid2x2 :size=\"14\" :stroke-width=\"2\" />\n </button>\n <button\n type=\"button\"\n class=\"tpl:flex tpl:size-6 tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:rounded tpl:transition-all tpl:duration-150\"\n :style=\"{\n color:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-primary)'\n : 'var(--tpl-text-muted)',\n backgroundColor:\n ui.layoutMode.value === 'list'\n ? 'var(--tpl-bg-elevated)'\n : 'transparent',\n }\"\n :title=\"t.mediaLibrary.viewList\"\n :aria-label=\"t.mediaLibrary.viewList\"\n :aria-pressed=\"ui.layoutMode.value === 'list'\"\n @click=\"ui.layoutMode.value = 'list'\"\n >\n <List :size=\"14\" :stroke-width=\"2\" />\n </button>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <select\n v-if=\"availableCategories.length > 1\"\n :aria-label=\"t.mediaLibrary.filterAriaLabel\"\n class=\"tpl:rounded-md tpl:border tpl:py-1.5 tpl:pr-7 tpl:pl-2.5 tpl:text-xs tpl:transition-all tpl:duration-150 tpl:outline-none\"\n style=\"\n border-color: var(--tpl-border);\n background-color: var(--tpl-bg);\n color: var(--tpl-text);\n \"\n :value=\"library.categoryFilter.value ?? ''\"\n @change=\"handleCategoryChange\"\n >\n <option value=\"\">\n {{ t.mediaLibrary.filterAll }}\n </option>\n <option\n v-for=\"category in availableCategories\"\n :key=\"category\"\n :value=\"category\"\n >\n {{ ui.getCategoryLabel(category) }}\n </option>\n </select>\n </div>\n </div>\n\n <div class=\"tpl:min-h-0 tpl:flex-1 tpl:overflow-y-auto\">\n <div\n v-if=\"\n library.viewMode.value === 'files' && (canCreate || canImport)\n \"\n class=\"tpl:px-4 tpl:pt-3\"\n >\n <MediaUploadZone\n v-if=\"canCreate\"\n :is-uploading=\"library.isUploading.value\"\n :upload-progress=\"library.uploadProgress.value\"\n @upload=\"ui.handleUpload\"\n />\n <button\n v-if=\"canImport\"\n data-testid=\"media-import-url\"\n class=\"tpl:mt-2 tpl:flex tpl:w-full tpl:cursor-pointer tpl:items-center tpl:justify-center tpl:gap-1.5 tpl:rounded-md tpl:border tpl:border-dashed tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text-muted);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showImportUrlModal.value = true\"\n >\n <Link :size=\"14\" :stroke-width=\"2\" />\n {{ t.mediaLibrary.importFromUrl }}\n </button>\n </div>\n\n <MediaGrid\n :items=\"ui.displayItems.value\"\n :selected-ids=\"library.selectedItems.value\"\n :is-loading=\"library.isLoading.value\"\n :has-more=\"\n library.viewMode.value === 'files' && library.hasMore.value\n \"\n :accept=\"accept\"\n :layout=\"ui.layoutMode.value\"\n :search-query=\"library.searchQuery.value\"\n :can-update=\"canUpdate\"\n :can-replace=\"canReplace\"\n @select=\"ui.handleSelect\"\n @toggle=\"library.toggleSelection\"\n @load-more=\"library.loadMore\"\n @edit=\"ui.handleEditItem\"\n @replace=\"ui.handleReplaceItem\"\n @confirm=\"confirmSelection\"\n />\n </div>\n </div>\n </div>\n\n <MediaImportUrlModal\n :visible=\"ui.showImportUrlModal.value\"\n :is-importing=\"library.isImportingFromUrl.value\"\n :error=\"library.importFromUrlError.value\"\n @import=\"ui.handleImportFromUrl\"\n @close=\"ui.showImportUrlModal.value = false\"\n />\n\n <MediaEditModal\n v-if=\"ui.editingItem.value\"\n :visible=\"true\"\n :item=\"ui.editingItem.value\"\n :save=\"ui.handleEditSave\"\n :can-crop=\"canReplace && ui.editingItem.value.canUpdate !== false\"\n @close=\"ui.editingItem.value = null\"\n />\n\n <MediaReplaceModal\n :visible=\"library.showReplaceWarning.value\"\n :item=\"library.pendingReplaceItem.value\"\n :usage-info=\"library.replaceUsageInfo.value\"\n :is-replacing=\"library.isReplacing.value\"\n :error=\"library.replaceError.value\"\n @replace=\"ui.handleReplaceFile\"\n @close=\"library.cancelReplace\"\n />\n\n <Transition\n enter-active-class=\"tpl:transition tpl:ease-out tpl:duration-150\"\n enter-from-class=\"tpl:opacity-0\"\n enter-to-class=\"tpl:opacity-100\"\n leave-active-class=\"tpl:transition tpl:ease-in tpl:duration-100\"\n leave-from-class=\"tpl:opacity-100\"\n leave-to-class=\"tpl:opacity-0\"\n >\n <div\n v-if=\"library.showDeleteWarning.value\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"tpl-media-delete-title\"\n class=\"tpl:absolute tpl:inset-0 tpl:z-10 tpl:flex tpl:items-center tpl:justify-center tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n background-color: var(--tpl-overlay);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n \"\n @click.self=\"library.cancelDelete\"\n >\n <div\n class=\"tpl-scale-in tpl:mx-4 tpl:w-full tpl:max-w-sm tpl:rounded-[var(--tpl-radius-lg)] tpl:p-5\"\n style=\"\n background-color: var(--tpl-bg-elevated);\n box-shadow: var(--tpl-shadow-xl);\n \"\n >\n <h3\n id=\"tpl-media-delete-title\"\n class=\"tpl:mb-2 tpl:text-sm tpl:font-semibold\"\n style=\"color: var(--tpl-text)\"\n >\n {{ t.mediaLibrary.deleteWarningTitle }}\n </h3>\n <p\n class=\"tpl:text-xs\"\n :class=\"ui.hasUsedFiles.value ? 'tpl:mb-2' : 'tpl:mb-4'\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningMessage }}\n </p>\n <p\n v-if=\"ui.hasUsedFiles.value\"\n data-testid=\"media-delete-usage\"\n class=\"tpl:mb-4 tpl:text-xs\"\n style=\"color: var(--tpl-text-muted)\"\n >\n {{ t.mediaLibrary.deleteWarningUsageNote }}\n </p>\n\n <div\n v-if=\"ui.hasUsedFiles.value\"\n class=\"tpl:mb-4 tpl:max-h-32 tpl:overflow-y-auto tpl:rounded tpl:border tpl:p-2\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div\n v-for=\"(info, mediaId) in library.deleteUsageInfo.value\"\n :key=\"mediaId\"\n class=\"tpl:text-xs\"\n style=\"color: var(--tpl-text)\"\n >\n <template v-if=\"info.templateCount > 0\">\n <span class=\"tpl:font-medium\">\n {{\n ui.displayItems.value.find((i) => i.id === mediaId)\n ?.filename || mediaId\n }}\n </span>\n <span style=\"color: var(--tpl-text-muted)\">\n -\n {{\n t.mediaLibrary.usedInTemplates.replace(\n \"{count}\",\n info.templateCount.toString(),\n )\n }}\n </span>\n </template>\n </div>\n </div>\n\n <div class=\"tpl:flex tpl:justify-end tpl:gap-2\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.cancelDelete\"\n >\n {{ t.mediaLibrary.cancel }}\n </button>\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"library.confirmDelete\"\n >\n {{\n ui.hasUsedFiles.value\n ? t.mediaLibrary.deleteAnyway\n : t.mediaLibrary.confirmDelete\n }}\n </button>\n </div>\n </div>\n </div>\n </Transition>\n\n <!-- Footer -->\n <div\n class=\"tpl:flex tpl:shrink-0 tpl:items-center tpl:justify-between tpl:border-t tpl:px-5 tpl:py-3\"\n style=\"border-color: var(--tpl-border)\"\n >\n <div class=\"tpl:flex tpl:min-w-0 tpl:flex-1 tpl:items-center tpl:gap-3\">\n <MediaPreviewPanel\n v-if=\"library.previewItem.value\"\n :item=\"library.previewItem.value\"\n :folders=\"ui.folderTree.value\"\n />\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-5\">\n <div\n v-if=\"library.selectedItems.value.size > 0\"\n class=\"tpl:flex tpl:items-center tpl:gap-2\"\n >\n <button\n v-if=\"library.previewItem.value\"\n class=\"tpl:flex tpl:cursor-pointer tpl:items-center tpl:gap-1 tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n :style=\"{\n borderColor: ui.copied.value\n ? 'var(--tpl-success)'\n : 'var(--tpl-border)',\n color: ui.copied.value ? 'var(--tpl-success)' : 'var(--tpl-text)',\n backgroundColor: 'var(--tpl-bg)',\n }\"\n @click=\"ui.copy(ui.selectedUrl.value!)\"\n >\n <Copy v-if=\"!ui.copied.value\" :size=\"12\" :stroke-width=\"2\" />\n <Check v-else :size=\"12\" :stroke-width=\"2\" />\n {{\n ui.copied.value ? t.mediaLibrary.copied : t.mediaLibrary.copyUrl\n }}\n </button>\n <div v-if=\"canMove\" class=\"tpl:relative\">\n <button\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-border);\n color: var(--tpl-text);\n background-color: var(--tpl-bg);\n \"\n @click=\"ui.showMovePicker.value = !ui.showMovePicker.value\"\n >\n {{ t.mediaLibrary.moveSelected }}\n </button>\n <MediaMovePicker\n v-if=\"ui.showMovePicker.value\"\n :folders=\"ui.folderTree.value\"\n :current-folder-id=\"library.currentFolderId.value\"\n @select=\"ui.handleMoveToFolder\"\n @close=\"ui.showMovePicker.value = false\"\n />\n </div>\n </div>\n <div class=\"tpl:flex tpl:items-center tpl:gap-2\">\n <button\n v-if=\"selectedDeletableCount > 0\"\n data-testid=\"media-delete\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:border tpl:px-3 tpl:py-1.5 tpl:text-xs tpl:font-medium tpl:transition-all tpl:duration-150\"\n style=\"\n border-color: var(--tpl-danger);\n color: var(--tpl-danger);\n background-color: var(--tpl-bg);\n \"\n @click=\"handleDeleteClick\"\n >\n {{ t.mediaLibrary.deleteSelected }}\n </button>\n <button\n v-if=\"showConfirm\"\n :data-testid=\"resolvedConfirmTestId\"\n class=\"tpl:cursor-pointer tpl:rounded-md tpl:px-4 tpl:py-1.5 tpl:text-sm tpl:font-medium tpl:shadow-xs tpl:transition-all tpl:duration-150 tpl:hover:bg-[var(--tpl-primary-hover)] tpl:disabled:cursor-not-allowed tpl:disabled:opacity-50\"\n style=\"\n background-color: var(--tpl-primary);\n color: var(--tpl-on-primary);\n \"\n :disabled=\"!isConfirmable\"\n @click=\"confirmSelection\"\n >\n {{\n accept?.length\n ? t.mediaLibrary.selectImage\n : t.mediaLibrary.selectFile\n }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaLibraryChrome from \"../components/MediaLibraryChrome.vue\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { computed, provide, ref, toRef } from \"vue\";\nimport { POPOVER_TARGET_KEY, TRANSLATIONS_KEY, UI_LOCALE_KEY } from \"../keys\";\n\nconst props = defineProps<{\n /**\n * Storage backend. **A prop, not an injection.** A bare string never\n * resolves the `Symbol` a host provides, and the miss is silent. A prop\n * is checked at compile time.\n */\n provider: MediaProvider;\n translations: MediaTranslations;\n onSelect?: (asset: MediaAsset) => void;\n accept?: MediaCategory[];\n locale?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"ready\"): void;\n}>();\n\nconst t = computed(() => props.translations);\n\n// `TRANSLATIONS_KEY`, not the bare string: a string never resolves the\n// `Symbol` a host provides under the same name. Wrapped in a ref because\n// the key carries one — this shell always has its strings up front.\nprovide(TRANSLATIONS_KEY, ref(props.translations));\n\nprovide(\n UI_LOCALE_KEY,\n toRef(() => props.locale),\n);\n\nconst popoverRoot = ref<HTMLElement | null>(null);\nprovide(POPOVER_TARGET_KEY, popoverRoot);\n\nfunction handleConfirm(item: MediaAsset): void {\n props.onSelect?.(item);\n}\n</script>\n\n<template>\n <div\n ref=\"popoverRoot\"\n class=\"tpl tpl:flex tpl:flex-col tpl:overflow-hidden tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n width: 100%;\n height: 100%;\n background-color: var(--tpl-bg-elevated);\n border: 1px solid var(--tpl-border);\n \"\n >\n <MediaLibraryChrome\n :provider=\"provider\"\n :accept=\"accept\"\n :translations=\"t\"\n :show-confirm=\"!!onSelect\"\n confirm-test-id=\"media-select\"\n @confirm=\"handleConfirm\"\n @ready=\"emit('ready')\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport MediaLibraryChrome from \"../components/MediaLibraryChrome.vue\";\nimport type {\n MediaAsset,\n MediaCategory,\n MediaProvider,\n} from \"@templatical/types\";\nimport type { MediaTranslations } from \"../i18n\";\nimport { computed, provide, ref, toRef } from \"vue\";\nimport { POPOVER_TARGET_KEY, TRANSLATIONS_KEY, UI_LOCALE_KEY } from \"../keys\";\n\nconst props = defineProps<{\n /**\n * Storage backend. **A prop, not an injection.** A bare string never\n * resolves the `Symbol` a host provides, and the miss is silent. A prop\n * is checked at compile time.\n */\n provider: MediaProvider;\n translations: MediaTranslations;\n onSelect?: (asset: MediaAsset) => void;\n accept?: MediaCategory[];\n locale?: string;\n}>();\n\nconst emit = defineEmits<{\n (e: \"ready\"): void;\n}>();\n\nconst t = computed(() => props.translations);\n\n// `TRANSLATIONS_KEY`, not the bare string: a string never resolves the\n// `Symbol` a host provides under the same name. Wrapped in a ref because\n// the key carries one — this shell always has its strings up front.\nprovide(TRANSLATIONS_KEY, ref(props.translations));\n\nprovide(\n UI_LOCALE_KEY,\n toRef(() => props.locale),\n);\n\nconst popoverRoot = ref<HTMLElement | null>(null);\nprovide(POPOVER_TARGET_KEY, popoverRoot);\n\nfunction handleConfirm(item: MediaAsset): void {\n props.onSelect?.(item);\n}\n</script>\n\n<template>\n <div\n ref=\"popoverRoot\"\n class=\"tpl tpl:flex tpl:flex-col tpl:overflow-hidden tpl:rounded-[var(--tpl-radius-lg)]\"\n style=\"\n width: 100%;\n height: 100%;\n background-color: var(--tpl-bg-elevated);\n border: 1px solid var(--tpl-border);\n \"\n >\n <MediaLibraryChrome\n :provider=\"provider\"\n :accept=\"accept\"\n :translations=\"t\"\n :show-confirm=\"!!onSelect\"\n confirm-test-id=\"media-select\"\n @confirm=\"handleConfirm\"\n @ready=\"emit('ready')\"\n />\n </div>\n</template>\n","/// <reference types=\"vite/client\" />\n\nimport type en from \"./locales/en\";\n\nexport type MediaTranslations = typeof en;\n\n// Vite resolves this glob at build time. Adding a new locale file\n// automatically registers it — no array to keep in sync.\nconst localeModules = import.meta.glob<{ default: MediaTranslations }>(\n \"./locales/*.ts\",\n);\n\nfunction localesFromGlob(modules: Record<string, unknown>): string[] {\n return Object.keys(modules)\n .map((path) => path.match(/\\/([^/]+)\\.ts$/)?.[1])\n .filter((locale): locale is string => Boolean(locale));\n}\n\nconst supportedLocales = localesFromGlob(localeModules);\n\nfunction canonicalize(locale: string): string {\n return locale.trim().replace(/_/g, \"-\").toLowerCase();\n}\n\nfunction findSupportedLocale(locale: string): string | undefined {\n const canonical = canonicalize(locale);\n return supportedLocales.find((s) => canonicalize(s) === canonical);\n}\n\nfunction getBaseLocale(locale: string): string {\n return canonicalize(locale).split(\"-\")[0];\n}\n\nfunction resolveLocale(locale: string): string {\n return (\n findSupportedLocale(locale) ??\n findSupportedLocale(getBaseLocale(locale)) ??\n \"en\"\n );\n}\n\nexport async function loadMediaTranslations(\n locale: string,\n): Promise<MediaTranslations> {\n const target = resolveLocale(locale);\n const loader = localeModules[`./locales/${target}.ts`];\n const module = await loader();\n return module.default;\n}\n","import MediaLibrary from \"./MediaLibrary.vue\";\nimport { loadMediaTranslations, type MediaTranslations } from \"../i18n\";\nimport type { MediaLibraryConfig, MediaLibraryInstance } from \"./types\";\nimport { createApp, h, ref, type App, type Ref } from \"vue\";\n\nimport \"../styles/index.css\";\n\nexport type { MediaAsset, MediaFolder } from \"@templatical/types\";\nexport type { MediaLibraryConfig, MediaLibraryInstance } from \"./types\";\n\nlet appInstance: App | null = null;\nconst mediaLibraryRef: Ref<InstanceType<typeof MediaLibrary> | null> =\n ref(null);\n\nasync function init(config: MediaLibraryConfig): Promise<MediaLibraryInstance> {\n const container =\n typeof config.container === \"string\"\n ? document.querySelector(config.container)\n : config.container;\n\n if (!container) {\n throw new Error(`Container element not found: ${config.container}`);\n }\n\n const translations: MediaTranslations = await loadMediaTranslations(\n config.locale ?? \"en\",\n );\n\n applyTheme(container as HTMLElement, config.theme);\n\n // Unmount any prior app *after* awaits so concurrent init() calls don't\n // both pass an early check while appInstance is still null and orphan\n // the first-mounted app.\n if (appInstance) {\n unmount();\n }\n\n return new Promise((resolve, reject) => {\n try {\n appInstance = createApp({\n setup() {\n const onReady = () => {\n const instance: MediaLibraryInstance = {\n setTheme: (theme) => applyTheme(container as HTMLElement, theme),\n unmount,\n };\n\n resolve(instance);\n };\n\n return () =>\n h(MediaLibrary, {\n provider: config.provider,\n translations,\n onSelect: config.onSelect,\n accept: config.accept,\n locale: config.locale,\n ref: mediaLibraryRef,\n onReady,\n });\n },\n });\n\n appInstance.mount(container);\n } catch (error) {\n reject(error);\n }\n });\n}\n\nfunction unmount(): void {\n if (appInstance) {\n appInstance.unmount();\n appInstance = null;\n mediaLibraryRef.value = null;\n }\n}\n\nfunction applyTheme(\n container: HTMLElement,\n theme?: { primaryColor?: string; borderRadius?: number },\n): void {\n if (!theme) {\n return;\n }\n\n if (theme.primaryColor) {\n // `--tpl-user-*`: `.tpl` redeclares `--tpl-primary` from this override.\n // Writing `--tpl-primary` on the container is wiped by that declaration.\n container.style.setProperty(\"--tpl-user-primary\", theme.primaryColor);\n }\n\n if (theme.borderRadius !== undefined) {\n container.style.setProperty(\"--tpl-user-radius\", `${theme.borderRadius}px`);\n container.style.setProperty(\n \"--tpl-user-radius-sm\",\n `${Math.max(0, theme.borderRadius - 3)}px`,\n );\n container.style.setProperty(\n \"--tpl-user-radius-lg\",\n `${theme.borderRadius + 4}px`,\n );\n }\n}\n\nconst TemplaticalMedia = {\n init,\n unmount,\n};\n\nif (typeof window !== \"undefined\") {\n (\n window as unknown as Window & { TemplaticalMedia: typeof TemplaticalMedia }\n ).TemplaticalMedia = TemplaticalMedia;\n}\n\nexport { init, unmount };\n\nexport default TemplaticalMedia;\n"],"mappings":"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"}
|