@webx-ui/module-media 0.8.13 → 0.8.14

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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/api.ts","../src/format.ts","../src/DirectoryTree.vue","../src/FileGrid.vue","../src/MediaToolbar.vue","../src/MoveDialog.vue","../src/NameDialog.vue","../src/messages.ts","../src/i18n.ts","../src/MediaManager.vue","../src/MediaPicker.vue","../src/openMediaPicker.ts","../src/MediaList.vue","../src/FileField.vue","../src/FilesField.vue","../src/GalleryField.vue","../src/MediaField.vue","../src/MediaPage.vue","../src/index.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { EditOperations, FileQuery, MediaDirectory, MediaFile, MediaPage } from './types'\n\nexport interface MediaApi {\n directories(): Promise<MediaDirectory[]>\n createDirectory(parentId: number, title: string): Promise<MediaDirectory>\n renameDirectory(id: number, title: string): Promise<MediaDirectory>\n moveDirectory(id: number, parentId: number): Promise<MediaDirectory>\n /** Without `force` the server refuses a folder that holds anything, and says what it holds. */\n deleteDirectory(id: number, force?: boolean): Promise<void>\n\n files(query?: FileQuery): Promise<MediaPage>\n file(id: number): Promise<MediaFile>\n /**\n * The file behind a key.\n *\n * An entity stores the key and nothing else, so this is how a form draws the picture it\n * saved last time. `null` when the file is gone from the library.\n */\n fileByPath(path: string): Promise<MediaFile | null>\n upload(\n directoryId: number,\n files: File[],\n onProgress?: (percent: number) => void,\n ): Promise<MediaFile[]>\n rename(id: number, name: string): Promise<MediaFile>\n move(ids: number[], directoryId: number): Promise<number>\n remove(ids: number[]): Promise<number>\n removeOne(id: number): Promise<void>\n\n edit(id: number, operations: EditOperations): Promise<MediaFile>\n copy(id: number): Promise<MediaFile>\n restoreOriginal(id: number): Promise<MediaFile>\n\n /** The address of a preview at a size the server allows. */\n thumb(file: MediaFile, width: number, height?: number, fit?: 'cover' | 'contain'): string | null\n}\n\nexport function createMediaApi(admin: AdminContext): MediaApi {\n const base = `${admin.apiPath}/media`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n directories: () => admin.http.get<{ data: MediaDirectory[] }>(`${base}/directories`).then(data),\n\n createDirectory: (parentId, title) =>\n admin.http\n .post<{ data: MediaDirectory }>(`${base}/directories`, { parent_id: parentId, title })\n .then(data),\n\n renameDirectory: (id, title) =>\n admin.http.patch<{ data: MediaDirectory }>(`${base}/directories/${id}`, { title }).then(data),\n\n moveDirectory: (id, parentId) =>\n admin.http\n .patch<{ data: MediaDirectory }>(`${base}/directories/${id}/move`, { parent_id: parentId })\n .then(data),\n\n deleteDirectory: (id, force = false) =>\n admin.http.delete<void>(`${base}/directories/${id}`, {\n query: { force: force ? 1 : undefined },\n }),\n\n files: (query = {}) =>\n admin.http.get<MediaPage>(`${base}/files`, {\n query: {\n directory_id: query.directory_id ?? undefined,\n q: query.q || undefined,\n type: query.type ?? undefined,\n sort: query.sort,\n page: query.page,\n per_page: query.per_page,\n },\n }),\n\n file: (id) => admin.http.get<{ data: MediaFile }>(`${base}/files/${id}`).then(data),\n\n fileByPath: (path) =>\n admin.http\n .get<{ data: MediaFile }>(`${base}/files/by-path?path=${encodeURIComponent(path)}`)\n .then(data)\n .catch(() => null),\n\n async upload(directoryId, files, onProgress) {\n const body = new FormData()\n body.append('directory_id', String(directoryId))\n files.forEach((file) => body.append('files[]', file))\n\n // XHR rather than fetch, for the one thing fetch still cannot do: say how far an upload\n // has got. A person watching a forty-megabyte video wants a bar, not a spinner.\n return new Promise<MediaFile[]>((resolve, reject) => {\n const request = new XMLHttpRequest()\n\n request.open('POST', `${base}/files`)\n request.responseType = 'json'\n request.withCredentials = true\n request.setRequestHeader('Accept', 'application/json')\n request.setRequestHeader('X-Requested-With', 'XMLHttpRequest')\n // Without this the refusal comes back in English while the panel is in Russian: the\n // panel's own client sends it on every request, and this one is not that client.\n request.setRequestHeader('X-Webx-Locale', admin.i18n.state.locale)\n\n const token = csrfToken()\n if (token) {\n request.setRequestHeader('X-XSRF-TOKEN', token)\n }\n\n request.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable) {\n onProgress?.(Math.round((event.loaded / event.total) * 100))\n }\n })\n\n request.addEventListener('load', () => {\n const answer = request.response as { data?: MediaFile[]; message?: string } | null\n\n if (request.status >= 200 && request.status < 300 && answer?.data) {\n resolve(answer.data)\n\n return\n }\n\n reject(\n Object.assign(new Error(answer?.message ?? 'Upload failed'), {\n status: request.status,\n body: answer,\n }),\n )\n })\n\n request.addEventListener('error', () => reject(new Error('Upload failed')))\n request.send(body)\n })\n },\n\n rename: (id, name) =>\n admin.http.patch<{ data: MediaFile }>(`${base}/files/${id}`, { name }).then(data),\n\n move: (ids, directoryId) =>\n admin.http\n .post<{ data: { moved: number } }>(`${base}/files/move`, { ids, directory_id: directoryId })\n .then((body) => body.data.moved),\n\n // A POST for the batch, not a DELETE: the panel's own client sends no body on DELETE, and\n // a list of ids in a query string is a worse answer than an honest verb.\n remove: (ids) =>\n admin.http\n .post<{ data: { deleted: number } }>(`${base}/files/delete`, { ids })\n .then((body) => body.data.deleted),\n\n removeOne: (id) => admin.http.delete<void>(`${base}/files/${id}`),\n\n edit: (id, operations) =>\n admin.http.post<{ data: MediaFile }>(`${base}/files/${id}/edit`, operations).then(data),\n\n copy: (id) => admin.http.post<{ data: MediaFile }>(`${base}/files/${id}/copy`).then(data),\n\n restoreOriginal: (id) =>\n admin.http.post<{ data: MediaFile }>(`${base}/files/${id}/restore-original`).then(data),\n\n thumb(file, width, height, fit = 'cover') {\n if (!file.thumb) {\n return null\n }\n\n const url = new URL(file.thumb, window.location.origin)\n url.searchParams.set('w', String(width))\n if (height) {\n url.searchParams.set('h', String(height))\n }\n url.searchParams.set('fit', fit)\n\n return url.pathname + url.search\n },\n }\n}\n\nfunction csrfToken(): string | null {\n const cookie = document.cookie.split('; ').find((part) => part.startsWith('XSRF-TOKEN='))\n\n return cookie ? decodeURIComponent(cookie.slice('XSRF-TOKEN='.length)) : null\n}\n","/** Bytes as somebody would say them: 32 MB, not 33 554 432. */\nexport function readable(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n let size = bytes\n let unit = 0\n\n while (size >= 1024 && unit < units.length - 1) {\n size /= 1024\n unit++\n }\n\n return `${size >= 10 || unit === 0 ? Math.round(size) : size.toFixed(1)} ${units[unit]}`\n}\n\n/** The file name out of a stored key, for a file the library can no longer be asked about. */\nexport function nameFromPath(path: string): string {\n return path.split('/').pop() || path\n}\n","<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { WxTree, type TreeDropEvent } from '@webx-ui/core'\nimport type { MediaDirectory } from './types'\n\n/**\n * The folders, on the left.\n *\n * The root carries a translated label rather than the title it was given in the database: it is\n * the library itself, and an editor should not have to know it is a row.\n */\nconst props = defineProps<{\n directories: MediaDirectory[]\n}>()\n\nconst selected = defineModel<number | null>('selected', { default: null })\n\nconst emit = defineEmits<{\n move: [id: number, parentId: number]\n}>()\n\nconst t = useTranslate('webx-media')\n\ntype Node = MediaDirectory & { label: string }\n\n// The tree rearranges this list itself when something is dropped, and then says so; the answer\n// from the server replaces it a moment later. A computed would have nothing to rearrange.\nconst nodes = ref<Node[]>([])\nconst expanded = ref<number[]>([])\n\nwatch(\n () => props.directories,\n (directories) => {\n nodes.value = directories.map(label)\n expanded.value = keysOf(directories)\n },\n { immediate: true, deep: true },\n)\n\nfunction label(directory: MediaDirectory): Node {\n return {\n ...directory,\n label: directory.is_root ? t('manager.root') : directory.title,\n children: (directory.children ?? []).map(label),\n }\n}\n\nfunction keysOf(directories: MediaDirectory[]): number[] {\n return directories.flatMap((directory) => [directory.id, ...keysOf(directory.children ?? [])])\n}\n\nfunction onDrop(event: TreeDropEvent<Node>): void {\n const parent = event.zone === 'inside' ? event.target : event.parent\n\n if (parent) {\n emit('move', event.node.id, parent.id)\n }\n}\n</script>\n\n<template>\n <wx-tree\n v-model:selected=\"selected\"\n v-model:expanded=\"expanded\"\n v-model=\"nodes\"\n class=\"wx-media-tree\"\n node-key=\"id\"\n draggable\n show-lines\n @drop=\"onDrop\"\n />\n</template>\n\n<style>\n.wx-media-tree {\n min-width: 0;\n overflow: auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { toast, WxEmpty, WxFileCard, WxSelectionArea } from '@webx-ui/core'\nimport type { MediaApi } from './api'\nimport type { MediaFile } from './types'\n\n/**\n * The files, as cards, with a rubber band over them.\n *\n * Previews come from the server's thumbnail endpoint rather than the files themselves: a grid of\n * forty photographs is forty full-size images otherwise, and on a phone that is the difference\n * between a screen and a wait.\n */\nconst props = defineProps<{\n files: MediaFile[]\n api: MediaApi\n query?: string\n /** A picker takes one file; the manager selects to act on a batch. */\n single?: boolean\n}>()\n\nconst selected = defineModel<number[]>('selected', { default: () => [] })\n\nconst emit = defineEmits<{\n open: [file: MediaFile]\n rename: [file: MediaFile, name: string]\n edit: [file: MediaFile]\n remove: [file: MediaFile]\n}>()\n\nconst t = useTranslate('webx-media')\n\nconst empty = computed(() =>\n props.query ? t('manager.empty-search', { query: props.query }) : t('manager.empty'),\n)\n\nfunction copied(): void {\n toast.success(t('manager.link-copied'))\n}\n\n/**\n * The clipboard is not always there: it is missing outside a secure context, and an iframe has\n * to be granted it. Rather than let the card fail silently, try the old way — and if that is\n * refused too, put the address on screen, where it can at least be copied by hand.\n */\nfunction copyFailed(file: MediaFile): void {\n const area = document.createElement('textarea')\n area.value = file.url\n area.setAttribute('readonly', '')\n area.style.position = 'fixed'\n area.style.opacity = '0'\n document.body.appendChild(area)\n area.select()\n\n let done = false\n\n try {\n done = document.execCommand('copy')\n } catch {\n done = false\n }\n\n area.remove()\n\n if (done) {\n copied()\n\n return\n }\n\n toast.danger(`${t('errors.copy')} ${file.url}`, { duration: 0 })\n}\n</script>\n\n<template>\n <wx-selection-area\n v-if=\"files.length > 0\"\n v-slot=\"{ isSelected }\"\n v-model=\"selected\"\n class=\"wx-media-grid\"\n :multiple=\"!single\"\n >\n <wx-file-card\n v-for=\"file in files\"\n :key=\"file.id\"\n v-wx-select=\"file.id\"\n class=\"wx-media-grid__card\"\n :name=\"file.name\"\n :url=\"file.url\"\n :thumbnail=\"api.thumb(file, 320, 320) ?? undefined\"\n :type=\"file.mime\"\n :selected=\"isSelected(file.id)\"\n renamable\n :editable=\"file.editable\"\n removable\n copyable\n actions-menu\n :rename-label=\"t('manager.rename')\"\n :edit-label=\"t('manager.edit')\"\n :remove-label=\"t('manager.delete')\"\n :remove-confirm-text=\"t('dialogs.delete-file', { name: file.name })\"\n :cancel-label=\"t('manager.cancel')\"\n :save-label=\"t('manager.save')\"\n :download-url=\"file.source ?? file.url\"\n :copy-label=\"t('manager.copy-link')\"\n :download-label=\"t('manager.download')\"\n :copied-label=\"t('manager.link-copied')\"\n @rename=\"(name) => emit('rename', file, name)\"\n @edit=\"emit('edit', file)\"\n @remove=\"emit('remove', file)\"\n @copy=\"copied\"\n @copy-error=\"copyFailed(file)\"\n @dblclick=\"emit('open', file)\"\n />\n </wx-selection-area>\n\n <wx-empty v-else :description=\"empty\" />\n</template>\n\n<style>\n.wx-media-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n gap: var(--wx-space-12);\n align-content: start;\n min-height: 0;\n overflow: auto;\n padding: var(--wx-space-2);\n /* A rubber band over names would otherwise select the names. */\n user-select: none;\n}\n\n.wx-media--compact .wx-media-grid {\n grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));\n gap: var(--wx-space-8);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { WxAction, WxActions, WxDropdown, WxDropdownItem, WxInput } from '@webx-ui/core'\nimport type { MediaKind } from './types'\n\n/**\n * One row of icons over the files.\n *\n * Actions rather than labelled buttons: the row has to survive a phone and a picker dialog half\n * the width of a window, and a row of words does not. What each icon means is a tooltip away,\n * and what is chosen is said in the icon's own label.\n */\nconst props = defineProps<{\n canUpload: boolean\n canManage: boolean\n selected: number\n /** The caller asked for one kind of file, so there is nothing to filter by. */\n fixedType?: boolean\n /** Narrow screens get the folders behind a button instead of beside the files. */\n compact: boolean\n}>()\n\nconst search = defineModel<string>('search', { default: '' })\nconst type = defineModel<MediaKind | 'all'>('type', { default: 'all' })\nconst sort = defineModel<string>('sort', { default: '-created_at' })\n\nconst emit = defineEmits<{\n upload: []\n move: []\n remove: []\n folders: []\n}>()\n\nconst t = useTranslate('webx-media')\n\nconst types = computed<{ value: MediaKind | 'all'; label: string }[]>(() => [\n { value: 'all', label: t('manager.all-types') },\n ...(['image', 'video', 'audio', 'document', 'other'] as MediaKind[]).map((kind) => ({\n value: kind,\n label: t(`manager.${kind}`),\n })),\n])\n\nconst sorts = computed(() => [\n { value: '-created_at', label: t('manager.sort-newest') },\n { value: 'created_at', label: t('manager.sort-oldest') },\n { value: 'name', label: t('manager.sort-name') },\n { value: '-size', label: t('manager.sort-size') },\n])\n\nconst typeLabel = computed(() => types.value.find((one) => one.value === type.value)?.label ?? '')\nconst sortLabel = computed(() => sorts.value.find((one) => one.value === sort.value)?.label ?? '')\n</script>\n\n<template>\n <header class=\"wx-media-toolbar\">\n <!--\n `sidebar`, the same icon the shell shows and hides its own column with: the gesture is\n the same one, so the sign is the same one. A folder would only say \"folder\", which is\n also what the button further along this row would be saying.\n -->\n <wx-action\n v-if=\"compact\"\n icon=\"sidebar\"\n :title=\"t('manager.folders')\"\n size=\"sm\"\n @click=\"emit('folders')\"\n />\n\n <wx-input\n v-model=\"search\"\n class=\"wx-media-toolbar__search\"\n :placeholder=\"t('manager.search')\"\n clearable\n size=\"sm\"\n />\n\n <wx-dropdown v-if=\"!fixedType\">\n <template #trigger>\n <wx-action icon=\"filter\" :title=\"`${t('manager.all-types')}: ${typeLabel}`\" size=\"sm\" />\n </template>\n\n <wx-dropdown-item\n v-for=\"option in types\"\n :key=\"String(option.value)\"\n :active=\"option.value === type\"\n @click=\"type = option.value\"\n >\n {{ option.label }}\n </wx-dropdown-item>\n </wx-dropdown>\n\n <wx-dropdown>\n <template #trigger>\n <!-- Not `type=\"sort\"`: in this set that means reordering by hand, and its icon is a drag handle. -->\n <wx-action icon=\"arrow-down\" :title=\"sortLabel\" size=\"sm\" />\n </template>\n\n <wx-dropdown-item\n v-for=\"option in sorts\"\n :key=\"option.value\"\n :active=\"option.value === sort\"\n @click=\"sort = option.value\"\n >\n {{ option.label }}\n </wx-dropdown-item>\n </wx-dropdown>\n\n <wx-action\n v-if=\"canUpload\"\n type=\"upload\"\n :title=\"t('manager.upload')\"\n size=\"sm\"\n @click=\"emit('upload')\"\n />\n\n <!-- Kept in the row rather than added to it: the icons are always here and simply cannot\n be used while nothing is selected, so the grid never moves down a line. -->\n <wx-actions v-if=\"canManage\" size=\"sm\">\n <wx-action\n icon=\"folder-move\"\n :title=\"t('manager.move')\"\n :disabled=\"props.selected === 0\"\n @click=\"emit('move')\"\n />\n <wx-action\n type=\"remove\"\n :title=\"t('manager.delete')\"\n :disabled=\"props.selected === 0\"\n @click=\"emit('remove')\"\n />\n </wx-actions>\n </header>\n</template>\n\n<style>\n.wx-media-toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-6);\n}\n\n.wx-media-toolbar__search {\n flex: 1 1 180px;\n min-width: 120px;\n}\n\n.wx-media-toolbar__count {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n margin-inline-start: auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxSelect, WxSpace } from '@webx-ui/core'\nimport type { MediaDirectory } from './types'\n\n/**\n * Where to put the selected files.\n *\n * A list of folders rather than a box to type in: an editor knows a folder by its name and its\n * place in the tree, and has no reason to ever learn that it also has a number.\n */\nconst props = defineProps<{\n directories: MediaDirectory[]\n /** The folder they are in now, which is not worth offering. */\n from?: number | null\n count: number\n}>()\n\nconst { open, resolve, dismiss } = useModal<number>()\n\nconst t = useTranslate('webx-media')\n\nconst options = computed(() =>\n flatten(props.directories).filter((option) => option.value !== props.from),\n)\nconst target = ref<number | null>(options.value[0]?.value ?? null)\n\nfunction flatten(directories: MediaDirectory[]): { value: number; label: string }[] {\n return directories.flatMap((directory) => [\n {\n value: directory.id,\n // Indented, so the tree is still readable once it is a flat list.\n label:\n ' '.repeat(directory.depth * 3) + (directory.is_root ? t('manager.root') : directory.title),\n },\n ...flatten(directory.children ?? []),\n ])\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('manager.move-to')\" :width=\"420\">\n <wx-select v-model=\"target\" :options=\"options\" />\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('manager.cancel') }}</wx-button>\n <wx-button type=\"primary\" :disabled=\"target === null\" @click=\"resolve(target!)\">\n {{ t('manager.move') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxInput, WxSpace } from '@webx-ui/core'\n\n/**\n * A name, asked for properly.\n *\n * `window.prompt` is not an option: a browser is free to refuse it — an embedded frame usually\n * does — and then the button simply does nothing, which is exactly how \"new folder\" looked.\n */\nconst props = withDefaults(\n defineProps<{\n title: string\n label?: string\n value?: string\n confirmText?: string\n }>(),\n { label: undefined, value: '', confirmText: undefined },\n)\n\nconst { open, resolve, dismiss } = useModal<string>()\n\nconst t = useTranslate('webx-media')\n\nconst name = ref(props.value)\nconst input = useTemplateRef<HTMLElement>('input')\n\nonMounted(async () => {\n await nextTick()\n input.value?.querySelector('input')?.focus()\n})\n\nfunction submit(): void {\n const value = name.value.trim()\n\n if (value !== '') {\n resolve(value)\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"title\" :width=\"420\">\n <div ref=\"input\">\n <!-- The dialog's title says what is being named, so the field carries the same words as a\n placeholder and as its accessible name rather than repeating them above itself. -->\n <wx-input\n v-model=\"name\"\n :placeholder=\"label ?? t('manager.folder-name')\"\n :aria-label=\"label ?? t('manager.folder-name')\"\n @keyup.enter=\"submit\"\n />\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('manager.cancel') }}</wx-button>\n <wx-button type=\"primary\" :disabled=\"name.trim() === ''\" @click=\"submit\">\n {{ confirmText ?? t('manager.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * What this package says, in English.\n *\n * The same keys `webx-ui/module-media` ships as `lang/en/*.php`, and the same ones every other\n * language there has. Kept here so a manager placed by hand, with no WebX UI server behind it,\n * still has words — and so a key never reaches the screen when a translation is missing.\n */\nexport const mediaMessages: Record<string, Messages> = {\n module: {\n title: 'Files',\n },\n manager: {\n root: 'Library',\n search: 'Search files',\n 'new-folder': 'New folder',\n 'folder-name': 'Folder name',\n rename: 'Rename',\n move: 'Move',\n 'move-to': 'Move to…',\n delete: 'Delete',\n upload: 'Upload',\n 'upload-hint': 'Drop files here or choose them',\n select: 'Select',\n selected: ':count selected',\n empty: 'This folder is empty',\n 'empty-search': 'Nothing matches :query',\n 'all-types': 'All types',\n image: 'Images',\n video: 'Video',\n audio: 'Audio',\n document: 'Documents',\n other: 'Other',\n 'sort-newest': 'Newest first',\n 'sort-oldest': 'Oldest first',\n 'sort-name': 'By name',\n 'sort-size': 'Largest first',\n 'duplicate-added': 'That file was already in this folder',\n uploaded: ':count file(s) added',\n cancel: 'Cancel',\n save: 'Save',\n folders: 'Folders',\n edit: 'Edit',\n 'copy-link': 'Copy the link',\n download: 'Download',\n 'link-copied': 'Link copied',\n 'status-files': 'Files: :count',\n 'status-selected': 'selected: :count',\n 'status-size': 'size: :size',\n pick: 'Choose (:count)',\n chosen: 'Chosen :count of :max',\n },\n // The picture field on a form, which is the module used from outside its own section.\n field: {\n select: 'Choose a picture',\n edit: 'Captions',\n replace: 'Replace',\n captions: 'Captions',\n 'alt-hint': 'What is on the picture, for a reader who cannot see it',\n 'title-hint': 'A caption shown on hover',\n 'no-preview': 'No preview',\n 'clear-title': 'Clear the field?',\n 'clear-text': 'The file stays in the library — only this record stops using it.',\n 'clear-confirm': 'Clear',\n add: 'Add',\n empty: 'Nothing chosen yet',\n reorder: 'Reorder',\n open: 'Open in the library',\n missing: 'No longer in the library',\n 'remove-title': 'Take it out of the list?',\n 'remove-text': 'The file stays in the library — only this record stops using it.',\n 'remove-confirm': 'Remove',\n count: ':count of :max',\n 'at-least': 'At least :count',\n },\n // The image editor is a component of the design system, so its words are props rather than\n // lines in the dictionary — the manager hands it these when it opens it.\n editor: {\n title: 'Edit picture',\n save: 'Save',\n cancel: 'Cancel',\n reset: 'Reset',\n 'rotate-left': 'Turn left',\n 'rotate-right': 'Turn right',\n 'flip-horizontal': 'Mirror across',\n 'flip-vertical': 'Mirror down',\n ratio: 'Ratio',\n crop: 'Crop',\n output: 'Output',\n 'output-hint': 'The size of the picture you will get',\n width: 'Width',\n height: 'Height',\n free: 'Free',\n original: 'Original',\n error: 'This picture could not be loaded',\n },\n dialogs: {\n 'delete-files-title': 'Delete :count file(s)?',\n 'delete-files-text': 'Anywhere they are already used, they will stop opening.',\n 'delete-folder-title': 'Delete the folder :title?',\n 'delete-folder-text': 'It goes for good, and so does whatever is inside it.',\n 'delete-folder-contents': 'It holds :files file(s) and :directories folder(s).',\n 'delete-folder-warning':\n 'Everything inside goes too, and pictures already placed in content will stop opening.',\n confirm: 'Delete',\n 'delete-file': 'Delete :name?',\n },\n errors: {\n 'directory-not-empty': 'This folder is not empty.',\n 'root-immutable': 'The library root cannot be moved or deleted.',\n 'directory-into-itself': 'A folder cannot be moved into itself.',\n 'not-an-image': 'This file is not an image.',\n 'image-too-large': 'This image is larger than :megapixels megapixels.',\n upload: 'That file was not accepted.',\n copy: 'The clipboard is not available here. The address:',\n 'unsupported-type': 'Only these can be uploaded: :types.',\n 'file-too-large': 'The file is larger than :size MB.',\n 'too-many-files': 'No more than :count files at a time.',\n 'file-not-found': 'There is no such file in the library.',\n },\n files: {\n copy: ':name (copy)',\n },\n validation: {\n title: 'name',\n parent_id: 'parent folder',\n directory_id: 'folder',\n files: 'files',\n item: 'File :number: :message',\n shape: 'This is not a file from the library.',\n accept: 'This field takes :kind only.',\n localized: 'A field of files cannot be translated — the captions inside it are.',\n kind: {\n image: 'images',\n video: 'video',\n audio: 'audio',\n document: 'documents',\n other: 'other files',\n },\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { mediaMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/**\n * Put this package's English under the panel's dictionary, once per panel.\n *\n * The server's translations land on top of it when they arrive. What this prevents is the half\n * second — or the whole session, in a panel assembled without a server — where a screen shows\n * `manager.upload` instead of a word.\n */\nexport function useMediaMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-media', mediaMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n openImageEditor,\n toast,\n useElementWidth,\n WxAction,\n WxActions,\n WxDrawer,\n WxPagination,\n} from '@webx-ui/core'\nimport DirectoryTree from './DirectoryTree.vue'\nimport FileGrid from './FileGrid.vue'\nimport MediaToolbar from './MediaToolbar.vue'\nimport MoveDialog from './MoveDialog.vue'\nimport NameDialog from './NameDialog.vue'\nimport { createMediaApi } from './api'\nimport { readable } from './format'\nimport { useMediaMessages } from './i18n'\nimport type { MediaDirectory, MediaFile, MediaKind, MediaPage } from './types'\n\n/**\n * The library: folders on the left, files on the right, a row of icons over both.\n *\n * Everything the screen does is one request and a reload of the part that changed — there is no\n * cache here on purpose. A library is edited by several people, and a panel that believes its\n * own copy shows a folder that was emptied an hour ago.\n *\n * The width that decides the layout is this component's own, not the window's: the same manager\n * is a page, half of a picker dialog, and a phone.\n */\nconst props = withDefaults(\n defineProps<{\n /** A picker hands one file back instead of managing the library. */\n picking?: boolean\n /** Only these kinds can be chosen, when picking. */\n accept?: MediaKind | null\n /**\n * A picker that takes several files at once.\n *\n * What this switches on is the selection the manager already has for its batch operations:\n * the rubber band, the tap that toggles, the run held with shift. A picker with a way of\n * marking files of its own would be a second thing to learn for the very same job.\n */\n multiple?: boolean\n /** The most that may be held at once, when picking several. */\n max?: number | null\n /**\n * Drawn as a section of the panel rather than inside a picker.\n *\n * The screen around it then carries the two things a section carries — the main action and\n * the views of the list — so the toolbar stops offering an upload icon and a kind filter\n * of its own. In a dialog there is no screen around it, and it keeps both.\n */\n inPage?: boolean\n }>(),\n { picking: false, accept: null, multiple: false, max: null, inPage: false },\n)\n\nconst emit = defineEmits<{ pick: [file: MediaFile]; selection: [files: MediaFile[]] }>()\n\nconst admin = useAdmin()\nconst api = createMediaApi(admin)\nuseMediaMessages()\n\nconst t = useTranslate('webx-media')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\n/**\n * The image editor's words, in the language the panel is being read in.\n *\n * Read when the editor is opened rather than kept in a computed: the dialog is mounted outside\n * the app, so it is handed plain strings once and nothing re-renders it afterwards.\n */\nfunction editorLabels(): Record<string, string> {\n return {\n title: t('editor.title'),\n saveLabel: t('editor.save'),\n cancelLabel: t('editor.cancel'),\n resetLabel: t('editor.reset'),\n rotateLeftLabel: t('editor.rotate-left'),\n rotateRightLabel: t('editor.rotate-right'),\n flipHorizontalLabel: t('editor.flip-horizontal'),\n flipVerticalLabel: t('editor.flip-vertical'),\n ratioLabel: t('editor.ratio'),\n cropLabel: t('editor.crop'),\n outputLabel: t('editor.output'),\n outputHint: t('editor.output-hint'),\n widthLabel: t('editor.width'),\n heightLabel: t('editor.height'),\n freeLabel: t('editor.free'),\n originalLabel: t('editor.original'),\n errorText: t('editor.error'),\n }\n}\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\nconst compact = computed(() => width.value > 0 && width.value < 640)\n\nconst directories = ref<MediaDirectory[]>([])\nconst current = ref<number | null>(null)\nconst page = ref<MediaPage | null>(null)\nconst selected = ref<number[]>([])\nconst search = ref('')\n/*\n * What the caller asked for is not a default the person can put back: a field that wants a\n * picture would otherwise be handed a PDF, and find out when the page renders.\n */\nconst type = defineModel<MediaKind | 'all'>('type', { default: 'all' })\n\nif (props.accept) type.value = props.accept\n\nwatch(\n () => props.accept,\n (kind) => {\n type.value = kind ?? 'all'\n },\n)\nconst sort = ref('-created_at')\nconst busy = ref(false)\nconst foldersOpen = ref(false)\nconst picker = useTemplateRef<HTMLInputElement>('picker')\n\nconst rows = computed(() => page.value?.data ?? [])\nconst canManage = computed(() => admin.can('media.manage'))\nconst canUpload = computed(() => admin.can('media.upload') || canManage.value)\nconst folder = computed(() => find(current.value))\n/** Picking one file is the one case the band is off for; the manager itself always has it. */\nconst picksOne = computed(() => props.picking && !props.multiple)\n\n/**\n * The limit belongs to the field, so the dialog keeps to it.\n *\n * Trimmed rather than refused: what the person sees is that the grid stops taking more, and the\n * count under it says how far they have got. Finding out on save that a field takes ten is not\n * a limit, it is a rejected form.\n */\nwatch(selected, (ids) => {\n if (props.max !== null && props.max > 0 && ids.length > props.max) {\n selected.value = ids.slice(0, props.max)\n\n return\n }\n\n // In the order they are on screen, not the order they were clicked in: what comes back is a\n // gallery, and its order is the one the person was looking at.\n emit(\n 'selection',\n rows.value.filter((file) => ids.includes(file.id)),\n )\n})\n\nonMounted(load)\n\nwatch([current, type, sort], () => loadFiles(1))\nwatch(\n search,\n debounce(() => loadFiles(1), 300),\n)\nwatch(compact, (narrow) => {\n if (!narrow) {\n foldersOpen.value = false\n }\n})\n\nasync function load(): Promise<void> {\n directories.value = await api.directories()\n current.value ??= directories.value[0]?.id ?? null\n await loadFiles(1)\n}\n\nasync function loadFiles(to = page.value?.meta.current_page ?? 1): Promise<void> {\n busy.value = true\n\n try {\n page.value = await api.files({\n directory_id: search.value ? null : current.value,\n q: search.value,\n type: type.value === 'all' ? null : type.value,\n sort: sort.value,\n page: to,\n })\n selected.value = []\n } finally {\n busy.value = false\n }\n}\n\nfunction choose(): void {\n picker.value?.click()\n}\n\n/**\n * Uploading says what happened in a toast and nowhere else.\n *\n * A list of what was just added under the toolbar is a second place to look and a thing to\n * dismiss; the files themselves appear in the grid a moment later, which is the answer.\n */\nasync function upload(event: Event): Promise<void> {\n const input = event.target as HTMLInputElement\n const chosen = Array.from(input.files ?? [])\n\n // Cleared straight away, so choosing the same file twice in a row still counts as a change.\n input.value = ''\n\n if (chosen.length === 0 || current.value === null) {\n return\n }\n\n try {\n const stored = await api.upload(current.value, chosen)\n const duplicates = stored.filter((file) => file.duplicate).length\n\n if (stored.length > duplicates) {\n toast.success(t('manager.uploaded', { count: stored.length - duplicates }))\n }\n\n if (duplicates > 0) {\n toast.info(t('manager.duplicate-added'))\n }\n\n await Promise.all([load(), loadFiles(1)])\n } catch (error) {\n toast.danger(message(error, t('errors.upload')))\n }\n}\n\nconst askName = createModal<\n string,\n { title: string; label?: string; value?: string; confirmText?: string }\n>(NameDialog)\n\nasync function createFolder(): Promise<void> {\n const title = await askName({\n title: t('manager.new-folder'),\n confirmText: t('manager.new-folder'),\n })\n\n if (title === undefined || current.value === null) {\n return\n }\n\n await api.createDirectory(current.value, title)\n await load()\n}\n\nasync function renameFolder(): Promise<void> {\n const target = folder.value\n\n if (!target || target.is_root) {\n return\n }\n\n const title = await askName({\n title: t('manager.rename'),\n value: target.title,\n confirmText: t('manager.save'),\n })\n\n if (title !== undefined) {\n await api.renameDirectory(target.id, title)\n await load()\n }\n}\n\nasync function deleteFolder(): Promise<void> {\n const target = folder.value\n\n if (!target || target.is_root) {\n return\n }\n\n // Asked before anything is tried, because a folder is a folder whether or not it holds\n // anything (§14.2) — and asked a second time, with the counts the server sent back, when it\n // turns out that what goes with it is somebody's article illustrations.\n const first = await confirm({\n title: t('dialogs.delete-folder-title', { title: target.title }),\n message: t('dialogs.delete-folder-text'),\n confirmText: t('dialogs.confirm'),\n cancelText: t('manager.cancel'),\n tone: 'danger',\n })\n\n if (!first) {\n return\n }\n\n try {\n await api.deleteDirectory(target.id)\n } catch (error) {\n const counts = countsOf(error)\n\n if (!counts) {\n toast.danger(message(error))\n\n return\n }\n\n const agreed = await confirm({\n title: t('dialogs.delete-folder-title', { title: target.title }),\n message: `${t('dialogs.delete-folder-contents', counts)} ${t('dialogs.delete-folder-warning')}`,\n confirmText: t('dialogs.confirm'),\n cancelText: t('manager.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) {\n return\n }\n\n await api.deleteDirectory(target.id, true)\n }\n\n current.value = null\n await load()\n}\n\nasync function moveFolder(id: number, parentId: number): Promise<void> {\n try {\n await api.moveDirectory(id, parentId)\n } catch (error) {\n toast.danger(message(error, t('errors.directory-into-itself')))\n }\n\n await load()\n}\n\nconst askWhereTo = createModal<\n number,\n { directories: MediaDirectory[]; from: number | null; count: number }\n>(MoveDialog)\n\nasync function moveSelected(): Promise<void> {\n const to = await askWhereTo({\n directories: directories.value,\n from: current.value,\n count: selected.value.length,\n })\n\n if (to === undefined) {\n return\n }\n\n await api.move([...selected.value], to)\n await Promise.all([load(), loadFiles()])\n}\n\nasync function removeSelected(): Promise<void> {\n const ids = [...selected.value]\n\n const agreed = await confirm({\n title: t('dialogs.delete-files-title', { count: ids.length }),\n message: t('dialogs.delete-files-text'),\n confirmText: t('dialogs.confirm'),\n cancelText: t('manager.cancel'),\n tone: 'danger',\n })\n\n if (agreed) {\n await api.remove(ids)\n await Promise.all([load(), loadFiles()])\n }\n}\n\nasync function rename(file: MediaFile, name: string): Promise<void> {\n await api.rename(file.id, name)\n await loadFiles()\n}\n\nasync function remove(file: MediaFile): Promise<void> {\n await api.removeOne(file.id)\n await Promise.all([load(), loadFiles()])\n}\n\n/**\n * The editor works on a preview and hands back a blob; the server is told the operations\n * instead and applies them to the original, at full size.\n *\n * Filters and adjustments are switched off for the same reason: they cannot be said as an\n * operation, and a picture that is quietly smaller than it was is worse than a button that is\n * not there.\n */\nasync function edit(file: MediaFile): Promise<void> {\n // `source`, not `url`: the editor draws the picture onto a canvas and writes that canvas out,\n // and a browser refuses to do that for bytes from another origin without CORS headers — which\n // is exactly what a CDN in front of the library is. `url` stays what everything that only\n // looks at the picture uses.\n const result = await openImageEditor({\n src: file.source ?? file.url,\n fileName: file.file_name,\n filters: false,\n // The editor is a component of the design system: its words are props with English\n // defaults, not lines in the panel's dictionary. Whoever opens it is the one who knows\n // what language the panel is being read in.\n ...editorLabels(),\n })\n\n if (!result) {\n return\n }\n\n await api.edit(file.id, {\n // The editor's frame is read against the picture as it is on screen — already turned — and\n // the server applies the operations in that order for the same reason.\n crop: result.crop,\n rotate: (((result.rotation % 360) + 360) % 360) as 0 | 90 | 180 | 270,\n flip: result.flipX ? 'horizontal' : result.flipY ? 'vertical' : undefined,\n resize: { width: result.width, height: result.height },\n })\n\n // Both addresses — the picture and its preview — carry the new version, so what is on screen\n // a moment later is what was just saved rather than what the browser kept.\n await loadFiles()\n}\n\n/**\n * A double-click means \"this one, now\".\n *\n * While several are being picked it means nothing extra: the first click of it has already put\n * the file in the selection, and the dialog ends on its own button instead.\n */\nfunction open(file: MediaFile): void {\n if (!props.multiple) {\n emit('pick', file)\n }\n}\n\nfunction find(id: number | null): MediaDirectory | null {\n const walk = (nodes: MediaDirectory[]): MediaDirectory | null => {\n for (const node of nodes) {\n if (node.id === id) {\n return node\n }\n\n const deeper = walk(node.children ?? [])\n\n if (deeper) {\n return deeper\n }\n }\n\n return null\n }\n\n return id === null ? null : walk(directories.value)\n}\n\nfunction countsOf(error: unknown): { files: number; directories: number } | null {\n const body = (\n error as { body?: { code?: string; counts?: { files: number; directories: number } } }\n )?.body\n\n return body?.code === 'directory_not_empty' ? (body.counts ?? null) : null\n}\n\n/* The page's own upload button opens the same file dialog the toolbar's icon does. */\ndefineExpose({ upload: choose })\n\nfunction debounce(run: () => void, wait: number): () => void {\n let timer: ReturnType<typeof setTimeout> | undefined\n\n return () => {\n clearTimeout(timer)\n timer = setTimeout(run, wait)\n }\n}\n</script>\n\n<template>\n <div ref=\"root\" class=\"wx-media\" :class=\"{ 'wx-media--compact': compact }\">\n <aside v-if=\"!compact\" class=\"wx-media__folders\">\n <directory-tree v-model:selected=\"current\" :directories=\"directories\" @move=\"moveFolder\" />\n\n <wx-actions v-if=\"canManage\" size=\"sm\" align=\"start\">\n <wx-action type=\"add\" :title=\"t('manager.new-folder')\" @click=\"createFolder\" />\n <wx-action\n type=\"edit\"\n :title=\"t('manager.rename')\"\n :disabled=\"!folder || folder.is_root\"\n @click=\"renameFolder\"\n />\n <wx-action\n type=\"remove\"\n :title=\"t('manager.delete')\"\n :disabled=\"!folder || folder.is_root\"\n @click=\"deleteFolder\"\n />\n </wx-actions>\n </aside>\n\n <section class=\"wx-media__files\">\n <!--\n Uploading is offered while picking too. Most of the time the picture somebody is looking\n for is the one on their desk, and a picker that can only choose from what is already\n there sends them off to the library, to the upload, and back to a form they then have to\n find again.\n -->\n <media-toolbar\n v-model:search=\"search\"\n v-model:type=\"type\"\n v-model:sort=\"sort\"\n :can-upload=\"canUpload && !inPage\"\n :fixed-type=\"accept !== null || inPage\"\n :can-manage=\"canManage\"\n :selected=\"selected.length\"\n :compact=\"compact\"\n @upload=\"choose\"\n @move=\"moveSelected\"\n @remove=\"removeSelected\"\n @folders=\"foldersOpen = true\"\n />\n\n <!--\n `accept` here is a hint to the file dialog, not a rule: what may be uploaded at all is\n the server's list of extensions, and it says so in a language the person reads.\n -->\n <input\n ref=\"picker\"\n type=\"file\"\n multiple\n hidden\n :accept=\"accept === null ? undefined : `${accept}/*`\"\n @change=\"upload\"\n />\n\n <file-grid\n v-model:selected=\"selected\"\n :files=\"rows\"\n :api=\"api\"\n :query=\"search\"\n :single=\"picksOne\"\n @rename=\"rename\"\n @edit=\"edit\"\n @remove=\"remove\"\n @open=\"open\"\n />\n\n <footer class=\"wx-media__status\">\n <span>{{ t('manager.status-files', { count: page?.stats.files ?? 0 }) }}</span>\n <span v-if=\"selected.length > 0\">\n {{ t('manager.status-selected', { count: selected.length }) }}\n </span>\n <span>{{ t('manager.status-size', { size: readable(page?.stats.size ?? 0) }) }}</span>\n </footer>\n\n <wx-pagination\n v-if=\"page && page.meta.last_page > 1\"\n :page=\"page.meta.current_page\"\n :per-page=\"page.meta.per_page\"\n :total=\"page.meta.total\"\n :last-page=\"page.meta.last_page\"\n :disabled=\"busy\"\n size=\"sm\"\n @change=\"({ page: to }) => loadFiles(to)\"\n />\n </section>\n\n <!-- On a narrow screen the folders are a drawer: a tree beside a grid leaves room for\n neither, and an editor on a phone is looking for one thing at a time. -->\n <wx-drawer v-model:open=\"foldersOpen\" side=\"left\" :title=\"t('manager.folders')\" :size=\"280\">\n <div class=\"wx-media__drawer\">\n <directory-tree\n v-model:selected=\"current\"\n :directories=\"directories\"\n @move=\"moveFolder\"\n @update:selected=\"foldersOpen = false\"\n />\n\n <wx-actions v-if=\"canManage\" size=\"sm\" align=\"start\">\n <wx-action type=\"add\" :title=\"t('manager.new-folder')\" @click=\"createFolder\" />\n <wx-action\n type=\"edit\"\n :title=\"t('manager.rename')\"\n :disabled=\"!folder || folder.is_root\"\n @click=\"renameFolder\"\n />\n <wx-action\n type=\"remove\"\n :title=\"t('manager.delete')\"\n :disabled=\"!folder || folder.is_root\"\n @click=\"deleteFolder\"\n />\n </wx-actions>\n </div>\n </wx-drawer>\n </div>\n</template>\n\n<style>\n.wx-media {\n display: grid;\n grid-template-columns: minmax(180px, 240px) minmax(0, 1fr);\n gap: var(--wx-gap, var(--wx-space-16));\n min-height: 0;\n height: 100%;\n}\n\n.wx-media--compact {\n grid-template-columns: minmax(0, 1fr);\n gap: var(--wx-space-8);\n}\n\n.wx-media__folders {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n min-height: 0;\n border-right: 1px solid var(--wx-border-muted);\n padding-right: var(--wx-space-12);\n}\n\n/* The drawer hands its content the edge of the screen; the tree and its actions want air. */\n.wx-media__drawer {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-10);\n padding-block: var(--wx-space-4);\n}\n\n.wx-media__status {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n border-top: 1px solid var(--wx-border-muted);\n padding-top: var(--wx-space-6);\n}\n\n.wx-media__files {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-10);\n min-height: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxSpace } from '@webx-ui/core'\nimport MediaManager from './MediaManager.vue'\nimport type { MediaFile, MediaKind } from './types'\n\n/**\n * The library as a dialog, for a form that needs a picture.\n *\n * The same manager, not a second one: a picker that browses differently from the manager is a\n * second thing to learn and a second thing to keep working.\n */\nwithDefaults(\n defineProps<{\n accept?: MediaKind | null\n title?: string\n /** Opened to manage the library rather than to choose from it. */\n manage?: boolean\n /** Several files at once, handed back as a list. */\n multiple?: boolean\n /** The most that may be chosen, when choosing several. */\n max?: number | null\n }>(),\n { accept: 'image', title: undefined, manage: false, multiple: false, max: null },\n)\n\nconst { open, resolve, dismiss } = useModal<MediaFile | MediaFile[]>()\n\nconst t = useTranslate('webx-media')\n\n/**\n * What is marked in the grid right now.\n *\n * A dialog that takes several files needs a button to end on, and the button needs to say how\n * many it would hand back — a double-click cannot mean \"these ten\".\n */\nconst chosen = ref<MediaFile[]>([])\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"title ?? t('module.title')\" :width=\"960\">\n <div class=\"wx-media-picker\">\n <media-manager\n :picking=\"!manage\"\n :accept=\"manage ? null : accept\"\n :multiple=\"multiple && !manage\"\n :max=\"max\"\n @pick=\"(file) => resolve(file)\"\n @selection=\"(files) => (chosen = files)\"\n />\n </div>\n\n <template v-if=\"multiple && !manage\" #footer>\n <wx-space size=\"sm\">\n <!-- The count is what tells somebody they have reached the limit: the grid stops\n taking more, and this is where it says so. -->\n <span v-if=\"max !== null && max > 0\" class=\"wx-media-picker__count\">\n {{ t('manager.chosen', { count: chosen.length, max }) }}\n </span>\n\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('manager.cancel') }}</wx-button>\n <wx-button\n class=\"wx-media-picker__confirm\"\n type=\"primary\"\n :disabled=\"chosen.length === 0\"\n @click=\"resolve(chosen)\"\n >\n {{ t('manager.pick', { count: chosen.length }) }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style>\n.wx-media-picker {\n height: min(70vh, 560px);\n min-height: 0;\n}\n\n.wx-media-picker__count {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n align-self: center;\n}\n</style>\n","import { createModal } from '@webx-ui/core'\nimport MediaPicker from './MediaPicker.vue'\nimport type { MediaFile, MediaKind } from './types'\n\nexport interface MediaPickerOptions {\n /** Narrow what can be chosen; `null` offers everything. */\n accept?: MediaKind | null\n title?: string\n}\n\nexport interface MediaFilesOptions extends MediaPickerOptions {\n /**\n * The most that may be chosen at once.\n *\n * The dialog keeps to it, and so does the server: the limit lives in the field's schema, and\n * a request that never opened a dialog has to meet it too.\n */\n max?: number | null\n}\n\n/**\n * The library, opened from code, resolving with the file that was chosen.\n *\n * A form does not need to own a dialog for this — `const file = await openMediaPicker()` is the\n * whole integration, and `undefined` means the person closed it.\n */\nexport const openMediaPicker = createModal<MediaFile, MediaPickerOptions>(MediaPicker)\n\n/**\n * The same library, resolving with everything that was chosen.\n *\n * A second function rather than a flag on the first one, because the answer is a different\n * shape: `MediaFile[]` here against `MediaFile` there, said in the signature instead of in a\n * conditional type nobody can read. `undefined` still means the dialog was closed.\n */\nexport const openMediaFiles = createModal<MediaFile[], MediaFilesOptions>(MediaPicker, {\n props: { multiple: true },\n})\n\n/**\n * The whole library as a dialog, from anywhere.\n *\n * The same manager, the same folders, the same uploads — for a screen that needs to put files in\n * order without navigating away from what it was doing. It resolves when a file is opened, and\n * with `undefined` when the dialog is simply closed.\n */\nexport const openMediaLibrary = createModal<MediaFile, Omit<MediaPickerOptions, 'accept'>>(\n MediaPicker,\n { props: { manage: true } },\n)\n","<script setup lang=\"ts\">\nimport { computed, inject, ref, watch } from 'vue'\nimport { adminKey, useTranslate, type AdminContext } from '@webx-ui/module-admin'\nimport {\n WxAction,\n WxActions,\n WxButton,\n WxFileCard,\n WxInput,\n WxPopconfirm,\n WxPopover,\n WxSortableList,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { createMediaApi, type MediaApi } from './api'\nimport { nameFromPath, readable } from './format'\nimport { openMediaFiles, openMediaLibrary } from './openMediaPicker'\nimport { useMediaMessages } from './i18n'\nimport type { MediaAspect, MediaFile, MediaKind, MediaValue } from './types'\n\n/**\n * Several files on a form, in an order somebody chose.\n *\n * One component under `wx-gallery`, `wx-file` and `wx-files`: what differs between them is a\n * layout and a limit, and everything else — picking, ordering, captions, the card of a file\n * that is no longer there — is the same work. The three names exist for the person choosing a\n * field type, not for the code.\n *\n * What is stored is `{ path, alt?, title? }` per item and the order they are in. Everything\n * else on screen — the preview, the name, the size — is asked of the library on every read,\n * because the address of a file is not a thing worth writing into a thousand records.\n */\nconst props = withDefaults(\n defineProps<{\n label?: string\n /** Cards side by side for pictures, a line each for documents. */\n layout?: 'grid' | 'rows'\n /** Narrow what the library offers. The server checks the same thing on the way in. */\n accept?: MediaKind | null\n /** Off where a caption is noise — a row of logos, a set of downloads. */\n captions?: boolean\n max?: number | null\n min?: number | null\n /** A fixed number of columns instead of as many as fit. */\n columns?: number | null\n /** Shape of a thumbnail in the grid: a preset, or anything CSS reads as a ratio. */\n aspect?: MediaAspect | null\n /** One file, kept as a list of one — what `wx-file` is. */\n single?: boolean\n disabled?: boolean\n }>(),\n {\n label: undefined,\n layout: 'grid',\n accept: null,\n captions: true,\n max: null,\n min: null,\n columns: null,\n aspect: null,\n single: false,\n disabled: false,\n },\n)\n\nconst model = defineModel<MediaValue[]>({ default: () => [] })\n\nuseMediaMessages()\n\nconst t = useTranslate('webx-media')\n\n/*\n * The panel, when there is one. A field has to draw itself without it — a demo page or a test\n * has no library to ask — so this is `inject` rather than `useAdmin()`, which throws.\n */\nconst admin: AdminContext | null = inject(adminKey, null)\nconst api: MediaApi | null = admin ? createMediaApi(admin) : null\n\n/** The presets, spelled the way CSS wants them. Anything else is passed through as given. */\nconst RATIOS: Record<string, string> = {\n '16/9': '16 / 9',\n '4/3': '4 / 3',\n '1/1': '1 / 1',\n}\n\nconst items = computed<MediaValue[]>({\n get: () => (Array.isArray(model.value) ? model.value : []),\n set: (next) => {\n model.value = next\n },\n})\n\nconst limit = computed(() => (props.single ? 1 : (props.max ?? null)))\n\nconst full = computed(() => limit.value !== null && items.value.length >= limit.value)\n\n/* ------------------------------------------------------------------ the library --- */\n\n/*\n * What the library says about each key on screen.\n *\n * A key that is not in here yet is a key nothing has been asked about — the field draws what\n * it can from the key itself. A key mapped to `null` is one the library has answered about:\n * the file is gone, and that is what the card says.\n */\nconst files = ref<Record<string, MediaFile | null>>({})\n\nconst asking = new Set<string>()\n\nasync function resolve(paths: string[]): Promise<void> {\n if (!api) return\n\n const wanted = paths.filter((path) => !(path in files.value) && !asking.has(path))\n\n if (wanted.length === 0) return\n\n wanted.forEach((path) => asking.add(path))\n\n const found = await Promise.all(\n wanted.map(async (path) => [path, await api.fileByPath(path)] as const),\n )\n\n wanted.forEach((path) => asking.delete(path))\n files.value = { ...files.value, ...Object.fromEntries(found) }\n}\n\nwatch(\n () => items.value.map((item) => item.path),\n (paths) => void resolve(paths.filter(Boolean)),\n { immediate: true },\n)\n\n/** What was just chosen is already known; asking the library about it again is a round trip. */\nfunction remember(chosen: MediaFile[]): void {\n files.value = { ...files.value, ...Object.fromEntries(chosen.map((file) => [file.path, file])) }\n}\n\nfunction fileOf(item: MediaValue): MediaFile | null | undefined {\n return files.value[item.path]\n}\n\n/** Only an answered question counts as broken: unasked is not the same as missing. */\nfunction broken(item: MediaValue): boolean {\n return fileOf(item) === null\n}\n\nfunction nameOf(item: MediaValue): string {\n return fileOf(item)?.name ?? nameFromPath(item.path)\n}\n\n/*\n * Previews come from the thumbnail the server already made, never from the file itself: a\n * gallery of forty photographs drawn from the originals is forty originals.\n */\nfunction thumbOf(item: MediaValue): string | undefined {\n const file = fileOf(item)\n\n return file && api ? (api.thumb(file, 320, 320) ?? undefined) : undefined\n}\n\nfunction metaOf(item: MediaValue): string {\n const file = fileOf(item)\n\n if (!file) return ''\n\n const size = readable(file.size)\n\n return file.width && file.height ? `${file.width}×${file.height} · ${size}` : size\n}\n\n/* ------------------------------------------------------------------ editing --- */\n\nconst editing = ref<number | null>(null)\n\nfunction keyFor(item: MediaValue, index: number): string {\n // The same file twice in one list is unusual but not forbidden, and two rows under one key\n // is a list Vue cannot tell apart.\n const first = items.value.findIndex((other) => other.path === item.path)\n\n return first === index ? item.path : `${item.path}#${index}`\n}\n\n/**\n * The captions have to be readable as a record even when what was stored is the plain string a\n * column held before the site had a second language.\n */\nfunction words(index: number, key: 'alt' | 'title'): LocalizedValue | string {\n return items.value[index]?.[key] ?? {}\n}\n\nfunction setWords(index: number, key: 'alt' | 'title', next: unknown): void {\n const current = items.value[index]\n\n if (!current) return\n\n const list = [...items.value]\n list[index] = { ...current, [key]: next as LocalizedValue }\n items.value = list\n}\n\nasync function add(): Promise<void> {\n if (props.disabled) return\n\n const room = limit.value === null ? null : limit.value - items.value.length\n const chosen = await openMediaFiles({ accept: props.accept, max: room })\n\n if (!chosen || chosen.length === 0) return\n\n remember(chosen)\n\n const known = new Set(items.value.map((item) => item.path))\n const added = chosen\n .filter((file) => !known.has(file.path))\n .map<MediaValue>((file) => ({ path: file.path }))\n\n // Only the key and the order are kept: the address is worked out on every read, and writing\n // it into the value would be a copy that goes stale the moment the library moves.\n items.value = props.single ? added.slice(0, 1) : [...items.value, ...added]\n}\n\n/** Out of this record, not out of the library — the file is very likely used elsewhere. */\nfunction remove(index: number): void {\n items.value = items.value.filter((_, at) => at !== index)\n editing.value = null\n}\n\n/**\n * The library, over the form.\n *\n * Cropping a file from here would crop it everywhere it is used, so the field does not offer\n * it — it offers the place where those consequences are visible. What comes back from that\n * dialog is ignored; what matters is that the picture may have changed, so it is asked about\n * again.\n */\nasync function openLibrary(item: MediaValue): Promise<void> {\n await openMediaLibrary({})\n\n const rest = { ...files.value }\n delete rest[item.path]\n files.value = rest\n\n void resolve([item.path])\n}\n\n/* ------------------------------------------------------------------ the frame --- */\n\nconst style = computed(() => {\n const columns = props.columns\n ? `repeat(${props.columns}, minmax(0, 1fr))`\n : 'repeat(auto-fill, minmax(140px, 1fr))'\n\n return props.aspect\n ? {\n '--wx-media-list-columns': columns,\n '--wx-media-list-aspect': RATIOS[props.aspect] ?? props.aspect,\n }\n : { '--wx-media-list-columns': columns }\n})\n\nconst hint = computed(() => {\n const count = items.value.length\n\n if (props.min && count < props.min) return t('field.at-least', { count: props.min })\n\n return limit.value === null ? '' : t('field.count', { count, max: limit.value })\n})\n</script>\n\n<template>\n <div\n class=\"wx-media-list\"\n :class=\"[`wx-media-list--${layout}`, { 'is-disabled': disabled }]\"\n :style=\"style\"\n >\n <span v-if=\"label\" class=\"wx-media-list__label\">{{ label }}</span>\n\n <wx-sortable-list\n v-model=\"items\"\n plain\n :disabled=\"disabled\"\n :handle=\"layout === 'grid' ? 'row' : 'grip'\"\n :item-key=\"keyFor\"\n :item-label=\"nameOf\"\n :drag-label=\"t('field.reorder')\"\n :empty-text=\"t('field.empty')\"\n >\n <template #default=\"{ item, index }\">\n <div\n class=\"wx-media-list__cell\"\n :class=\"{ 'is-broken': broken(item), 'is-busy': editing === index }\"\n >\n <wx-file-card\n :name=\"nameOf(item)\"\n :thumbnail=\"thumbOf(item)\"\n :type=\"fileOf(item)?.mime\"\n :size=\"layout === 'rows' ? 'sm' : 'md'\"\n >\n <template #meta>\n <span v-if=\"broken(item)\" class=\"wx-media-list__missing\">\n {{ t('field.missing') }}\n </span>\n <span v-else>{{ metaOf(item) }}</span>\n </template>\n </wx-file-card>\n\n <div v-if=\"!disabled\" class=\"wx-media-list__tools\">\n <wx-actions size=\"sm\">\n <!--\n The captions are asked for beside the picture they describe, the same way the\n single field asks for them: what they are about has to stay in sight while\n they are being written.\n -->\n <wx-popover\n v-if=\"captions\"\n :open=\"editing === index\"\n :title=\"t('field.captions')\"\n :width=\"360\"\n side=\"top\"\n teleport\n @update:open=\"(open: boolean) => (editing = open ? index : null)\"\n >\n <template #trigger>\n <wx-action type=\"edit\" :title=\"t('field.captions')\" />\n </template>\n\n <div class=\"wx-media-list__captions\">\n <div class=\"wx-media-list__caption\">\n <span class=\"wx-media-list__caption-label\">alt</span>\n <wx-input\n localized\n :model-value=\"words(index, 'alt')\"\n :placeholder=\"t('field.alt-hint')\"\n @update:model-value=\"(next: unknown) => setWords(index, 'alt', next)\"\n />\n </div>\n\n <div class=\"wx-media-list__caption\">\n <span class=\"wx-media-list__caption-label\">title</span>\n <wx-input\n localized\n :model-value=\"words(index, 'title')\"\n :placeholder=\"t('field.title-hint')\"\n @update:model-value=\"(next: unknown) => setWords(index, 'title', next)\"\n />\n </div>\n\n <div class=\"wx-media-list__captions-footer\">\n <wx-button size=\"sm\" type=\"primary\" @click=\"editing = null\">\n {{ t('manager.save') }}\n </wx-button>\n </div>\n </div>\n </wx-popover>\n\n <wx-action\n v-if=\"!broken(item)\"\n type=\"goto\"\n :title=\"t('field.open')\"\n @click=\"openLibrary(item)\"\n />\n\n <wx-popconfirm\n :title=\"t('field.remove-title')\"\n :description=\"t('field.remove-text')\"\n :confirm-text=\"t('field.remove-confirm')\"\n :cancel-text=\"t('manager.cancel')\"\n confirm-type=\"danger\"\n side=\"top\"\n @confirm=\"remove(index)\"\n >\n <template #trigger>\n <wx-action type=\"remove\" :title=\"t('field.remove-confirm')\" />\n </template>\n </wx-popconfirm>\n </wx-actions>\n </div>\n </div>\n </template>\n </wx-sortable-list>\n\n <div class=\"wx-media-list__footer\">\n <wx-button size=\"sm\" variant=\"outline\" :disabled=\"disabled || full\" @click=\"add\">\n {{ t('field.add') }}\n </wx-button>\n\n <span v-if=\"hint\" class=\"wx-media-list__hint\">{{ hint }}</span>\n </div>\n </div>\n</template>\n\n<style>\n.wx-media-list {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-media-list__label {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-media-list__footer {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n}\n\n.wx-media-list__hint {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n}\n\n/*\n * The list brings its own rules with it, and they are written from here through three classes\n * rather than one: a component of the design system carries scoped styles, and a scoped\n * selector is a class plus an attribute — a single class from outside ties with it and loses to\n * whichever stylesheet happens to come last.\n */\n.wx-media-list--grid .wx-sortable-list .wx-sortable-list__body {\n display: grid;\n grid-template-columns: var(--wx-media-list-columns);\n gap: var(--wx-space-8);\n}\n\n.wx-media-list--grid .wx-sortable-list .wx-sortable-list__row {\n display: block;\n padding: 0;\n border-top: none;\n}\n\n.wx-media-list--grid .wx-sortable-list .wx-sortable-list__empty {\n grid-column: 1 / -1;\n padding-inline: 0;\n}\n\n.wx-media-list__cell {\n position: relative;\n min-width: 0;\n}\n\n/* The shape of the frame is the field's to choose; the card's own 4/3 is the default. */\n.wx-media-list--grid .wx-media-list__cell > .wx-file-card > .wx-file-card__preview {\n aspect-ratio: var(--wx-media-list-aspect, 4 / 3);\n}\n\n/*\n * A document in a list is a line, not a card standing on end: the picture shrinks to a mark at\n * the start of the row and the name runs along it.\n */\n.wx-media-list--rows .wx-media-list__cell {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n}\n\n.wx-media-list--rows .wx-media-list__cell > .wx-file-card {\n flex: 1 1 auto;\n flex-direction: row;\n align-items: center;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n.wx-media-list--rows .wx-media-list__cell > .wx-file-card > .wx-file-card__preview {\n flex: 0 0 auto;\n width: 48px;\n aspect-ratio: 1 / 1;\n}\n\n.wx-media-list--rows .wx-media-list__cell > .wx-file-card > .wx-file-card__body {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* The card centres its name under the picture; beside one it belongs at the start of the line. */\n.wx-media-list--rows .wx-media-list__cell .wx-file-card__name,\n.wx-media-list--rows .wx-media-list__cell .wx-file-card__meta {\n text-align: start;\n}\n\n/* A key the library no longer answers about: visible, and with the button to take it out. */\n.wx-media-list__cell.is-broken > .wx-file-card > .wx-file-card__preview {\n border-color: var(--wx-color-danger);\n background: var(--wx-color-danger-soft);\n}\n\n.wx-media-list__missing {\n color: var(--wx-color-danger);\n}\n\n.wx-media-list__tools {\n display: flex;\n padding: 2px;\n border: 1px solid var(--wx-border-muted);\n border-radius: var(--wx-radius-sm);\n background: color-mix(in srgb, var(--wx-bg-surface) 88%, transparent);\n backdrop-filter: blur(2px);\n}\n\n.wx-media-list--grid .wx-media-list__tools {\n position: absolute;\n top: var(--wx-space-8);\n right: var(--wx-space-8);\n}\n\n/*\n * Out of the way until the pointer arrives — but only where one can arrive. A touch screen\n * never hovers, and buttons waiting for a hover that cannot happen are buttons nobody reaches.\n */\n@media (hover: hover) {\n .wx-media-list--grid .wx-media-list__tools {\n opacity: 0;\n transition: opacity var(--wx-duration-fast) var(--wx-easing-standard);\n }\n\n /*\n * `is-busy` among them because the captions panel is teleported: while it is up neither the\n * pointer nor the focus is inside the cell, and the buttons would fade out from under it.\n */\n .wx-media-list--grid .wx-media-list__cell:hover .wx-media-list__tools,\n .wx-media-list--grid .wx-media-list__cell:focus-within .wx-media-list__tools,\n .wx-media-list--grid .wx-media-list__cell.is-busy .wx-media-list__tools {\n opacity: 1;\n }\n}\n\n.wx-media-list__captions {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n width: 100%;\n}\n\n.wx-media-list__caption {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n\n.wx-media-list__caption-label {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n}\n\n.wx-media-list__captions-footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--wx-space-8);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport MediaList from './MediaList.vue'\nimport type { MediaValue } from './types'\n\n/**\n * `wx-file`: one document, on a card that says what it is.\n *\n * The same list underneath, kept to one item. What is stored is a single value rather than a\n * list of one, because that is what a template reading `props.file.url` expects — the shape of\n * the value follows the field, not the component that draws it.\n *\n * The props of the list underneath are not declared here on purpose — see `GalleryField`.\n */\ndefineOptions({ name: 'WxFileField', inheritAttrs: false })\n\nconst model = defineModel<MediaValue | null>({ default: null })\n\nconst list = computed<MediaValue[]>({\n get: () => (model.value ? [model.value] : []),\n set: (next) => {\n model.value = next[0] ?? null\n },\n})\n</script>\n\n<template>\n <media-list v-bind=\"$attrs\" v-model=\"list\" layout=\"rows\" single />\n</template>\n","<script setup lang=\"ts\">\nimport MediaList from './MediaList.vue'\nimport type { MediaValue } from './types'\n\n/**\n * `wx-files`: a list of attachments, one to a line.\n *\n * The props of the list underneath are not declared here on purpose — see `GalleryField`.\n */\ndefineOptions({ name: 'WxFilesField', inheritAttrs: false })\n\nconst model = defineModel<MediaValue[]>({ default: () => [] })\n</script>\n\n<template>\n <media-list v-bind=\"$attrs\" v-model=\"model\" layout=\"rows\" />\n</template>\n","<script setup lang=\"ts\">\nimport MediaList from './MediaList.vue'\nimport type { MediaValue } from './types'\n\n/**\n * `wx-gallery`: a grid of pictures in an order somebody chose.\n *\n * The props of the list underneath are deliberately not declared here. Vue turns an undeclared\n * Boolean prop into `false` rather than leaving it `undefined`, so a wrapper that declares them\n * and passes them on hands the child `false` everywhere the caller asked for nothing — and half\n * the behaviour switches itself off silently. Attributes travel as they were given.\n */\ndefineOptions({ name: 'WxGalleryField', inheritAttrs: false })\n\nconst model = defineModel<MediaValue[]>({ default: () => [] })\n</script>\n\n<template>\n <!-- `accept` last and fixed: a gallery of `.zip` files is not a gallery. -->\n <media-list v-bind=\"$attrs\" v-model=\"model\" layout=\"grid\" accept=\"image\" />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport {\n WxAction,\n WxActions,\n WxButton,\n WxImage,\n WxInput,\n WxPopconfirm,\n WxPopover,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { createMediaApi } from './api'\nimport { openMediaPicker } from './openMediaPicker'\nimport { useMediaMessages } from './i18n'\nimport type { MediaAspect, MediaKind, MediaValue } from './types'\n\n/**\n * A picture on a form, with the words this entity uses for it.\n *\n * Two states and nothing in between: an empty frame that opens the library, and the picture\n * with what can be done to it. `alt` and `title` live here rather than on the file, because one\n * picture used by two articles needs two captions — and they are localized for the same reason\n * everything else on a form is.\n *\n * What the field stores is the key and those words. The address is worked out when the page is\n * drawn, so moving the library to another disk changes nothing written before.\n */\nconst props = withDefaults(\n defineProps<{\n label?: string\n accept?: MediaKind | null\n /** Off for a decorative picture, where a caption is noise. */\n captions?: boolean\n /**\n * Shape of the frame: one of the presets, any `width / height`, or `null` for a fixed\n * height instead.\n *\n * A field that holds a cover and a field that holds an avatar are not the same shape, and\n * the frame should say which before anything is in it.\n */\n aspect?: MediaAspect | null\n /** Height of the frame when there is no aspect — a number in pixels, or any CSS length. */\n height?: number | string\n disabled?: boolean\n }>(),\n {\n label: undefined,\n accept: 'image',\n captions: true,\n aspect: '16/9',\n height: 220,\n disabled: false,\n },\n)\n\nconst value = defineModel<MediaValue | null>({ default: null })\n\nuseMediaMessages()\n\nconst t = useTranslate('webx-media')\n\nconst api = createMediaApi(useAdmin())\n\n/** The presets, spelled the way CSS wants them. Anything else is passed through as given. */\nconst RATIOS: Record<string, string> = {\n '16/9': '16 / 9',\n '4/3': '4 / 3',\n '1/1': '1 / 1',\n}\n\nconst editing = ref(false)\n\n/*\n * What a record stores is the key. The address beside it is only there when the picture was\n * chosen in this session, so a form opened on something saved last week has a key and nothing\n * to draw — and the library is the only thing that knows where that key currently lives.\n */\nconst resolved = ref<Record<string, string>>({})\n\nwatch(\n () => value.value?.path,\n async (path) => {\n if (!path || value.value?.url || resolved.value[path]) return\n\n const file = await api.fileByPath(path)\n\n if (file) resolved.value = { ...resolved.value, [path]: file.url }\n },\n { immediate: true },\n)\n\nconst preview = computed(() => {\n const current = value.value\n\n if (!current) return null\n\n return current.url ?? resolved.value[current.path] ?? null\n})\n\nconst frameStyle = computed(() =>\n props.aspect\n ? { aspectRatio: RATIOS[props.aspect] ?? props.aspect }\n : { height: typeof props.height === 'number' ? `${props.height}px` : props.height },\n)\n\n/**\n * `alt` and `title` are edited through the field, so they have to be readable as a record even\n * when what was stored is the plain string a column held before the site had a second language.\n */\nfunction words(key: 'alt' | 'title'): LocalizedValue | string {\n return value.value?.[key] ?? {}\n}\n\nfunction setWords(key: 'alt' | 'title', next: unknown): void {\n if (!value.value) return\n\n value.value = { ...value.value, [key]: next as LocalizedValue }\n}\n\nasync function choose(): Promise<void> {\n if (props.disabled) return\n\n const file = await openMediaPicker({ accept: props.accept })\n\n if (!file) return\n\n /*\n * Remembered against the key as well as put on the value. The address on the value is the\n * session's own and does not survive a save — the server stores the key and answers with\n * what it stored — and the key has not changed, so the watcher above does not fire either.\n * Without this the picture vanishes from the field the moment the form is saved and comes\n * back only on a reload.\n */\n resolved.value = { ...resolved.value, [file.path]: file.url }\n\n // The captions belong to the entity, so picking a different picture keeps them: usually the\n // words are still right and retyping them in three languages is the punishment for a swap.\n value.value = { ...value.value, path: file.path, url: file.url }\n}\n\n/** Clears the field. The file stays in the library — it is very likely used somewhere else. */\nfunction clear(): void {\n value.value = null\n editing.value = false\n}\n</script>\n\n<template>\n <div class=\"wx-media-field\">\n <span v-if=\"label\" class=\"wx-media-field__label\">{{ label }}</span>\n\n <div v-if=\"!value\" class=\"wx-media-field__frame is-empty\" :style=\"frameStyle\">\n <button type=\"button\" class=\"wx-media-field__pick\" :disabled=\"disabled\" @click=\"choose\">\n {{ t('field.select') }}\n </button>\n </div>\n\n <div v-else class=\"wx-media-field__frame\" :style=\"frameStyle\">\n <!--\n The picture and the buttons are wrapped in elements of our own rather than styled\n through their class: a component of the design system carries scoped rules, and a scoped\n rule is a class plus an attribute, so a single class from outside loses to it — silently,\n and only in a browser.\n\n The picture is also the way to swap it. A button labelled \"replace\" inside the captions\n popover is a second place to look for something the picture itself already offers.\n -->\n <button\n type=\"button\"\n class=\"wx-media-field__picture\"\n :disabled=\"disabled\"\n :title=\"t('field.replace')\"\n @click=\"choose\"\n >\n <wx-image v-if=\"preview\" :src=\"preview\" fit=\"contain\" width=\"100%\" height=\"100%\" />\n <span v-else class=\"wx-media-field__missing\">{{ t('field.no-preview') }}</span>\n </button>\n\n <div v-if=\"!disabled\" class=\"wx-media-field__bar\">\n <wx-actions size=\"sm\">\n <!--\n The captions are asked for where the picture is, not in a dialog over the whole form:\n what they describe has to stay in sight while they are being written.\n -->\n <wx-popover\n v-if=\"captions\"\n v-model:open=\"editing\"\n :title=\"t('field.captions')\"\n :width=\"360\"\n side=\"top\"\n teleport\n >\n <template #trigger>\n <wx-action type=\"edit\" :title=\"t('field.edit')\" />\n </template>\n\n <div class=\"wx-media-field__captions\">\n <div class=\"wx-media-field__caption\">\n <label class=\"wx-media-field__caption-label\" for=\"wx-media-alt\">alt</label>\n <wx-input\n id=\"wx-media-alt\"\n localized\n :model-value=\"words('alt')\"\n :placeholder=\"t('field.alt-hint')\"\n @update:model-value=\"(next) => setWords('alt', next)\"\n />\n </div>\n\n <div class=\"wx-media-field__caption\">\n <label class=\"wx-media-field__caption-label\" for=\"wx-media-title\">title</label>\n <wx-input\n id=\"wx-media-title\"\n localized\n :model-value=\"words('title')\"\n :placeholder=\"t('field.title-hint')\"\n @update:model-value=\"(next) => setWords('title', next)\"\n />\n </div>\n\n <div class=\"wx-media-field__captions-footer\">\n <wx-button size=\"sm\" type=\"primary\" @click=\"editing = false\">\n {{ t('manager.save') }}\n </wx-button>\n </div>\n </div>\n </wx-popover>\n\n <!--\n Clearing the field is not deleting the file: the same picture is very likely used by\n another record, and this is the one place somebody would expect otherwise.\n -->\n <wx-popconfirm\n :title=\"t('field.clear-title')\"\n :description=\"t('field.clear-text')\"\n :confirm-text=\"t('field.clear-confirm')\"\n :cancel-text=\"t('manager.cancel')\"\n confirm-type=\"danger\"\n side=\"top\"\n @confirm=\"clear\"\n >\n <template #trigger>\n <wx-action type=\"remove\" :title=\"t('field.clear-confirm')\" />\n </template>\n </wx-popconfirm>\n </wx-actions>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n.wx-media-field {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-6);\n}\n\n.wx-media-field__label {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-media-field__frame {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n padding: var(--wx-space-8);\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n overflow: hidden;\n}\n\n.wx-media-field__frame.is-empty {\n border-style: dashed;\n padding: 0;\n}\n\n.wx-media-field__pick {\n width: 100%;\n height: 100%;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--wx-text-placeholder);\n font-family: inherit;\n font-size: var(--wx-font-size-md);\n cursor: pointer;\n transition: color var(--wx-duration-fast) var(--wx-easing-standard);\n}\n\n.wx-media-field__pick:hover:not(:disabled) {\n color: var(--wx-text-muted);\n}\n\n.wx-media-field__pick:disabled {\n cursor: not-allowed;\n}\n\n.wx-media-field__picture {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n min-width: 0;\n padding: 0;\n border: none;\n /* Inscribed in the frame, so rounded a step less than it — concentric, not parallel. */\n border-radius: var(--wx-radius-xs);\n overflow: hidden;\n background: transparent;\n cursor: pointer;\n}\n\n.wx-media-field__picture:disabled {\n cursor: not-allowed;\n}\n\n.wx-media-field__missing {\n color: var(--wx-text-placeholder);\n font-size: var(--wx-font-size-sm);\n}\n\n/* On the picture, out of the way of it: the frame is the preview, not a toolbar. */\n.wx-media-field__bar {\n position: absolute;\n /* Clear of the frame's inner edge rather than sitting on it. */\n bottom: var(--wx-space-16);\n left: 50%;\n transform: translateX(-50%);\n padding: var(--wx-space-4);\n border-radius: var(--wx-radius-control);\n background: var(--wx-bg-surface);\n box-shadow: var(--wx-shadow-sm);\n}\n\n/* A label belongs to the field under it, so it sits closer to that than to the next one. */\n.wx-media-field__captions {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n width: 100%;\n}\n\n.wx-media-field__caption {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n\n.wx-media-field__caption-label {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n}\n\n.wx-media-field__captions-footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--wx-space-8);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate, WxListScreen, type ScreenAction } from '@webx-ui/module-admin'\nimport { WxCard, type TabItem, type TabValue } from '@webx-ui/core'\nimport MediaManager from './MediaManager.vue'\nimport { useMediaMessages } from './i18n'\nimport type { MediaKind } from './types'\n\n/**\n * The library as a page of the panel.\n *\n * It looks like the other lists: the section's name on its own line, the one action the\n * section exists for beside it, the kinds of file as the views of the list, and the manager\n * under them in a card.\n *\n * Uploading is that action, so it is a filled primary button with a word on it rather than the\n * third grey icon in a row of six (§18). Blue and not green: in this system green means\n * \"it worked\", and a green button says \"uploaded\" where it means \"upload\". The library brings\n * its own card because the card is what scrolls here.\n */\nconst context = useAdmin()\nuseMediaMessages()\n\nconst t = useTranslate('webx-media')\n\nconst manager = useTemplateRef<{ upload: () => void }>('manager')\nconst kind = ref<TabValue>('all')\n/* The tabs speak in tab values; the manager speaks in kinds of file. */\nconst fileKind = computed(() => kind.value as MediaKind | 'all')\n\nconst canUpload = computed(() => context.can('media.upload') || context.can('media.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'media')?.title ??\n t('module.title'),\n)\n\n/* The same kinds the toolbar used to hide in a dropdown; here they are what the list is. */\nconst views = computed<TabItem[]>(() => [\n { value: 'all', label: t('manager.all-types') },\n ...(['image', 'video', 'audio', 'document', 'other'] as MediaKind[]).map((one) => ({\n value: one,\n label: t(`manager.${one}`),\n })),\n])\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canUpload.value\n ? [\n {\n key: 'upload',\n label: t('manager.upload'),\n icon: 'upload',\n primary: true,\n run: () => manager.value?.upload(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <wx-list-screen\n v-model:view=\"kind\"\n :title=\"title\"\n :views=\"views\"\n :actions=\"actions\"\n :card=\"false\"\n fill\n >\n <wx-card class=\"wx-media-page\" padding=\"md\">\n <media-manager ref=\"manager\" :type=\"fileKind\" in-page />\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n.wx-media-page {\n height: 100%;\n min-height: 0;\n}\n\n/* The card is the page: its body is what scrolls, not the page behind it. */\n.wx-media-page > .wx-card__body {\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport FileField from './FileField.vue'\nimport FilesField from './FilesField.vue'\nimport GalleryField from './GalleryField.vue'\nimport MediaField from './MediaField.vue'\nimport MediaPage from './MediaPage.vue'\nimport { mediaMessages } from './messages'\nimport { openMediaPicker } from './openMediaPicker'\n\nexport interface MediaOptions {\n /** Where the section lives inside the panel. */\n path?: string\n}\n\n/**\n * The file manager as a section of the panel.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: a section shows up when both halves are installed, and stays out of the way when\n * only one is.\n */\nexport function media(options: MediaOptions = {}): AdminModule {\n const path = options.path ?? '/media'\n\n return {\n id: 'media',\n path,\n routes: [\n {\n path,\n name: 'webx.media',\n component: MediaPage,\n },\n ],\n // What a screen means by these four: the fields over the library. The server registers the\n // same names for the values it stores. Three of them are one component with a layout and a\n // limit — the names are what an author picks from a list, and nobody picks \"media with\n // `multiple: true` and `accept: document`\".\n types: {\n 'wx-media': { component: MediaField, kind: 'field' },\n 'wx-gallery': { component: GalleryField, kind: 'field', wide: true },\n 'wx-file': { component: FileField, kind: 'field' },\n 'wx-files': { component: FilesField, kind: 'field', wide: true },\n },\n /**\n * Where the panel's own fields get a picture from. `wx-rich-text` lives in\n * `module-admin`, which cannot reach the library — the dependency runs this way — so the\n * module that has the files hands the way in, and a panel without a file manager simply\n * has no image button in its editors.\n */\n pickImage: async () => {\n const file = await openMediaPicker({ accept: 'image' })\n\n // The key travels with the address, and it is the key that is kept: a document written\n // today has to still find its pictures after the library moves disks.\n return file ? { url: file.url, path: file.path } : null\n },\n }\n}\n\nexport { mediaMessages }\nexport { createMediaApi, type MediaApi } from './api'\nexport {\n openMediaFiles,\n openMediaLibrary,\n openMediaPicker,\n type MediaFilesOptions,\n type MediaPickerOptions,\n} from './openMediaPicker'\nexport { default as WxMediaManager } from './MediaManager.vue'\nexport { default as WxMediaPage } from './MediaPage.vue'\nexport { default as WxMediaPicker } from './MediaPicker.vue'\nexport { default as WxMediaField } from './MediaField.vue'\nexport { default as WxGalleryField } from './GalleryField.vue'\nexport { default as WxFileField } from './FileField.vue'\nexport { default as WxFilesField } from './FilesField.vue'\nexport type {\n DirectoryNotEmpty,\n EditOperations,\n FileQuery,\n MediaDirectory,\n MediaFile,\n MediaAspect,\n MediaKind,\n MediaPage,\n MediaValue,\n} from './types'\n"],"names":["createMediaApi","admin","base","data","body","parentId","title","id","force","query","path","directoryId","files","onProgress","file","resolve","reject","request","token","csrfToken","event","answer","name","ids","operations","width","height","fit","url","cookie","part","readable","bytes","units","size","unit","nameFromPath","props","__props","selected","_useModel","emit","__emit","t","useTranslate","nodes","ref","expanded","watch","directories","label","keysOf","directory","onDrop","parent","_createBlock","_unref","WxTree","$event","empty","computed","copied","toast","copyFailed","area","done","WxSelectionArea","_withCtx","isSelected","_createElementBlock","_Fragment","_renderList","WxFileCard","_directive_wx_select","WxEmpty","search","type","sort","types","kind","sorts","typeLabel","one","sortLabel","_openBlock","_hoisted_1","WxAction","_createVNode","WxInput","WxDropdown","option","WxDropdownItem","_createTextVNode","_toDisplayString","WxActions","open","dismiss","useModal","options","flatten","target","WxDialog","WxSpace","WxButton","_cache","WxSelect","input","useTemplateRef","onMounted","nextTick","submit","value","_createElementVNode","mediaMessages","seeded","useMediaMessages","i18n","useAdmin","api","message","useErrorText","editorLabels","root","useElementWidth","compact","current","page","busy","foldersOpen","picker","rows","canManage","canUpload","folder","find","picksOne","load","loadFiles","debounce","narrow","to","choose","upload","chosen","stored","duplicates","error","askName","createModal","NameDialog","createFolder","renameFolder","deleteFolder","confirm","counts","countsOf","moveFolder","askWhereTo","MoveDialog","moveSelected","removeSelected","rename","remove","edit","result","openImageEditor","walk","node","deeper","__expose","run","wait","timer","_normalizeClass","DirectoryTree","_hoisted_2","MediaToolbar","FileGrid","_hoisted_4","_hoisted_5","WxPagination","WxDrawer","_hoisted_6","MediaManager","openMediaPicker","MediaPicker","openMediaFiles","openMediaLibrary","model","inject","adminKey","RATIOS","items","next","limit","full","asking","paths","wanted","found","item","remember","fileOf","broken","nameOf","thumbOf","metaOf","editing","keyFor","index","other","words","key","setWords","list","add","room","known","added","_","at","openLibrary","rest","style","columns","hint","count","WxSortableList","_hoisted_3","WxPopover","_hoisted_7","_hoisted_8","WxPopconfirm","_hoisted_9","_hoisted_10","MediaList","_mergeProps","_ctx","resolved","preview","frameStyle","clear","WxImage","context","manager","fileKind","module","views","actions","WxListScreen","WxCard","media","MediaPage","MediaField","GalleryField","FileField","FilesField"],"mappings":";;;AAsCO,SAASA,GAAeC,GAA+B;AAC5D,QAAMC,IAAO,GAAGD,EAAM,OAAO,UACvBE,IAAO,CAAIC,MAAyBA,EAAK;AAE/C,SAAO;AAAA,IACL,aAAa,MAAMH,EAAM,KAAK,IAAgC,GAAGC,CAAI,cAAc,EAAE,KAAKC,CAAI;AAAA,IAE9F,iBAAiB,CAACE,GAAUC,MAC1BL,EAAM,KACH,KAA+B,GAAGC,CAAI,gBAAgB,EAAE,WAAWG,GAAU,OAAAC,GAAO,EACpF,KAAKH,CAAI;AAAA,IAEd,iBAAiB,CAACI,GAAID,MACpBL,EAAM,KAAK,MAAgC,GAAGC,CAAI,gBAAgBK,CAAE,IAAI,EAAE,OAAAD,GAAO,EAAE,KAAKH,CAAI;AAAA,IAE9F,eAAe,CAACI,GAAIF,MAClBJ,EAAM,KACH,MAAgC,GAAGC,CAAI,gBAAgBK,CAAE,SAAS,EAAE,WAAWF,GAAU,EACzF,KAAKF,CAAI;AAAA,IAEd,iBAAiB,CAACI,GAAIC,IAAQ,OAC5BP,EAAM,KAAK,OAAa,GAAGC,CAAI,gBAAgBK,CAAE,IAAI;AAAA,MACnD,OAAO,EAAE,OAAOC,IAAQ,IAAI,OAAA;AAAA,IAAU,CACvC;AAAA,IAEH,OAAO,CAACC,IAAQ,CAAA,MACdR,EAAM,KAAK,IAAe,GAAGC,CAAI,UAAU;AAAA,MACzC,OAAO;AAAA,QACL,cAAcO,EAAM,gBAAgB;AAAA,QACpC,GAAGA,EAAM,KAAK;AAAA,QACd,MAAMA,EAAM,QAAQ;AAAA,QACpB,MAAMA,EAAM;AAAA,QACZ,MAAMA,EAAM;AAAA,QACZ,UAAUA,EAAM;AAAA,MAAA;AAAA,IAClB,CACD;AAAA,IAEH,MAAM,CAACF,MAAON,EAAM,KAAK,IAAyB,GAAGC,CAAI,UAAUK,CAAE,EAAE,EAAE,KAAKJ,CAAI;AAAA,IAElF,YAAY,CAACO,MACXT,EAAM,KACH,IAAyB,GAAGC,CAAI,uBAAuB,mBAAmBQ,CAAI,CAAC,EAAE,EACjF,KAAKP,CAAI,EACT,MAAM,MAAM,IAAI;AAAA,IAErB,MAAM,OAAOQ,GAAaC,GAAOC,GAAY;AAC3C,YAAMT,IAAO,IAAI,SAAA;AACjB,aAAAA,EAAK,OAAO,gBAAgB,OAAOO,CAAW,CAAC,GAC/CC,EAAM,QAAQ,CAACE,MAASV,EAAK,OAAO,WAAWU,CAAI,CAAC,GAI7C,IAAI,QAAqB,CAACC,GAASC,MAAW;AACnD,cAAMC,IAAU,IAAI,eAAA;AAEpB,QAAAA,EAAQ,KAAK,QAAQ,GAAGf,CAAI,QAAQ,GACpCe,EAAQ,eAAe,QACvBA,EAAQ,kBAAkB,IAC1BA,EAAQ,iBAAiB,UAAU,kBAAkB,GACrDA,EAAQ,iBAAiB,oBAAoB,gBAAgB,GAG7DA,EAAQ,iBAAiB,iBAAiBhB,EAAM,KAAK,MAAM,MAAM;AAEjE,cAAMiB,IAAQC,GAAA;AACd,QAAID,KACFD,EAAQ,iBAAiB,gBAAgBC,CAAK,GAGhDD,EAAQ,OAAO,iBAAiB,YAAY,CAACG,MAAU;AACrD,UAAIA,EAAM,oBACRP,IAAa,KAAK,MAAOO,EAAM,SAASA,EAAM,QAAS,GAAG,CAAC;AAAA,QAE/D,CAAC,GAEDH,EAAQ,iBAAiB,QAAQ,MAAM;AACrC,gBAAMI,IAASJ,EAAQ;AAEvB,cAAIA,EAAQ,UAAU,OAAOA,EAAQ,SAAS,OAAOI,GAAQ,MAAM;AACjE,YAAAN,EAAQM,EAAO,IAAI;AAEnB;AAAA,UACF;AAEA,UAAAL;AAAA,YACE,OAAO,OAAO,IAAI,MAAMK,GAAQ,WAAW,eAAe,GAAG;AAAA,cAC3D,QAAQJ,EAAQ;AAAA,cAChB,MAAMI;AAAA,YAAA,CACP;AAAA,UAAA;AAAA,QAEL,CAAC,GAEDJ,EAAQ,iBAAiB,SAAS,MAAMD,EAAO,IAAI,MAAM,eAAe,CAAC,CAAC,GAC1EC,EAAQ,KAAKb,CAAI;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,CAACG,GAAIe,MACXrB,EAAM,KAAK,MAA2B,GAAGC,CAAI,UAAUK,CAAE,IAAI,EAAE,MAAAe,GAAM,EAAE,KAAKnB,CAAI;AAAA,IAElF,MAAM,CAACoB,GAAKZ,MACVV,EAAM,KACH,KAAkC,GAAGC,CAAI,eAAe,EAAE,KAAAqB,GAAK,cAAcZ,GAAa,EAC1F,KAAK,CAACP,MAASA,EAAK,KAAK,KAAK;AAAA;AAAA;AAAA,IAInC,QAAQ,CAACmB,MACPtB,EAAM,KACH,KAAoC,GAAGC,CAAI,iBAAiB,EAAE,KAAAqB,EAAA,CAAK,EACnE,KAAK,CAACnB,MAASA,EAAK,KAAK,OAAO;AAAA,IAErC,WAAW,CAACG,MAAON,EAAM,KAAK,OAAa,GAAGC,CAAI,UAAUK,CAAE,EAAE;AAAA,IAEhE,MAAM,CAACA,GAAIiB,MACTvB,EAAM,KAAK,KAA0B,GAAGC,CAAI,UAAUK,CAAE,SAASiB,CAAU,EAAE,KAAKrB,CAAI;AAAA,IAExF,MAAM,CAACI,MAAON,EAAM,KAAK,KAA0B,GAAGC,CAAI,UAAUK,CAAE,OAAO,EAAE,KAAKJ,CAAI;AAAA,IAExF,iBAAiB,CAACI,MAChBN,EAAM,KAAK,KAA0B,GAAGC,CAAI,UAAUK,CAAE,mBAAmB,EAAE,KAAKJ,CAAI;AAAA,IAExF,MAAMW,GAAMW,GAAOC,GAAQC,IAAM,SAAS;AACxC,UAAI,CAACb,EAAK;AACR,eAAO;AAGT,YAAMc,IAAM,IAAI,IAAId,EAAK,OAAO,OAAO,SAAS,MAAM;AACtD,aAAAc,EAAI,aAAa,IAAI,KAAK,OAAOH,CAAK,CAAC,GACnCC,KACFE,EAAI,aAAa,IAAI,KAAK,OAAOF,CAAM,CAAC,GAE1CE,EAAI,aAAa,IAAI,OAAOD,CAAG,GAExBC,EAAI,WAAWA,EAAI;AAAA,IAC5B;AAAA,EAAA;AAEJ;AAEA,SAAST,KAA2B;AAClC,QAAMU,IAAS,SAAS,OAAO,MAAM,IAAI,EAAE,KAAK,CAACC,MAASA,EAAK,WAAW,aAAa,CAAC;AAExF,SAAOD,IAAS,mBAAmBA,EAAO,MAAM,EAAoB,CAAC,IAAI;AAC3E;ACpLO,SAASE,GAASC,GAAuB;AAC9C,QAAMC,IAAQ,CAAC,KAAK,MAAM,MAAM,MAAM,IAAI;AAC1C,MAAIC,IAAOF,GACPG,IAAO;AAEX,SAAOD,KAAQ,QAAQC,IAAOF,EAAM,SAAS;AAC3C,IAAAC,KAAQ,MACRC;AAGF,SAAO,GAAGD,KAAQ,MAAMC,MAAS,IAAI,KAAK,MAAMD,CAAI,IAAIA,EAAK,QAAQ,CAAC,CAAC,IAAID,EAAME,CAAI,CAAC;AACxF;AAGO,SAASC,GAAa1B,GAAsB;AACjD,SAAOA,EAAK,MAAM,GAAG,EAAE,SAASA;AAClC;;;;;;;;;;;ACLA,UAAM2B,IAAQC,GAIRC,IAAWC,KAA2B,UAA6B,GAEnEC,IAAOC,GAIPC,IAAIC,EAAa,YAAY,GAM7BC,IAAQC,EAAY,EAAE,GACtBC,IAAWD,EAAc,EAAE;AAEjC,IAAAE;AAAA,MACE,MAAMX,EAAM;AAAA,MACZ,CAACY,MAAgB;AACf,QAAAJ,EAAM,QAAQI,EAAY,IAAIC,CAAK,GACnCH,EAAS,QAAQI,EAAOF,CAAW;AAAA,MACrC;AAAA,MACA,EAAE,WAAW,IAAM,MAAM,GAAA;AAAA,IAAK;AAGhC,aAASC,EAAME,GAAiC;AAC9C,aAAO;AAAA,QACL,GAAGA;AAAA,QACH,OAAOA,EAAU,UAAUT,EAAE,cAAc,IAAIS,EAAU;AAAA,QACzD,WAAWA,EAAU,YAAY,CAAA,GAAI,IAAIF,CAAK;AAAA,MAAA;AAAA,IAElD;AAEA,aAASC,EAAOF,GAAyC;AACvD,aAAOA,EAAY,QAAQ,CAACG,MAAc,CAACA,EAAU,IAAI,GAAGD,EAAOC,EAAU,YAAY,CAAA,CAAE,CAAC,CAAC;AAAA,IAC/F;AAEA,aAASC,EAAOjC,GAAkC;AAChD,YAAMkC,IAASlC,EAAM,SAAS,WAAWA,EAAM,SAASA,EAAM;AAE9D,MAAIkC,KACFb,EAAK,QAAQrB,EAAM,KAAK,IAAIkC,EAAO,EAAE;AAAA,IAEzC;2BAIEC,EASEC,EAAAC,EAAA,GAAA;AAAA,MARQ,UAAUlB,EAAA;AAAA,kDAAAA,EAAQ,QAAAmB;AAAA,MAClB,UAAUX,EAAA;AAAA,kDAAAA,EAAQ,QAAAW;AAAA,kBACjBb,EAAA;AAAA,oDAAAA,EAAK,QAAAa;AAAA,MACd,OAAM;AAAA,MACN,YAAS;AAAA,MACT,WAAA;AAAA,MACA,cAAA;AAAA,MACC,QAAAL;AAAA,IAAA;;;;;;;;;;;;;;;ACxDL,UAAMhB,IAAQC,GAQRC,IAAWC,EAAqBF,GAAC,UAAiC,GAElEG,IAAOC,GAOPC,IAAIC,EAAa,YAAY,GAE7Be,IAAQC;AAAA,MAAS,MACrBvB,EAAM,QAAQM,EAAE,wBAAwB,EAAE,OAAON,EAAM,MAAA,CAAO,IAAIM,EAAE,eAAe;AAAA,IAAA;AAGrF,aAASkB,IAAe;AACtB,MAAAC,EAAM,QAAQnB,EAAE,qBAAqB,CAAC;AAAA,IACxC;AAOA,aAASoB,EAAWjD,GAAuB;AACzC,YAAMkD,IAAO,SAAS,cAAc,UAAU;AAC9C,MAAAA,EAAK,QAAQlD,EAAK,KAClBkD,EAAK,aAAa,YAAY,EAAE,GAChCA,EAAK,MAAM,WAAW,SACtBA,EAAK,MAAM,UAAU,KACrB,SAAS,KAAK,YAAYA,CAAI,GAC9BA,EAAK,OAAA;AAEL,UAAIC,IAAO;AAEX,UAAI;AACF,QAAAA,IAAO,SAAS,YAAY,MAAM;AAAA,MACpC,QAAQ;AACN,QAAAA,IAAO;AAAA,MACT;AAIA,UAFAD,EAAK,OAAA,GAEDC,GAAM;AACR,QAAAJ,EAAA;AAEA;AAAA,MACF;AAEA,MAAAC,EAAM,OAAO,GAAGnB,EAAE,aAAa,CAAC,IAAI7B,EAAK,GAAG,IAAI,EAAE,UAAU,EAAA,CAAG;AAAA,IACjE;;;AAKU,aAAAwB,EAAA,MAAM,SAAM,UADpBiB,EAuCoBC,EAAAU,EAAA,GAAA;AAAA;oBApCT3B,EAAA;AAAA,sDAAAA,EAAQ,QAAAmB;AAAA,QACjB,OAAM;AAAA,QACL,WAAWpB,EAAA;AAAA,MAAA;QAGV,SAAA6B,EAAA,CAAqB,EANb,YAAAC,QAAU;AAAA,kBAKpBC,EA+BEC,IAAA,MAAAC,GA9BejC,EAAA,OAAK,CAAbxB,eADTyC,EA+BEC,EAAAgB,EAAA,GAAA;AAAA,YA7BC,KAAK1D,EAAK;AAAA,YAEX,OAAM;AAAA,YACL,MAAMA,EAAK;AAAA,YACX,KAAKA,EAAK;AAAA,YACV,WAAWwB,EAAA,IAAI,MAAMxB,gBAAmB;AAAA,YACxC,MAAMA,EAAK;AAAA,YACX,UAAUsD,EAAWtD,EAAK,EAAE;AAAA,YAC7B,WAAA;AAAA,YACC,UAAUA,EAAK;AAAA,YAChB,WAAA;AAAA,YACA,UAAA;AAAA,YACA,gBAAA;AAAA,YACC,gBAAc0C,EAAAb,CAAA,EAAC,gBAAA;AAAA,YACf,cAAYa,EAAAb,CAAA,EAAC,cAAA;AAAA,YACb,gBAAca,EAAAb,CAAA,EAAC,gBAAA;AAAA,YACf,uBAAqBa,EAAAb,CAAA,EAAC,uBAAA,EAAA,MAAgC7B,EAAK,MAAI;AAAA,YAC/D,gBAAc0C,EAAAb,CAAA,EAAC,gBAAA;AAAA,YACf,cAAYa,EAAAb,CAAA,EAAC,cAAA;AAAA,YACb,gBAAc7B,EAAK,UAAUA,EAAK;AAAA,YAClC,cAAY0C,EAAAb,CAAA,EAAC,mBAAA;AAAA,YACb,kBAAgBa,EAAAb,CAAA,EAAC,kBAAA;AAAA,YACjB,gBAAca,EAAAb,CAAA,EAAC,qBAAA;AAAA,YACf,WAASrB,MAASmB,EAAI,UAAW3B,GAAMQ,CAAI;AAAA,YAC3C,QAAI,CAAAoC,MAAEjB,EAAI,QAAS3B,CAAI;AAAA,YACvB,UAAM,CAAA4C,MAAEjB,EAAI,UAAW3B,CAAI;AAAA,YAC3B,QAAM+C;AAAA,YACN,aAAU,CAAAH,MAAEK,EAAWjD,CAAI;AAAA,YAC3B,YAAQ,CAAA4C,MAAEjB,EAAI,QAAS3B,CAAI;AAAA,UAAA;YA3Bf,CAAA2D,GAAA3D,EAAK,EAAE;AAAA,UAAA;;;iDA+BxByC,EAAwCC,EAAAkB,EAAA,GAAA;AAAA;QAAtB,aAAaf,EAAA;AAAA,MAAA;;;;;;;;;;;;;;;;;;;;;ACxGjC,UAAMtB,IAAQC,GAURqC,IAASnC,EAAmBF,GAAC,QAAyB,GACtDsC,IAAOpC,EAA8BF,GAAC,MAA0B,GAChEuC,IAAOrC,EAAmBF,GAAC,MAAkC,GAE7DG,IAAOC,GAOP,IAAIE,EAAa,YAAY,GAE7BkC,IAAQlB,EAAwD,MAAM;AAAA,MAC1E,EAAE,OAAO,OAAO,OAAO,EAAE,mBAAmB,EAAA;AAAA,MAC5C,GAAI,CAAC,SAAS,SAAS,SAAS,YAAY,OAAO,EAAkB,IAAI,CAACmB,OAAU;AAAA,QAClF,OAAOA;AAAA,QACP,OAAO,EAAE,WAAWA,CAAI,EAAE;AAAA,MAAA,EAC1B;AAAA,IAAA,CACH,GAEKC,IAAQpB,EAAS,MAAM;AAAA,MAC3B,EAAE,OAAO,eAAe,OAAO,EAAE,qBAAqB,EAAA;AAAA,MACtD,EAAE,OAAO,cAAc,OAAO,EAAE,qBAAqB,EAAA;AAAA,MACrD,EAAE,OAAO,QAAQ,OAAO,EAAE,mBAAmB,EAAA;AAAA,MAC7C,EAAE,OAAO,SAAS,OAAO,EAAE,mBAAmB,EAAA;AAAA,IAAE,CACjD,GAEKqB,IAAYrB,EAAS,MAAMkB,EAAM,MAAM,KAAK,CAACI,MAAQA,EAAI,UAAUN,EAAK,KAAK,GAAG,SAAS,EAAE,GAC3FO,IAAYvB,EAAS,MAAMoB,EAAM,MAAM,KAAK,CAACE,MAAQA,EAAI,UAAUL,EAAK,KAAK,GAAG,SAAS,EAAE;sBAI/FO,EAAA,GAAAf,EA6ES,UA7ETgB,IA6ES;AAAA,MAtEC/C,EAAA,gBADRiB,EAMEC,EAAA8B,CAAA,GAAA;AAAA;QAJA,MAAK;AAAA,QACJ,OAAO9B,EAAA,CAAA,EAAC,iBAAA;AAAA,QACT,MAAK;AAAA,QACJ,gCAAOf,EAAI,SAAA;AAAA,MAAA;MAGd8C,EAME/B,EAAAgC,CAAA,GAAA;AAAA,oBALSb,EAAA;AAAA,sDAAAA,EAAM,QAAAjB;AAAA,QACf,OAAM;AAAA,QACL,aAAaF,EAAA,CAAA,EAAC,gBAAA;AAAA,QACf,WAAA;AAAA,QACA,MAAK;AAAA,MAAA;MAGalB,EAAA,8BAApBiB,EAacC,EAAAiC,EAAA,GAAA,EAAA,KAAA,KAAA;AAAA,QAZD,WACT,MAAwF;AAAA,UAAxFF,EAAwF/B,EAAA8B,CAAA,GAAA;AAAA,YAA7E,MAAK;AAAA,YAAU,OAAK,GAAK9B,EAAA,CAAA,EAAC,mBAAA,CAAA,KAA0ByB,EAAA,KAAS;AAAA,YAAI,MAAK;AAAA,UAAA;;mBAIjF,MAAuB;AAAA,kBADzBZ,EAOmBC,IAAA,MAAAC,GANAO,EAAA,OAAK,CAAfY,YADTnC,EAOmBC,EAAAmC,EAAA,GAAA;AAAA,YALhB,KAAK,OAAOD,EAAO,KAAK;AAAA,YACxB,QAAQA,EAAO,UAAUd,EAAA;AAAA,YACzB,SAAK,CAAAlB,MAAEkB,EAAA,QAAOc,EAAO;AAAA,UAAA;uBAEtB,MAAkB;AAAA,cAAfE,EAAAC,EAAAH,EAAO,KAAK,GAAA,CAAA;AAAA,YAAA;;;;;;MAInBH,EAcc/B,EAAAiC,EAAA,GAAA,MAAA;AAAA,QAbD,WAET,MAA4D;AAAA,UAA5DF,EAA4D/B,EAAA8B,CAAA,GAAA;AAAA,YAAjD,MAAK;AAAA,YAAc,OAAOH,EAAA;AAAA,YAAW,MAAK;AAAA,UAAA;;mBAIrD,MAAuB;AAAA,kBADzBd,EAOmBC,IAAA,MAAAC,GANAS,EAAA,OAAK,CAAfU,YADTnC,EAOmBC,EAAAmC,EAAA,GAAA;AAAA,YALhB,KAAKD,EAAO;AAAA,YACZ,QAAQA,EAAO,UAAUb,EAAA;AAAA,YACzB,SAAK,CAAAnB,MAAEmB,EAAA,QAAOa,EAAO;AAAA,UAAA;uBAEtB,MAAkB;AAAA,cAAfE,EAAAC,EAAAH,EAAO,KAAK,GAAA,CAAA;AAAA,YAAA;;;;;;MAKXpD,EAAA,kBADRiB,EAMEC,EAAA8B,CAAA,GAAA;AAAA;QAJA,MAAK;AAAA,QACJ,OAAO9B,EAAA,CAAA,EAAC,gBAAA;AAAA,QACT,MAAK;AAAA,QACJ,gCAAOf,EAAI,QAAA;AAAA,MAAA;MAKIH,EAAA,kBAAlBiB,EAaaC,EAAAsC,EAAA,GAAA;AAAA;QAbgB,MAAK;AAAA,MAAA;mBAChC,MAKE;AAAA,UALFP,EAKE/B,EAAA8B,CAAA,GAAA;AAAA,YAJA,MAAK;AAAA,YACJ,OAAO9B,EAAA,CAAA,EAAC,cAAA;AAAA,YACR,UAAUnB,EAAM,aAAQ;AAAA,YACxB,gCAAOI,EAAI,MAAA;AAAA,UAAA;UAEd8C,EAKE/B,EAAA8B,CAAA,GAAA;AAAA,YAJA,MAAK;AAAA,YACJ,OAAO9B,EAAA,CAAA,EAAC,gBAAA;AAAA,YACR,UAAUnB,EAAM,aAAQ;AAAA,YACxB,gCAAOI,EAAI,QAAA;AAAA,UAAA;;;;;;;;;;;;;;ACtHpB,UAAMJ,IAAQC,GAOR,EAAE,MAAAyD,GAAM,SAAAhF,GAAS,SAAAiF,EAAA,IAAYC,GAAA,GAE7BtD,IAAIC,EAAa,YAAY,GAE7BsD,IAAUtC;AAAA,MAAS,MACvBuC,EAAQ9D,EAAM,WAAW,EAAE,OAAO,CAACqD,MAAWA,EAAO,UAAUrD,EAAM,IAAI;AAAA,IAAA,GAErE+D,IAAStD,EAAmBoD,EAAQ,MAAM,CAAC,GAAG,SAAS,IAAI;AAEjE,aAASC,EAAQlD,GAAmE;AAClF,aAAOA,EAAY,QAAQ,CAACG,MAAc;AAAA,QACxC;AAAA,UACE,OAAOA,EAAU;AAAA;AAAA,UAEjB,OACE,IAAI,OAAOA,EAAU,QAAQ,CAAC,KAAKA,EAAU,UAAUT,EAAE,cAAc,IAAIS,EAAU;AAAA,QAAA;AAAA,QAEzF,GAAG+C,EAAQ/C,EAAU,YAAY,CAAA,CAAE;AAAA,MAAA,CACpC;AAAA,IACH;2BAIEG,EAWYC,EAAA6C,EAAA,GAAA;AAAA,MAXO,MAAM7C,EAAAuC,CAAA;AAAA,sDAAAA,EAAI,QAAArC,IAAA;AAAA,MAAG,OAAOF,EAAAb,CAAA,EAAC,iBAAA;AAAA,MAAsB,OAAO;AAAA,IAAA;MAGxD,UACT,MAKW;AAAA,QALX4C,EAKW/B,EAAA8C,EAAA,GAAA,EALD,MAAK,QAAI;AAAA,qBACjB,MAAqF;AAAA,YAArFf,EAAqF/B,EAAA+C,CAAA,GAAA;AAAA,cAA1E,SAAQ;AAAA,cAAW,gCAAO/C,EAAAwC,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAyB;AAAA,oBAAtBxC,EAAAb,CAAA,EAAC,gBAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpD4C,EAEY/B,EAAA+C,CAAA,GAAA;AAAA,cAFD,MAAK;AAAA,cAAW,UAAUH,EAAA,UAAM;AAAA,cAAY,SAAKI,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAA9C,MAAEF,EAAAzC,CAAA,EAAQqF,EAAA,KAAM;AAAA,YAAA;yBAC1E,MAAuB;AAAA,oBAApB5C,EAAAb,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBANV,MAAiD;AAAA,QAAjD4C,EAAiD/B,EAAAiD,EAAA,GAAA;AAAA,sBAA7BL,EAAA;AAAA,wDAAAA,EAAM,QAAA1C;AAAA,UAAG,SAASwC,EAAA;AAAA,QAAA;;;;;;;;;;;;;;AChC1C,UAAM7D,IAAQC,GAUR,EAAE,MAAAyD,GAAM,SAAAhF,GAAS,SAAAiF,EAAA,IAAYC,GAAA,GAE7BtD,IAAIC,EAAa,YAAY,GAE7BtB,IAAOwB,EAAIT,EAAM,KAAK,GACtBqE,IAAQC,GAA4B,OAAO;AAEjD,IAAAC,GAAU,YAAY;AACpB,YAAMC,GAAA,GACNH,EAAM,OAAO,cAAc,OAAO,GAAG,MAAA;AAAA,IACvC,CAAC;AAED,aAASI,IAAe;AACtB,YAAMC,IAAQzF,EAAK,MAAM,KAAA;AAEzB,MAAIyF,MAAU,MACZhG,EAAQgG,CAAK;AAAA,IAEjB;2BAIExD,EAoBYC,EAAA6C,EAAA,GAAA;AAAA,MApBO,MAAM7C,EAAAuC,CAAA;AAAA,sDAAAA,EAAI,QAAArC,IAAA;AAAA,MAAG,OAAOpB,EAAA;AAAA,MAAQ,OAAO;AAAA,IAAA;MAYzC,UACT,MAKW;AAAA,QALXiD,EAKW/B,EAAA8C,EAAA,GAAA,EALD,MAAK,QAAI;AAAA,qBACjB,MAAqF;AAAA,YAArFf,EAAqF/B,EAAA+C,CAAA,GAAA;AAAA,cAA1E,SAAQ;AAAA,cAAW,gCAAO/C,EAAAwC,CAAA,EAAA;AAAA,YAAO;yBAAI,MAAyB;AAAA,oBAAtBxC,EAAAb,CAAA,EAAC,gBAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YACpD4C,EAEY/B,EAAA+C,CAAA,GAAA;AAAA,cAFD,MAAK;AAAA,cAAW,UAAUjF,EAAA,MAAK,KAAA,MAAI;AAAA,cAAY,SAAOwF;AAAA,YAAA;yBAC/D,MAAsC;AAAA,gBAAnClB,EAAAC,EAAAvD,EAAA,eAAekB,EAAAb,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBAfzB,MASM;AAAA,QATNqE,EASM,OAAA;AAAA,mBATG;AAAA,UAAJ,KAAIN;AAAA,QAAA;UAGPnB,EAKE/B,EAAAgC,CAAA,GAAA;AAAA,wBAJSlE,EAAA;AAAA,0DAAAA,EAAI,QAAAoC;AAAA,YACZ,aAAapB,EAAA,SAASkB,EAAAb,CAAA,EAAC,qBAAA;AAAA,YACvB,cAAYL,EAAA,SAASkB,EAAAb,CAAA,EAAC,qBAAA;AAAA,YACtB,YAAamE,GAAM,CAAA,OAAA,CAAA;AAAA,UAAA;;;;;;IC1CfG,KAA0C;AAAA,EACrD,QAAQ;AAAA,IACN,OAAO;AAAA,EAAA;AAAA,EAET,SAAS;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,eAAe;AAAA,IACf,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,OAAO;AAAA,IACP,gBAAgB;AAAA,IAChB,aAAa;AAAA,IACb,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU;AAAA,IACV,OAAO;AAAA,IACP,eAAe;AAAA,IACf,eAAe;AAAA,IACf,aAAa;AAAA,IACb,aAAa;AAAA,IACb,mBAAmB;AAAA,IACnB,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAAA;AAAA,EAGV,OAAO;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,cAAc;AAAA,IACd,eAAe;AAAA,IACf,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,KAAK;AAAA,IACL,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,kBAAkB;AAAA,IAClB,OAAO;AAAA,IACP,YAAY;AAAA,EAAA;AAAA;AAAA;AAAA,EAId,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,EAAA;AAAA,EAET,SAAS;AAAA,IACP,sBAAsB;AAAA,IACtB,qBAAqB;AAAA,IACrB,uBAAuB;AAAA,IACvB,sBAAsB;AAAA,IACtB,0BAA0B;AAAA,IAC1B,yBACE;AAAA,IACF,SAAS;AAAA,IACT,eAAe;AAAA,EAAA;AAAA,EAEjB,QAAQ;AAAA,IACN,uBAAuB;AAAA,IACvB,kBAAkB;AAAA,IAClB,yBAAyB;AAAA,IACzB,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,oBAAoB;AAAA,IACpB,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,EAAA;AAAA,EAEpB,OAAO;AAAA,IACL,MAAM;AAAA,EAAA;AAAA,EAER,YAAY;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,cAAc;AAAA,IACd,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,UAAU;AAAA,MACV,OAAO;AAAA,IAAA;AAAA,EACT;AAEJ,GC1IMC,yBAAa,QAAA;AASZ,SAASC,KAAyB;AACvC,MAAI;AACF,UAAM,EAAE,MAAAC,EAAA,IAASC,GAAA;AAEjB,IAAKH,GAAO,IAAIE,CAAI,MAClBA,EAAK,SAAS,cAAcH,EAAa,GACzCC,GAAO,IAAIE,CAAI;AAAA,EAEnB,QAAQ;AAAA,EAER;AACF;;;;;;;;;;;;;;;;;;ACWA,UAAM/E,IAAQC,GA4BRG,IAAOC,GAEPzC,IAAQoH,GAAA,GACRC,IAAMtH,GAAeC,CAAK;AAChC,IAAAkH,GAAA;AAEA,UAAM,IAAIvE,EAAa,YAAY,GAE7B2E,IAAUC,GAAA;AAQhB,aAASC,IAAuC;AAC9C,aAAO;AAAA,QACL,OAAO,EAAE,cAAc;AAAA,QACvB,WAAW,EAAE,aAAa;AAAA,QAC1B,aAAa,EAAE,eAAe;AAAA,QAC9B,YAAY,EAAE,cAAc;AAAA,QAC5B,iBAAiB,EAAE,oBAAoB;AAAA,QACvC,kBAAkB,EAAE,qBAAqB;AAAA,QACzC,qBAAqB,EAAE,wBAAwB;AAAA,QAC/C,mBAAmB,EAAE,sBAAsB;AAAA,QAC3C,YAAY,EAAE,cAAc;AAAA,QAC5B,WAAW,EAAE,aAAa;AAAA,QAC1B,aAAa,EAAE,eAAe;AAAA,QAC9B,YAAY,EAAE,oBAAoB;AAAA,QAClC,YAAY,EAAE,cAAc;AAAA,QAC5B,aAAa,EAAE,eAAe;AAAA,QAC9B,WAAW,EAAE,aAAa;AAAA,QAC1B,eAAe,EAAE,iBAAiB;AAAA,QAClC,WAAW,EAAE,cAAc;AAAA,MAAA;AAAA,IAE/B;AAEA,UAAMC,IAAOf,GAA4B,MAAM,GACzClF,IAAQkG,GAAgBD,CAAI,GAC5BE,IAAUhE,EAAS,MAAMnC,EAAM,QAAQ,KAAKA,EAAM,QAAQ,GAAG,GAE7DwB,IAAcH,EAAsB,EAAE,GACtC+E,IAAU/E,EAAmB,IAAI,GACjCgF,IAAOhF,EAAsB,IAAI,GACjCP,IAAWO,EAAc,EAAE,GAC3B6B,IAAS7B,EAAI,EAAE,GAKf8B,IAAOpC,EAA8BF,GAAC,MAA0B;AAEtE,IAAID,EAAM,WAAQuC,EAAK,QAAQvC,EAAM,SAErCW;AAAA,MACE,MAAMX,EAAM;AAAA,MACZ,CAAC0C,MAAS;AACR,QAAAH,EAAK,QAAQG,KAAQ;AAAA,MACvB;AAAA,IAAA;AAEF,UAAMF,IAAO/B,EAAI,aAAa,GACxBiF,KAAOjF,EAAI,EAAK,GAChBkF,IAAclF,EAAI,EAAK,GACvBmF,KAAStB,GAAiC,QAAQ,GAElDuB,KAAOtE,EAAS,MAAMkE,EAAK,OAAO,QAAQ,EAAE,GAC5CK,IAAYvE,EAAS,MAAM3D,EAAM,IAAI,cAAc,CAAC,GACpDmI,KAAYxE,EAAS,MAAM3D,EAAM,IAAI,cAAc,KAAKkI,EAAU,KAAK,GACvEE,IAASzE,EAAS,MAAM0E,GAAKT,EAAQ,KAAK,CAAC,GAE3CU,KAAW3E,EAAS,MAAMvB,EAAM,WAAW,CAACA,EAAM,QAAQ;AAShE,IAAAW,EAAMT,GAAU,CAAChB,MAAQ;AACvB,UAAIc,EAAM,QAAQ,QAAQA,EAAM,MAAM,KAAKd,EAAI,SAASc,EAAM,KAAK;AACjE,QAAAE,EAAS,QAAQhB,EAAI,MAAM,GAAGc,EAAM,GAAG;AAEvC;AAAA,MACF;AAIA,MAAAI;AAAA,QACE;AAAA,QACAyF,GAAK,MAAM,OAAO,CAACpH,MAASS,EAAI,SAAST,EAAK,EAAE,CAAC;AAAA,MAAA;AAAA,IAErD,CAAC,GAED8F,GAAU4B,CAAI,GAEdxF,EAAM,CAAC6E,GAASjD,GAAMC,CAAI,GAAG,MAAM4D,EAAU,CAAC,CAAC,GAC/CzF;AAAA,MACE2B;AAAA,MACA+D,GAAS,MAAMD,EAAU,CAAC,GAAG,GAAG;AAAA,IAAA,GAElCzF,EAAM4E,GAAS,CAACe,MAAW;AACzB,MAAKA,MACHX,EAAY,QAAQ;AAAA,IAExB,CAAC;AAED,mBAAeQ,IAAsB;AACnC,MAAAvF,EAAY,QAAQ,MAAMqE,EAAI,YAAA,GAC9BO,EAAQ,UAAU5E,EAAY,MAAM,CAAC,GAAG,MAAM,MAC9C,MAAMwF,EAAU,CAAC;AAAA,IACnB;AAEA,mBAAeA,EAAUG,IAAKd,EAAK,OAAO,KAAK,gBAAgB,GAAkB;AAC/E,MAAAC,GAAK,QAAQ;AAEb,UAAI;AACF,QAAAD,EAAK,QAAQ,MAAMR,EAAI,MAAM;AAAA,UAC3B,cAAc3C,EAAO,QAAQ,OAAOkD,EAAQ;AAAA,UAC5C,GAAGlD,EAAO;AAAA,UACV,MAAMC,EAAK,UAAU,QAAQ,OAAOA,EAAK;AAAA,UACzC,MAAMC,EAAK;AAAA,UACX,MAAM+D;AAAA,QAAA,CACP,GACDrG,EAAS,QAAQ,CAAA;AAAA,MACnB,UAAA;AACE,QAAAwF,GAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAEA,aAASc,IAAe;AACtB,MAAAZ,GAAO,OAAO,MAAA;AAAA,IAChB;AAQA,mBAAea,EAAO1H,GAA6B;AACjD,YAAMsF,IAAQtF,EAAM,QACd2H,IAAS,MAAM,KAAKrC,EAAM,SAAS,CAAA,CAAE;AAK3C,UAFAA,EAAM,QAAQ,IAEV,EAAAqC,EAAO,WAAW,KAAKlB,EAAQ,UAAU;AAI7C,YAAI;AACF,gBAAMmB,IAAS,MAAM1B,EAAI,OAAOO,EAAQ,OAAOkB,CAAM,GAC/CE,IAAaD,EAAO,OAAO,CAAClI,OAASA,GAAK,SAAS,EAAE;AAE3D,UAAIkI,EAAO,SAASC,KAClBnF,EAAM,QAAQ,EAAE,oBAAoB,EAAE,OAAOkF,EAAO,SAASC,EAAA,CAAY,CAAC,GAGxEA,IAAa,KACfnF,EAAM,KAAK,EAAE,yBAAyB,CAAC,GAGzC,MAAM,QAAQ,IAAI,CAAC0E,EAAA,GAAQC,EAAU,CAAC,CAAC,CAAC;AAAA,QAC1C,SAASS,GAAO;AACd,UAAApF,EAAM,OAAOyD,EAAQ2B,GAAO,EAAE,eAAe,CAAC,CAAC;AAAA,QACjD;AAAA,IACF;AAEA,UAAMC,IAAUC,GAGdC,EAAU;AAEZ,mBAAeC,KAA8B;AAC3C,YAAMhJ,IAAQ,MAAM6I,EAAQ;AAAA,QAC1B,OAAO,EAAE,oBAAoB;AAAA,QAC7B,aAAa,EAAE,oBAAoB;AAAA,MAAA,CACpC;AAED,MAAI7I,MAAU,UAAauH,EAAQ,UAAU,SAI7C,MAAMP,EAAI,gBAAgBO,EAAQ,OAAOvH,CAAK,GAC9C,MAAMkI,EAAA;AAAA,IACR;AAEA,mBAAee,KAA8B;AAC3C,YAAMnD,IAASiC,EAAO;AAEtB,UAAI,CAACjC,KAAUA,EAAO;AACpB;AAGF,YAAM9F,IAAQ,MAAM6I,EAAQ;AAAA,QAC1B,OAAO,EAAE,gBAAgB;AAAA,QACzB,OAAO/C,EAAO;AAAA,QACd,aAAa,EAAE,cAAc;AAAA,MAAA,CAC9B;AAED,MAAI9F,MAAU,WACZ,MAAMgH,EAAI,gBAAgBlB,EAAO,IAAI9F,CAAK,GAC1C,MAAMkI,EAAA;AAAA,IAEV;AAEA,mBAAegB,KAA8B;AAC3C,YAAMpD,IAASiC,EAAO;AAiBtB,UAfI,GAACjC,KAAUA,EAAO,WAelB,CARU,MAAMqD,GAAQ;AAAA,QAC1B,OAAO,EAAE,+BAA+B,EAAE,OAAOrD,EAAO,OAAO;AAAA,QAC/D,SAAS,EAAE,4BAA4B;AAAA,QACvC,aAAa,EAAE,iBAAiB;AAAA,QAChC,YAAY,EAAE,gBAAgB;AAAA,QAC9B,MAAM;AAAA,MAAA,CACP,IAMD;AAAA,YAAI;AACF,gBAAMkB,EAAI,gBAAgBlB,EAAO,EAAE;AAAA,QACrC,SAAS8C,GAAO;AACd,gBAAMQ,IAASC,GAAST,CAAK;AAE7B,cAAI,CAACQ,GAAQ;AACX,YAAA5F,EAAM,OAAOyD,EAAQ2B,CAAK,CAAC;AAE3B;AAAA,UACF;AAUA,cAAI,CARW,MAAMO,GAAQ;AAAA,YAC3B,OAAO,EAAE,+BAA+B,EAAE,OAAOrD,EAAO,OAAO;AAAA,YAC/D,SAAS,GAAG,EAAE,kCAAkCsD,CAAM,CAAC,IAAI,EAAE,+BAA+B,CAAC;AAAA,YAC7F,aAAa,EAAE,iBAAiB;AAAA,YAChC,YAAY,EAAE,gBAAgB;AAAA,YAC9B,MAAM;AAAA,UAAA,CACP;AAGC;AAGF,gBAAMpC,EAAI,gBAAgBlB,EAAO,IAAI,EAAI;AAAA,QAC3C;AAEA,QAAAyB,EAAQ,QAAQ,MAChB,MAAMW,EAAA;AAAA;AAAA,IACR;AAEA,mBAAeoB,GAAWrJ,GAAYF,GAAiC;AACrE,UAAI;AACF,cAAMiH,EAAI,cAAc/G,GAAIF,CAAQ;AAAA,MACtC,SAAS6I,GAAO;AACd,QAAApF,EAAM,OAAOyD,EAAQ2B,GAAO,EAAE,8BAA8B,CAAC,CAAC;AAAA,MAChE;AAEA,YAAMV,EAAA;AAAA,IACR;AAEA,UAAMqB,KAAaT,GAGjBU,EAAU;AAEZ,mBAAeC,KAA8B;AAC3C,YAAMnB,IAAK,MAAMiB,GAAW;AAAA,QAC1B,aAAa5G,EAAY;AAAA,QACzB,MAAM4E,EAAQ;AAAA,QACd,OAAOtF,EAAS,MAAM;AAAA,MAAA,CACvB;AAED,MAAIqG,MAAO,WAIX,MAAMtB,EAAI,KAAK,CAAC,GAAG/E,EAAS,KAAK,GAAGqG,CAAE,GACtC,MAAM,QAAQ,IAAI,CAACJ,KAAQC,EAAA,CAAW,CAAC;AAAA,IACzC;AAEA,mBAAeuB,KAAgC;AAC7C,YAAMzI,IAAM,CAAC,GAAGgB,EAAS,KAAK;AAU9B,MARe,MAAMkH,GAAQ;AAAA,QAC3B,OAAO,EAAE,8BAA8B,EAAE,OAAOlI,EAAI,QAAQ;AAAA,QAC5D,SAAS,EAAE,2BAA2B;AAAA,QACtC,aAAa,EAAE,iBAAiB;AAAA,QAChC,YAAY,EAAE,gBAAgB;AAAA,QAC9B,MAAM;AAAA,MAAA,CACP,MAGC,MAAM+F,EAAI,OAAO/F,CAAG,GACpB,MAAM,QAAQ,IAAI,CAACiH,KAAQC,EAAA,CAAW,CAAC;AAAA,IAE3C;AAEA,mBAAewB,GAAOnJ,GAAiBQ,GAA6B;AAClE,YAAMgG,EAAI,OAAOxG,EAAK,IAAIQ,CAAI,GAC9B,MAAMmH,EAAA;AAAA,IACR;AAEA,mBAAeyB,GAAOpJ,GAAgC;AACpD,YAAMwG,EAAI,UAAUxG,EAAK,EAAE,GAC3B,MAAM,QAAQ,IAAI,CAAC0H,KAAQC,EAAA,CAAW,CAAC;AAAA,IACzC;AAUA,mBAAe0B,GAAKrJ,GAAgC;AAKlD,YAAMsJ,IAAS,MAAMC,GAAgB;AAAA,QACnC,KAAKvJ,EAAK,UAAUA,EAAK;AAAA,QACzB,UAAUA,EAAK;AAAA,QACf,SAAS;AAAA;AAAA;AAAA;AAAA,QAIT,GAAG2G,EAAA;AAAA,MAAa,CACjB;AAED,MAAK2C,MAIL,MAAM9C,EAAI,KAAKxG,EAAK,IAAI;AAAA;AAAA;AAAA,QAGtB,MAAMsJ,EAAO;AAAA,QACb,SAAWA,EAAO,WAAW,MAAO,OAAO;AAAA,QAC3C,MAAMA,EAAO,QAAQ,eAAeA,EAAO,QAAQ,aAAa;AAAA,QAChE,QAAQ,EAAE,OAAOA,EAAO,OAAO,QAAQA,EAAO,OAAA;AAAA,MAAO,CACtD,GAID,MAAM3B,EAAA;AAAA,IACR;AAQA,aAAS1C,GAAKjF,GAAuB;AACnC,MAAKuB,EAAM,YACTI,EAAK,QAAQ3B,CAAI;AAAA,IAErB;AAEA,aAASwH,GAAK/H,GAA0C;AACtD,YAAM+J,IAAO,CAACzH,MAAmD;AAC/D,mBAAW0H,KAAQ1H,GAAO;AACxB,cAAI0H,EAAK,OAAOhK;AACd,mBAAOgK;AAGT,gBAAMC,IAASF,EAAKC,EAAK,YAAY,CAAA,CAAE;AAEvC,cAAIC;AACF,mBAAOA;AAAA,QAEX;AAEA,eAAO;AAAA,MACT;AAEA,aAAOjK,MAAO,OAAO,OAAO+J,EAAKrH,EAAY,KAAK;AAAA,IACpD;AAEA,aAAS0G,GAAST,GAA+D;AAC/E,YAAM9I,IACJ8I,GACC;AAEH,aAAO9I,GAAM,SAAS,wBAAyBA,EAAK,UAAU,OAAQ;AAAA,IACxE;AAGA,IAAAqK,EAAa,EAAE,QAAQ5B,GAAQ;AAE/B,aAASH,GAASgC,GAAiBC,GAA0B;AAC3D,UAAIC;AAEJ,aAAO,MAAM;AACX,qBAAaA,CAAK,GAClBA,IAAQ,WAAWF,GAAKC,CAAI;AAAA,MAC9B;AAAA,IACF;2BAIEtG,EAoHM,OAAA;AAAA,eApHG;AAAA,MAAJ,KAAIqD;AAAA,MAAO,OAAKmD,GAAA,CAAC,YAAU,EAAA,qBAAgCjD,EAAA,OAAO,CAAA;AAAA,IAAA;MACvDA,EAAA,qBAAdxC,KAAAf,EAkBQ,SAlBRgB,IAkBQ;AAAA,QAjBNE,EAA2FuF,IAAA;AAAA,UAAnE,UAAUjD,EAAA;AAAA,sDAAAA,EAAO,QAAAnE;AAAA,UAAG,aAAaT,EAAA;AAAA,UAAc,QAAM2G;AAAA,QAAA;QAE3DzB,EAAA,cAAlB5E,EAcaC,EAAAsC,EAAA,GAAA;AAAA;UAdgB,MAAK;AAAA,UAAK,OAAM;AAAA,QAAA;qBAC3C,MAA+E;AAAA,YAA/EP,EAA+E/B,EAAA8B,CAAA,GAAA;AAAA,cAApE,MAAK;AAAA,cAAO,OAAO9B,EAAA,CAAA,EAAC,oBAAA;AAAA,cAAyB,SAAO8F;AAAA,YAAA;YAC/D/D,EAKE/B,EAAA8B,CAAA,GAAA;AAAA,cAJA,MAAK;AAAA,cACJ,OAAO9B,EAAA,CAAA,EAAC,gBAAA;AAAA,cACR,UAAQ,CAAG6E,EAAA,SAAUA,EAAA,MAAO;AAAA,cAC5B,SAAOkB;AAAA,YAAA;YAEVhE,EAKE/B,EAAA8B,CAAA,GAAA;AAAA,cAJA,MAAK;AAAA,cACJ,OAAO9B,EAAA,CAAA,EAAC,gBAAA;AAAA,cACR,UAAQ,CAAG6E,EAAA,SAAUA,EAAA,MAAO;AAAA,cAC5B,SAAOmB;AAAA,YAAA;;;;;MAKdxC,EAiEU,WAjEV+D,IAiEU;AAAA,QA1DRxF,EAaEyF,IAAA;AAAA,UAZQ,QAAQrG,EAAA;AAAA,oDAAAA,EAAM,QAAAjB;AAAA,UACd,MAAMkB,EAAA;AAAA,kDAAAA,EAAI,QAAAlB;AAAA,UACV,MAAMmB,EAAA;AAAA,kDAAAA,EAAI,QAAAnB;AAAA,UACjB,cAAY0E,GAAA,SAAS,CAAK9F,EAAA;AAAA,UAC1B,cAAYA,EAAA,WAAM,QAAaA,EAAA;AAAA,UAC/B,cAAY6F,EAAA;AAAA,UACZ,UAAU5F,EAAA,MAAS;AAAA,UACnB,SAASqF,EAAA;AAAA,UACT,UAAQiB;AAAA,UACR,QAAMkB;AAAA,UACN,UAAQC;AAAA,UACR,kCAAShC,EAAA,QAAW;AAAA,QAAA;QAOvBhB,EAOE,SAAA;AAAA,mBANI;AAAA,UAAJ,KAAIiB;AAAA,UACJ,MAAK;AAAA,UACL,UAAA;AAAA,UACA,QAAA;AAAA,UACC,QAAQ3F,EAAA,WAAM,OAAY,YAAeA,EAAA,MAAM;AAAA,UAC/C,UAAQwG;AAAA,QAAA;QAGXvD,EAUE0F,IAAA;AAAA,UATQ,UAAU1I,EAAA;AAAA,sDAAAA,EAAQ,QAAAmB;AAAA,UACzB,OAAOwE,GAAA;AAAA,UACP,KAAK1E,EAAA8D,CAAA;AAAA,UACL,OAAO3C,EAAA;AAAA,UACP,QAAQ4D,GAAA;AAAA,UACR,UAAQ0B;AAAA,UACR,QAAME;AAAA,UACN,UAAQD;AAAA,UACR,QAAMnE;AAAA,QAAA;QAGTiB,EAMS,UANTkE,IAMS;AAAA,UALPlE,EAA+E,gBAAtExD,EAAA,CAAA,EAAC,wBAAA,EAAA,OAAkCsE,SAAM,MAAM,SAAK,GAAA,CAAA,GAAA,CAAA;AAAA,UACjDvF,EAAA,MAAS,SAAM,KAA3B6C,EAAA,GAAAf,EAEO,QAAA8G,IAAAtF,EADFrC,EAAA,CAAA,EAAC,2BAAA,EAAA,OAAqCjB,EAAA,MAAS,OAAA,CAAM,CAAA,GAAA,CAAA;UAE1DyE,EAAsF,QAAA,MAAAnB,EAA7ErC,KAAC,uBAAA,EAAA,MAAgCA,EAAAzB,EAAA,EAAS+F,EAAA,OAAM,MAAM,QAAI,CAAA,GAAA,CAAA,GAAA,CAAA;AAAA,QAAA;QAI7DA,EAAA,SAAQA,EAAA,MAAK,KAAK,YAAS,UADnCvE,EASEC,EAAA4H,EAAA,GAAA;AAAA;UAPC,MAAMtD,EAAA,MAAK,KAAK;AAAA,UAChB,YAAUA,EAAA,MAAK,KAAK;AAAA,UACpB,OAAOA,EAAA,MAAK,KAAK;AAAA,UACjB,aAAWA,EAAA,MAAK,KAAK;AAAA,UACrB,UAAUC,GAAA;AAAA,UACX,MAAK;AAAA,UACJ,UAAMvB,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAA,EAAA,MAAWoC,QAASH,EAAUG,CAAE;AAAA,QAAA;;MAM3CrD,EAyBY/B,EAAA6H,EAAA,GAAA;AAAA,QAzBO,MAAMrD,EAAA;AAAA,gDAAAA,EAAW,QAAAtE;AAAA,QAAE,MAAK;AAAA,QAAQ,OAAOF,EAAA,CAAA,EAAC,iBAAA;AAAA,QAAsB,MAAM;AAAA,MAAA;mBACrF,MAuBM;AAAA,UAvBNwD,EAuBM,OAvBNsE,IAuBM;AAAA,YAtBJ/F,EAKEuF,IAAA;AAAA,cAJQ,UAAUjD,EAAA;AAAA;uCAAAA,EAAO,QAAAnE;AAAA,uCAGPsE,EAAA,QAAW;AAAA,cAAA;AAAA,cAF5B,aAAa/E,EAAA;AAAA,cACb,QAAM2G;AAAA,YAAA;YAISzB,EAAA,cAAlB5E,EAcaC,EAAAsC,EAAA,GAAA;AAAA;cAdgB,MAAK;AAAA,cAAK,OAAM;AAAA,YAAA;yBAC3C,MAA+E;AAAA,gBAA/EP,EAA+E/B,EAAA8B,CAAA,GAAA;AAAA,kBAApE,MAAK;AAAA,kBAAO,OAAO9B,EAAA,CAAA,EAAC,oBAAA;AAAA,kBAAyB,SAAO8F;AAAA,gBAAA;gBAC/D/D,EAKE/B,EAAA8B,CAAA,GAAA;AAAA,kBAJA,MAAK;AAAA,kBACJ,OAAO9B,EAAA,CAAA,EAAC,gBAAA;AAAA,kBACR,UAAQ,CAAG6E,EAAA,SAAUA,EAAA,MAAO;AAAA,kBAC5B,SAAOkB;AAAA,gBAAA;gBAEVhE,EAKE/B,EAAA8B,CAAA,GAAA;AAAA,kBAJA,MAAK;AAAA,kBACJ,OAAO9B,EAAA,CAAA,EAAC,gBAAA;AAAA,kBACR,UAAQ,CAAG6E,EAAA,SAAUA,EAAA,MAAO;AAAA,kBAC5B,SAAOmB;AAAA,gBAAA;;;;;;;;;;;;;;;;;;;;;;;AC9iBpB,UAAM,EAAE,MAAAzD,GAAM,SAAAhF,GAAS,SAAAiF,EAAA,IAAYC,GAAA,GAE7BtD,IAAIC,EAAa,YAAY,GAQ7BmG,IAASjG,EAAiB,EAAE;2BAIhCS,EA+BYC,EAAA6C,EAAA,GAAA;AAAA,MA/BO,MAAM7C,EAAAuC,CAAA;AAAA,sDAAAA,EAAI,QAAArC,IAAA;AAAA,MAAG,OAAOpB,EAAA,SAASkB,EAAAb,CAAA,EAAC,cAAA;AAAA,MAAmB,OAAO;AAAA,IAAA;iBACzE,MASM;AAAA,QATNqE,EASM,OATN3B,IASM;AAAA,UARJE,EAOEgG,IAAA;AAAA,YANC,UAAUjJ,EAAA;AAAA,YACV,QAAQA,EAAA,SAAM,OAAUA,EAAA;AAAA,YACxB,UAAUA,EAAA,YAAQ,CAAKA,EAAA;AAAA,YACvB,KAAKA,EAAA;AAAA,YACL,QAAIkE,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAG1F,MAAS0C,EAAAzC,CAAA,EAAQD,CAAI;AAAA,YAC5B,aAAS0F,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAG5F,MAAWmI,EAAA,QAASnI;AAAA,UAAA;;;;;MAIrB0B,EAAA,aAAaA,EAAA;cAAS;AAAA,cACpC,MAgBW;AAAA,UAhBXiD,EAgBW/B,EAAA8C,EAAA,GAAA,EAhBD,MAAK,QAAI;AAAA,uBAGjB,MAEO;AAAA,cAFKhE,EAAA,gBAAgBA,EAAA,MAAG,UAA/B+B,EAEO,QAFP0G,IAEOlF,EADFrC,EAAAb,CAAA,6BAA6BoG,EAAA,MAAO,QAAM,KAAEzG,EAAA,IAAA,CAAG,CAAA,GAAA,CAAA;cAGpDiD,EAAqF/B,EAAA+C,CAAA,GAAA;AAAA,gBAA1E,SAAQ;AAAA,gBAAW,gCAAO/C,EAAAwC,CAAA,EAAA;AAAA,cAAO;2BAAI,MAAyB;AAAA,sBAAtBxC,EAAAb,CAAA,EAAC,gBAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;cACpD4C,EAOY/B,EAAA+C,CAAA,GAAA;AAAA,gBANV,OAAM;AAAA,gBACN,MAAK;AAAA,gBACJ,UAAUwC,EAAA,MAAO,WAAM;AAAA,gBACvB,SAAKvC,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAA9C,MAAEF,EAAAzC,CAAA,EAAQgI,EAAA,KAAM;AAAA,cAAA;2BAEtB,MAAiD;AAAA,sBAA9CvF,EAAAb,CAAA,EAAC,gBAAA,EAAA,OAA0BoG,EAAA,MAAO,OAAA,CAAM,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;;;;;;;;IC1CxCyC,KAAkBpC,GAA2CqC,EAAW,GASxEC,KAAiBtC,GAA4CqC,IAAa;AAAA,EACrF,OAAO,EAAE,UAAU,GAAA;AACrB,CAAC,GASYE,KAAmBvC;AAAA,EAC9BqC;AAAAA,EACA,EAAE,OAAO,EAAE,QAAQ,KAAK;AAC1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACjBA,UAAMpJ,IAAQC,GAiCRsJ,IAAQpJ,EAAyBF,GAAA,YAAsB;AAE7D,IAAA6E,GAAA;AAEA,UAAMxE,IAAIC,EAAa,YAAY,GAM7B3C,IAA6B4L,GAAOC,IAAU,IAAI,GAClDxE,IAAuBrH,IAAQD,GAAeC,CAAK,IAAI,MAGvD8L,IAAiC;AAAA,MACrC,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,IAAA,GAGHC,IAAQpI,EAAuB;AAAA,MACnC,KAAK,MAAO,MAAM,QAAQgI,EAAM,KAAK,IAAIA,EAAM,QAAQ,CAAA;AAAA,MACvD,KAAK,CAACK,MAAS;AACb,QAAAL,EAAM,QAAQK;AAAA,MAChB;AAAA,IAAA,CACD,GAEKC,IAAQtI,EAAS,MAAOvB,EAAM,SAAS,IAAKA,EAAM,OAAO,IAAM,GAE/D8J,IAAOvI,EAAS,MAAMsI,EAAM,UAAU,QAAQF,EAAM,MAAM,UAAUE,EAAM,KAAK,GAW/EtL,IAAQkC,EAAsC,EAAE,GAEhDsJ,wBAAa,IAAA;AAEnB,mBAAerL,EAAQsL,GAAgC;AACrD,UAAI,CAAC/E,EAAK;AAEV,YAAMgF,IAASD,EAAM,OAAO,CAAC3L,MAAS,EAAEA,KAAQE,EAAM,UAAU,CAACwL,EAAO,IAAI1L,CAAI,CAAC;AAEjF,UAAI4L,EAAO,WAAW,EAAG;AAEzB,MAAAA,EAAO,QAAQ,CAAC5L,MAAS0L,EAAO,IAAI1L,CAAI,CAAC;AAEzC,YAAM6L,IAAQ,MAAM,QAAQ;AAAA,QAC1BD,EAAO,IAAI,OAAO5L,MAAS,CAACA,GAAM,MAAM4G,EAAI,WAAW5G,CAAI,CAAC,CAAU;AAAA,MAAA;AAGxE,MAAA4L,EAAO,QAAQ,CAAC5L,MAAS0L,EAAO,OAAO1L,CAAI,CAAC,GAC5CE,EAAM,QAAQ,EAAE,GAAGA,EAAM,OAAO,GAAG,OAAO,YAAY2L,CAAK,EAAA;AAAA,IAC7D;AAEA,IAAAvJ;AAAA,MACE,MAAMgJ,EAAM,MAAM,IAAI,CAACQ,MAASA,EAAK,IAAI;AAAA,MACzC,CAACH,MAAA;AAAU,QAAKtL,EAAQsL,EAAM,OAAO,OAAO,CAAC;AAAA;AAAA,MAC7C,EAAE,WAAW,GAAA;AAAA,IAAK;AAIpB,aAASI,EAAS1D,GAA2B;AAC3C,MAAAnI,EAAM,QAAQ,EAAE,GAAGA,EAAM,OAAO,GAAG,OAAO,YAAYmI,EAAO,IAAI,CAACjI,MAAS,CAACA,EAAK,MAAMA,CAAI,CAAC,CAAC,EAAA;AAAA,IAC/F;AAEA,aAAS4L,EAAOF,GAAgD;AAC9D,aAAO5L,EAAM,MAAM4L,EAAK,IAAI;AAAA,IAC9B;AAGA,aAASG,EAAOH,GAA2B;AACzC,aAAOE,EAAOF,CAAI,MAAM;AAAA,IAC1B;AAEA,aAASI,EAAOJ,GAA0B;AACxC,aAAOE,EAAOF,CAAI,GAAG,QAAQpK,GAAaoK,EAAK,IAAI;AAAA,IACrD;AAMA,aAASK,EAAQL,GAAsC;AACrD,YAAM1L,IAAO4L,EAAOF,CAAI;AAExB,aAAO1L,KAAQwG,IAAOA,EAAI,MAAMxG,GAAM,KAAK,GAAG,KAAK,SAAa;AAAA,IAClE;AAEA,aAASgM,EAAON,GAA0B;AACxC,YAAM1L,IAAO4L,EAAOF,CAAI;AAExB,UAAI,CAAC1L,EAAM,QAAO;AAElB,YAAMoB,IAAOH,GAASjB,EAAK,IAAI;AAE/B,aAAOA,EAAK,SAASA,EAAK,SAAS,GAAGA,EAAK,KAAK,IAAIA,EAAK,MAAM,MAAMoB,CAAI,KAAKA;AAAA,IAChF;AAIA,UAAM6K,IAAUjK,EAAmB,IAAI;AAEvC,aAASkK,GAAOR,GAAkBS,GAAuB;AAKvD,aAFcjB,EAAM,MAAM,UAAU,CAACkB,MAAUA,EAAM,SAASV,EAAK,IAAI,MAEtDS,IAAQT,EAAK,OAAO,GAAGA,EAAK,IAAI,IAAIS,CAAK;AAAA,IAC5D;AAMA,aAASE,EAAMF,GAAeG,GAA+C;AAC3E,aAAOpB,EAAM,MAAMiB,CAAK,IAAIG,CAAG,KAAK,CAAA;AAAA,IACtC;AAEA,aAASC,GAASJ,GAAeG,GAAsBnB,GAAqB;AAC1E,YAAMpE,IAAUmE,EAAM,MAAMiB,CAAK;AAEjC,UAAI,CAACpF,EAAS;AAEd,YAAMyF,IAAO,CAAC,GAAGtB,EAAM,KAAK;AAC5B,MAAAsB,EAAKL,CAAK,IAAI,EAAE,GAAGpF,GAAS,CAACuF,CAAG,GAAGnB,EAAA,GACnCD,EAAM,QAAQsB;AAAA,IAChB;AAEA,mBAAeC,KAAqB;AAClC,UAAIlL,EAAM,SAAU;AAEpB,YAAMmL,IAAOtB,EAAM,UAAU,OAAO,OAAOA,EAAM,QAAQF,EAAM,MAAM,QAC/DjD,IAAS,MAAM2C,GAAe,EAAE,QAAQrJ,EAAM,QAAQ,KAAKmL,GAAM;AAEvE,UAAI,CAACzE,KAAUA,EAAO,WAAW,EAAG;AAEpC,MAAA0D,EAAS1D,CAAM;AAEf,YAAM0E,IAAQ,IAAI,IAAIzB,EAAM,MAAM,IAAI,CAACQ,MAASA,EAAK,IAAI,CAAC,GACpDkB,IAAQ3E,EACX,OAAO,CAACjI,MAAS,CAAC2M,EAAM,IAAI3M,EAAK,IAAI,CAAC,EACtC,IAAgB,CAACA,OAAU,EAAE,MAAMA,EAAK,OAAO;AAIlD,MAAAkL,EAAM,QAAQ3J,EAAM,SAASqL,EAAM,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG1B,EAAM,OAAO,GAAG0B,CAAK;AAAA,IAC5E;AAGA,aAASxD,EAAO+C,GAAqB;AACnC,MAAAjB,EAAM,QAAQA,EAAM,MAAM,OAAO,CAAC2B,GAAGC,MAAOA,MAAOX,CAAK,GACxDF,EAAQ,QAAQ;AAAA,IAClB;AAUA,mBAAec,GAAYrB,GAAiC;AAC1D,YAAMb,GAAiB,CAAA,CAAE;AAEzB,YAAMmC,IAAO,EAAE,GAAGlN,EAAM,MAAA;AACxB,aAAOkN,EAAKtB,EAAK,IAAI,GACrB5L,EAAM,QAAQkN,GAET/M,EAAQ,CAACyL,EAAK,IAAI,CAAC;AAAA,IAC1B;AAIA,UAAMuB,IAAQnK,EAAS,MAAM;AAC3B,YAAMoK,IAAU3L,EAAM,UAClB,UAAUA,EAAM,OAAO,sBACvB;AAEJ,aAAOA,EAAM,SACT;AAAA,QACE,2BAA2B2L;AAAA,QAC3B,0BAA0BjC,EAAO1J,EAAM,MAAM,KAAKA,EAAM;AAAA,MAAA,IAE1D,EAAE,2BAA2B2L,EAAA;AAAA,IACnC,CAAC,GAEKC,KAAOrK,EAAS,MAAM;AAC1B,YAAMsK,IAAQlC,EAAM,MAAM;AAE1B,aAAI3J,EAAM,OAAO6L,IAAQ7L,EAAM,MAAYM,EAAE,kBAAkB,EAAE,OAAON,EAAM,IAAA,CAAK,IAE5E6J,EAAM,UAAU,OAAO,KAAKvJ,EAAE,eAAe,EAAE,OAAAuL,GAAO,KAAKhC,EAAM,MAAA,CAAO;AAAA,IACjF,CAAC;2BAIC7H,EAsHM,OAAA;AAAA,MArHJ,OAAKwG,GAAA,CAAC,iBAAe,CAAA,kBACMvI,EAAA,MAAM,qBAAqBA,EAAA,SAAA,CAAQ,CAAA,CAAA;AAAA,MAC7D,UAAOyL,EAAA,KAAK;AAAA,IAAA;MAEDzL,EAAA,cAAZ+B,EAAkE,QAAlEgB,IAAkEQ,EAAfvD,EAAA,KAAK,GAAA,CAAA;MAExDiD,EAsGmB/B,EAAA2K,EAAA,GAAA;AAAA,oBArGRnC,EAAA;AAAA,sDAAAA,EAAK,QAAAtI;AAAA,QACd,OAAA;AAAA,QACC,UAAUpB,EAAA;AAAA,QACV,QAAQA,EAAA,WAAM,SAAA,QAAA;AAAA,QACd,YAAU0K;AAAA,QACV,cAAYJ;AAAA,QACZ,cAAYpJ,EAAAb,CAAA,EAAC,eAAA;AAAA,QACb,cAAYa,EAAAb,CAAA,EAAC,aAAA;AAAA,MAAA;QAEH,SAAOwB,EAChB,CAyFM,EA1Fc,MAAAqI,GAAM,OAAAS,QAAK;AAAA,UAC/BjG,EAyFM,OAAA;AAAA,YAxFJ,OAAK6D,GAAA,CAAC,uBAAqB,EAAA,aACJ8B,EAAOH,CAAI,GAAA,WAAcO,EAAA,UAAYE,EAAA,CAAK,CAAA;AAAA,UAAA;YAEjE1H,EAYe/B,EAAAgB,EAAA,GAAA;AAAA,cAXZ,MAAMoI,EAAOJ,CAAI;AAAA,cACjB,WAAWK,EAAQL,CAAI;AAAA,cACvB,MAAME,EAAOF,CAAI,GAAG;AAAA,cACpB,MAAMlK,EAAA,WAAM,SAAA,OAAA;AAAA,YAAA;cAEF,QACT,MAEO;AAAA,gBAFKqK,EAAOH,CAAI,UAAvBnI,EAEO,QAFP0G,IAEOlF,EADFrC,EAAAb,CAAA,EAAC,eAAA,CAAA,GAAA,CAAA,WAEN0B,EAAsC,QAAA+J,IAAAvI,EAAtBiH,EAAON,CAAI,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAInBlK,EAAA,wBAAZ8C,KAAAf,EAsEM,OAtEN6G,IAsEM;AAAA,cArEJ3F,EAoEa/B,EAAAsC,EAAA,GAAA,EApED,MAAK,QAAI;AAAA,2BAMnB,MAwCa;AAAA,kBAvCLxD,EAAA,iBADRiB,EAwCaC,EAAA6K,EAAA,GAAA;AAAA;oBAtCV,MAAMtB,EAAA,UAAYE;AAAA,oBAClB,OAAOzJ,EAAAb,CAAA,EAAC,gBAAA;AAAA,oBACR,OAAO;AAAA,oBACR,MAAK;AAAA,oBACL,UAAA;AAAA,oBACC,kBAAcoD,MAAmBgH,UAAUhH,IAAOkH,IAAK;AAAA,kBAAA;oBAE7C,WACT,MAAsD;AAAA,sBAAtD1H,EAAsD/B,EAAA8B,CAAA,GAAA;AAAA,wBAA3C,MAAK;AAAA,wBAAQ,OAAO9B,EAAAb,CAAA,EAAC,gBAAA;AAAA,sBAAA;;+BAGlC,MA0BM;AAAA,sBA1BNqE,EA0BM,OA1BNmE,IA0BM;AAAA,wBAzBJnE,EAQM,OARNsE,IAQM;AAAA,0BAPJ9E,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAAQ,EAAqD,QAAA,EAA/C,OAAM,+BAAA,GAA+B,OAAG,EAAA;AAAA,0BAC9CzB,EAKE/B,EAAAgC,CAAA,GAAA;AAAA,4BAJA,WAAA;AAAA,4BACC,eAAa2H,EAAMF,GAAK,KAAA;AAAA,4BACxB,aAAazJ,EAAAb,CAAA,EAAC,gBAAA;AAAA,4BACd,wBAAqBsJ,MAAkBoB,GAASJ,UAAchB,CAAI;AAAA,0BAAA;;wBAIvEjF,EAQM,OARNsH,IAQM;AAAA,0BAPJ9H,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAAQ,EAAuD,QAAA,EAAjD,OAAM,+BAAA,GAA+B,SAAK,EAAA;AAAA,0BAChDzB,EAKE/B,EAAAgC,CAAA,GAAA;AAAA,4BAJA,WAAA;AAAA,4BACC,eAAa2H,EAAMF,GAAK,OAAA;AAAA,4BACxB,aAAazJ,EAAAb,CAAA,EAAC,kBAAA;AAAA,4BACd,wBAAqBsJ,MAAkBoB,GAASJ,YAAgBhB,CAAI;AAAA,0BAAA;;wBAIzEjF,EAIM,OAJNuH,IAIM;AAAA,0BAHJhJ,EAEY/B,EAAA+C,CAAA,GAAA;AAAA,4BAFD,MAAK;AAAA,4BAAK,MAAK;AAAA,4BAAW,gCAAOwG,EAAA,QAAO;AAAA,0BAAA;uCACjD,MAAuB;AAAA,kCAApBvJ,EAAAb,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,4BAAA;;;;;;;;kBAOHgK,EAAOH,CAAI,sBADpBjJ,EAKEC,EAAA8B,CAAA,GAAA;AAAA;oBAHA,MAAK;AAAA,oBACJ,OAAO9B,EAAAb,CAAA,EAAC,YAAA;AAAA,oBACR,SAAK,CAAAe,MAAEmK,GAAYrB,CAAI;AAAA,kBAAA;kBAG1BjH,EAYgB/B,EAAAgL,EAAA,GAAA;AAAA,oBAXb,OAAOhL,EAAAb,CAAA,EAAC,oBAAA;AAAA,oBACR,aAAaa,EAAAb,CAAA,EAAC,mBAAA;AAAA,oBACd,gBAAca,EAAAb,CAAA,EAAC,sBAAA;AAAA,oBACf,eAAaa,EAAAb,CAAA,EAAC,gBAAA;AAAA,oBACf,gBAAa;AAAA,oBACb,MAAK;AAAA,oBACJ,WAAO,CAAAe,MAAEwG,EAAO+C,CAAK;AAAA,kBAAA;oBAEX,WACT,MAA8D;AAAA,sBAA9D1H,EAA8D/B,EAAA8B,CAAA,GAAA;AAAA,wBAAnD,MAAK;AAAA,wBAAU,OAAO9B,EAAAb,CAAA,EAAC,sBAAA;AAAA,sBAAA;;;;;;;;;;;;MAShDqE,EAMM,OANNyH,IAMM;AAAA,QALJlJ,EAEY/B,EAAA+C,CAAA,GAAA;AAAA,UAFD,MAAK;AAAA,UAAK,SAAQ;AAAA,UAAW,UAAUjE,EAAA,YAAY6J,EAAA;AAAA,UAAO,SAAOoB;AAAA,QAAA;qBAC1E,MAAoB;AAAA,gBAAjB/J,EAAAb,CAAA,EAAC,WAAA,CAAA,GAAA,CAAA;AAAA,UAAA;;;QAGMsL,GAAA,cAAZ5J,EAA+D,QAA/DqK,IAA+D7I,EAAdoI,GAAA,KAAI,GAAA,CAAA;;;;;;;;;;;;;;ACjX3D,UAAMrC,IAAQpJ,EAA8BF,GAAA,YAAkB,GAExDgL,IAAO1J,EAAuB;AAAA,MAClC,KAAK,MAAOgI,EAAM,QAAQ,CAACA,EAAM,KAAK,IAAI,CAAA;AAAA,MAC1C,KAAK,CAACK,MAAS;AACb,QAAAL,EAAM,QAAQK,EAAK,CAAC,KAAK;AAAA,MAC3B;AAAA,IAAA,CACD;2BAIC1I,EAAkEoL,IAAlEC,GAAkEC,EAAA,QAAxC;AAAA,kBAAWvB,EAAA;AAAA,oDAAAA,EAAI,QAAA5J;AAAA,MAAE,QAAO;AAAA,MAAO,QAAA;AAAA,IAAA;;;;;;;;;;;;AChB3D,UAAMkI,IAAQpJ,EAAyBF,GAAA,YAAsB;2BAI3DiB,EAA4DoL,IAA5DC,GAA4DC,EAAA,QAAlC;AAAA,kBAAWjD,EAAA;AAAA,oDAAAA,EAAK,QAAAlI;AAAA,MAAE,QAAO;AAAA,IAAA;;;;;;;;;;;;ACDrD,UAAMkI,IAAQpJ,EAAyBF,GAAA,YAAsB;2BAK3DiB,EAA2EoL,IAA3EC,GAA2EC,EAAA,QAAjD;AAAA,kBAAWjD,EAAA;AAAA,oDAAAA,EAAK,QAAAlI;AAAA,MAAE,QAAO;AAAA,MAAO,QAAO;AAAA,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;ACUnE,UAAMrB,IAAQC,GA4BRyE,IAAQvE,EAA8BF,GAAA,YAAkB;AAE9D,IAAA6E,GAAA;AAEA,UAAMxE,IAAIC,EAAa,YAAY,GAE7B0E,IAAMtH,GAAeqH,IAAU,GAG/B0E,IAAiC;AAAA,MACrC,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,IAAA,GAGHgB,IAAUjK,EAAI,EAAK,GAOnBgM,IAAWhM,EAA4B,EAAE;AAE/C,IAAAE;AAAA,MACE,MAAM+D,EAAM,OAAO;AAAA,MACnB,OAAOrG,MAAS;AACd,YAAI,CAACA,KAAQqG,EAAM,OAAO,OAAO+H,EAAS,MAAMpO,CAAI,EAAG;AAEvD,cAAMI,IAAO,MAAMwG,EAAI,WAAW5G,CAAI;AAEtC,QAAII,MAAMgO,EAAS,QAAQ,EAAE,GAAGA,EAAS,OAAO,CAACpO,CAAI,GAAGI,EAAK,IAAA;AAAA,MAC/D;AAAA,MACA,EAAE,WAAW,GAAA;AAAA,IAAK;AAGpB,UAAMiO,IAAUnL,EAAS,MAAM;AAC7B,YAAMiE,IAAUd,EAAM;AAEtB,aAAKc,IAEEA,EAAQ,OAAOiH,EAAS,MAAMjH,EAAQ,IAAI,KAAK,OAFjC;AAAA,IAGvB,CAAC,GAEKmH,IAAapL;AAAA,MAAS,MAC1BvB,EAAM,SACF,EAAE,aAAa0J,EAAO1J,EAAM,MAAM,KAAKA,EAAM,OAAA,IAC7C,EAAE,QAAQ,OAAOA,EAAM,UAAW,WAAW,GAAGA,EAAM,MAAM,OAAOA,EAAM,OAAA;AAAA,IAAO;AAOtF,aAAS8K,EAAMC,GAA+C;AAC5D,aAAOrG,EAAM,QAAQqG,CAAG,KAAK,CAAA;AAAA,IAC/B;AAEA,aAASC,EAASD,GAAsBnB,GAAqB;AAC3D,MAAKlF,EAAM,UAEXA,EAAM,QAAQ,EAAE,GAAGA,EAAM,OAAO,CAACqG,CAAG,GAAGnB,EAAA;AAAA,IACzC;AAEA,mBAAepD,IAAwB;AACrC,UAAIxG,EAAM,SAAU;AAEpB,YAAMvB,IAAO,MAAM0K,GAAgB,EAAE,QAAQnJ,EAAM,QAAQ;AAE3D,MAAKvB,MASLgO,EAAS,QAAQ,EAAE,GAAGA,EAAS,OAAO,CAAChO,EAAK,IAAI,GAAGA,EAAK,IAAA,GAIxDiG,EAAM,QAAQ,EAAE,GAAGA,EAAM,OAAO,MAAMjG,EAAK,MAAM,KAAKA,EAAK,IAAA;AAAA,IAC7D;AAGA,aAASmO,IAAc;AACrB,MAAAlI,EAAM,QAAQ,MACdgG,EAAQ,QAAQ;AAAA,IAClB;sBAIE3H,EAAA,GAAAf,EAmGM,OAnGNgB,IAmGM;AAAA,MAlGQ/C,EAAA,cAAZ+B,EAAmE,QAAnE0G,IAAmElF,EAAfvD,EAAA,KAAK,GAAA,CAAA;MAE7CyE,EAAA,cAMZ1C,EAyFM,OAAA;AAAA;QAzFM,OAAM;AAAA,QAAyB,UAAO2K,EAAA,KAAU;AAAA,MAAA;QAU1DhI,EASS,UAAA;AAAA,UARP,MAAK;AAAA,UACL,OAAM;AAAA,UACL,UAAU1E,EAAA;AAAA,UACV,OAAOkB,EAAAb,CAAA,EAAC,eAAA;AAAA,UACR,SAAOkG;AAAA,QAAA;UAEQkG,EAAA,cAAhBxL,EAAmFC,EAAA0L,EAAA,GAAA;AAAA;YAAzD,KAAKH,EAAA;AAAA,YAAS,KAAI;AAAA,YAAU,OAAM;AAAA,YAAO,QAAO;AAAA,UAAA,8BAC1E1K,EAA+E,QAA/E8G,IAA+EtF,EAA/BrC,EAAAb,CAAA,EAAC,kBAAA,CAAA,GAAA,CAAA;AAAA,QAAA;QAGvCL,EAAA,wBAAZ8C,KAAAf,EAmEM,OAnENiH,IAmEM;AAAA,UAlEJ/F,EAiEa/B,EAAAsC,EAAA,GAAA,EAjED,MAAK,QAAI;AAAA,uBAKnB,MAyCa;AAAA,cAxCLxD,EAAA,iBADRiB,EAyCaC,EAAA6K,EAAA,GAAA;AAAA;gBAvCH,MAAMtB,EAAA;AAAA,wDAAAA,EAAO,QAAArJ;AAAA,gBACpB,OAAOF,EAAAb,CAAA,EAAC,gBAAA;AAAA,gBACR,OAAO;AAAA,gBACR,MAAK;AAAA,gBACL,UAAA;AAAA,cAAA;gBAEW,WACT,MAAkD;AAAA,kBAAlD4C,EAAkD/B,EAAA8B,CAAA,GAAA;AAAA,oBAAvC,MAAK;AAAA,oBAAQ,OAAO9B,EAAAb,CAAA,EAAC,YAAA;AAAA,kBAAA;;2BAGlC,MA4BM;AAAA,kBA5BNqE,EA4BM,OA5BNsH,IA4BM;AAAA,oBA3BJtH,EASM,OATNuH,IASM;AAAA,sCARJvH,EAA2E,SAAA;AAAA,wBAApE,OAAM;AAAA,wBAAgC,KAAI;AAAA,sBAAA,GAAe,OAAG,EAAA;AAAA,sBACnEzB,EAME/B,EAAAgC,CAAA,GAAA;AAAA,wBALA,IAAG;AAAA,wBACH,WAAA;AAAA,wBACC,eAAa2H,EAAK,KAAA;AAAA,wBAClB,aAAa3J,EAAAb,CAAA,EAAC,gBAAA;AAAA,wBACd,uBAAkB6D,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAGyF,MAASoB,SAAgBpB,CAAI;AAAA,sBAAA;;oBAIvDjF,EASM,OATNyH,IASM;AAAA,sCARJzH,EAA+E,SAAA;AAAA,wBAAxE,OAAM;AAAA,wBAAgC,KAAI;AAAA,sBAAA,GAAiB,SAAK,EAAA;AAAA,sBACvEzB,EAME/B,EAAAgC,CAAA,GAAA;AAAA,wBALA,IAAG;AAAA,wBACH,WAAA;AAAA,wBACC,eAAa2H,EAAK,OAAA;AAAA,wBAClB,aAAa3J,EAAAb,CAAA,EAAC,kBAAA;AAAA,wBACd,uBAAkB6D,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAGyF,MAASoB,WAAkBpB,CAAI;AAAA,sBAAA;;oBAIzDjF,EAIM,OAJN0H,IAIM;AAAA,sBAHJnJ,EAEY/B,EAAA+C,CAAA,GAAA;AAAA,wBAFD,MAAK;AAAA,wBAAK,MAAK;AAAA,wBAAW,gCAAOwG,EAAA,QAAO;AAAA,sBAAA;mCACjD,MAAuB;AAAA,8BAApBvJ,EAAAb,CAAA,EAAC,cAAA,CAAA,GAAA,CAAA;AAAA,wBAAA;;;;;;;;cAUZ4C,EAYgB/B,EAAAgL,EAAA,GAAA;AAAA,gBAXb,OAAOhL,EAAAb,CAAA,EAAC,mBAAA;AAAA,gBACR,aAAaa,EAAAb,CAAA,EAAC,kBAAA;AAAA,gBACd,gBAAca,EAAAb,CAAA,EAAC,qBAAA;AAAA,gBACf,eAAaa,EAAAb,CAAA,EAAC,gBAAA;AAAA,gBACf,gBAAa;AAAA,gBACb,MAAK;AAAA,gBACJ,WAASsM;AAAA,cAAA;gBAEC,WACT,MAA6D;AAAA,kBAA7D1J,EAA6D/B,EAAA8B,CAAA,GAAA;AAAA,oBAAlD,MAAK;AAAA,oBAAU,OAAO9B,EAAAb,CAAA,EAAC,qBAAA;AAAA,kBAAA;;;;;;;;qBA1F5C0B,EAIM,OAAA;AAAA;QAJa,OAAM;AAAA,QAAkC,UAAO2K,EAAA,KAAU;AAAA,MAAA;QAC1EhI,EAES,UAAA;AAAA,UAFD,MAAK;AAAA,UAAS,OAAM;AAAA,UAAwB,UAAU1E,EAAA;AAAA,UAAW,SAAOuG;AAAA,QAAA,KAC3ErF,EAAAb,CAAA,EAAC,cAAA,CAAA,GAAA,GAAAyL,EAAA;AAAA,MAAA;;;;;;ACvIZ,UAAMe,IAAU9H,GAAA;AAChB,IAAAF,GAAA;AAEA,UAAMxE,IAAIC,EAAa,YAAY,GAE7BwM,IAAUzI,GAAuC,SAAS,GAC1D5B,IAAOjC,EAAc,KAAK,GAE1BuM,IAAWzL,EAAS,MAAMmB,EAAK,KAA0B,GAEzDqD,IAAYxE,EAAS,MAAMuL,EAAQ,IAAI,cAAc,KAAKA,EAAQ,IAAI,cAAc,CAAC,GAErF7O,IAAQsD;AAAA,MACZ,MACEuL,EAAQ,MAAM,UAAU,QAAQ,KAAK,CAACG,MAAWA,EAAO,OAAO,OAAO,GAAG,SACzE3M,EAAE,cAAc;AAAA,IAAA,GAId4M,IAAQ3L,EAAoB,MAAM;AAAA,MACtC,EAAE,OAAO,OAAO,OAAOjB,EAAE,mBAAmB,EAAA;AAAA,MAC5C,GAAI,CAAC,SAAS,SAAS,SAAS,YAAY,OAAO,EAAkB,IAAI,CAACuC,OAAS;AAAA,QACjF,OAAOA;AAAA,QACP,OAAOvC,EAAE,WAAWuC,CAAG,EAAE;AAAA,MAAA,EACzB;AAAA,IAAA,CACH,GAGKsK,IAAU5L;AAAA,MAAyB,MACvCwE,EAAU,QACN;AAAA,QACE;AAAA,UACE,KAAK;AAAA,UACL,OAAOzF,EAAE,gBAAgB;AAAA,UACzB,MAAM;AAAA,UACN,SAAS;AAAA,UACT,KAAK,MAAMyM,EAAQ,OAAO,OAAA;AAAA,QAAO;AAAA,MACnC,IAEF,CAAA;AAAA,IAAC;2BAKL7L,EAWiBC,EAAAiM,EAAA,GAAA;AAAA,MAVP,MAAM1K,EAAA;AAAA,8CAAAA,EAAI,QAAArB;AAAA,MACjB,OAAOpD,EAAA;AAAA,MACP,OAAOiP,EAAA;AAAA,MACP,SAASC,EAAA;AAAA,MACT,MAAM;AAAA,MACP,MAAA;AAAA,IAAA;iBAEA,MAEU;AAAA,QAFVjK,EAEU/B,EAAAkM,EAAA,GAAA;AAAA,UAFD,OAAM;AAAA,UAAgB,SAAQ;AAAA,QAAA;qBACrC,MAAwD;AAAA,YAAxDnK,EAAwDgG,IAAA;AAAA,uBAArC;AAAA,cAAJ,KAAI6D;AAAA,cAAW,MAAMC,EAAA;AAAA,cAAU,WAAA;AAAA,YAAA;;;;;;;;;ACpD7C,SAASM,GAAMzJ,IAAwB,IAAiB;AAC7D,QAAMxF,IAAOwF,EAAQ,QAAQ;AAE7B,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAAxF;AAAA,IACA,QAAQ;AAAA,MACN;AAAA,QACE,MAAAA;AAAA,QACA,MAAM;AAAA,QACN,WAAWkP;AAAAA,MAAA;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMF,OAAO;AAAA,MACL,YAAY,EAAE,WAAWC,IAAY,MAAM,QAAA;AAAA,MAC3C,cAAc,EAAE,WAAWC,IAAc,MAAM,SAAS,MAAM,GAAA;AAAA,MAC9D,WAAW,EAAE,WAAWC,IAAW,MAAM,QAAA;AAAA,MACzC,YAAY,EAAE,WAAWC,IAAY,MAAM,SAAS,MAAM,GAAA;AAAA,IAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQjE,WAAW,YAAY;AACrB,YAAMlP,IAAO,MAAM0K,GAAgB,EAAE,QAAQ,SAAS;AAItD,aAAO1K,IAAO,EAAE,KAAKA,EAAK,KAAK,MAAMA,EAAK,SAAS;AAAA,IACrD;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"index.js","sources":["../src/api.ts","../src/format.ts","../src/DirectoryTree.vue","../src/FileGrid.vue","../src/MediaToolbar.vue","../src/MoveDialog.vue","../src/NameDialog.vue","../src/messages.ts","../src/i18n.ts","../src/MediaManager.vue","../src/MediaPicker.vue","../src/openMediaPicker.ts","../src/MediaList.vue","../src/FileField.vue","../src/FilesField.vue","../src/GalleryField.vue","../src/MediaField.vue","../src/MediaPage.vue","../src/index.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { EditOperations, FileQuery, MediaDirectory, MediaFile, MediaPage } from './types'\n\nexport interface MediaApi {\n directories(): Promise<MediaDirectory[]>\n createDirectory(parentId: number, title: string): Promise<MediaDirectory>\n renameDirectory(id: number, title: string): Promise<MediaDirectory>\n moveDirectory(id: number, parentId: number): Promise<MediaDirectory>\n /** Without `force` the server refuses a folder that holds anything, and says what it holds. */\n deleteDirectory(id: number, force?: boolean): Promise<void>\n\n files(query?: FileQuery): Promise<MediaPage>\n file(id: number): Promise<MediaFile>\n /**\n * The file behind a key.\n *\n * An entity stores the key and nothing else, so this is how a form draws the picture it\n * saved last time. `null` when the file is gone from the library.\n */\n fileByPath(path: string): Promise<MediaFile | null>\n upload(\n directoryId: number,\n files: File[],\n onProgress?: (percent: number) => void,\n ): Promise<MediaFile[]>\n rename(id: number, name: string): Promise<MediaFile>\n move(ids: number[], directoryId: number): Promise<number>\n remove(ids: number[]): Promise<number>\n removeOne(id: number): Promise<void>\n\n edit(id: number, operations: EditOperations): Promise<MediaFile>\n copy(id: number): Promise<MediaFile>\n restoreOriginal(id: number): Promise<MediaFile>\n\n /** The address of a preview at a size the server allows. */\n thumb(file: MediaFile, width: number, height?: number, fit?: 'cover' | 'contain'): string | null\n}\n\nexport function createMediaApi(admin: AdminContext): MediaApi {\n const base = `${admin.apiPath}/media`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n directories: () => admin.http.get<{ data: MediaDirectory[] }>(`${base}/directories`).then(data),\n\n createDirectory: (parentId, title) =>\n admin.http\n .post<{ data: MediaDirectory }>(`${base}/directories`, { parent_id: parentId, title })\n .then(data),\n\n renameDirectory: (id, title) =>\n admin.http.patch<{ data: MediaDirectory }>(`${base}/directories/${id}`, { title }).then(data),\n\n moveDirectory: (id, parentId) =>\n admin.http\n .patch<{ data: MediaDirectory }>(`${base}/directories/${id}/move`, { parent_id: parentId })\n .then(data),\n\n deleteDirectory: (id, force = false) =>\n admin.http.delete<void>(`${base}/directories/${id}`, {\n query: { force: force ? 1 : undefined },\n }),\n\n files: (query = {}) =>\n admin.http.get<MediaPage>(`${base}/files`, {\n query: {\n directory_id: query.directory_id ?? undefined,\n q: query.q || undefined,\n type: query.type ?? undefined,\n sort: query.sort,\n page: query.page,\n per_page: query.per_page,\n },\n }),\n\n file: (id) => admin.http.get<{ data: MediaFile }>(`${base}/files/${id}`).then(data),\n\n fileByPath: (path) =>\n admin.http\n .get<{ data: MediaFile }>(`${base}/files/by-path?path=${encodeURIComponent(path)}`)\n .then(data)\n .catch(() => null),\n\n async upload(directoryId, files, onProgress) {\n const body = new FormData()\n body.append('directory_id', String(directoryId))\n files.forEach((file) => body.append('files[]', file))\n\n // XHR rather than fetch, for the one thing fetch still cannot do: say how far an upload\n // has got. A person watching a forty-megabyte video wants a bar, not a spinner.\n return new Promise<MediaFile[]>((resolve, reject) => {\n const request = new XMLHttpRequest()\n\n request.open('POST', `${base}/files`)\n request.responseType = 'json'\n request.withCredentials = true\n request.setRequestHeader('Accept', 'application/json')\n request.setRequestHeader('X-Requested-With', 'XMLHttpRequest')\n // Without this the refusal comes back in English while the panel is in Russian: the\n // panel's own client sends it on every request, and this one is not that client.\n request.setRequestHeader('X-Webx-Locale', admin.i18n.state.locale)\n\n const token = csrfToken()\n if (token) {\n request.setRequestHeader('X-XSRF-TOKEN', token)\n }\n\n request.upload.addEventListener('progress', (event) => {\n if (event.lengthComputable) {\n onProgress?.(Math.round((event.loaded / event.total) * 100))\n }\n })\n\n request.addEventListener('load', () => {\n const answer = request.response as { data?: MediaFile[]; message?: string } | null\n\n if (request.status >= 200 && request.status < 300 && answer?.data) {\n resolve(answer.data)\n\n return\n }\n\n reject(\n Object.assign(new Error(answer?.message ?? 'Upload failed'), {\n status: request.status,\n body: answer,\n }),\n )\n })\n\n request.addEventListener('error', () => reject(new Error('Upload failed')))\n request.send(body)\n })\n },\n\n rename: (id, name) =>\n admin.http.patch<{ data: MediaFile }>(`${base}/files/${id}`, { name }).then(data),\n\n move: (ids, directoryId) =>\n admin.http\n .post<{ data: { moved: number } }>(`${base}/files/move`, { ids, directory_id: directoryId })\n .then((body) => body.data.moved),\n\n // A POST for the batch, not a DELETE: the panel's own client sends no body on DELETE, and\n // a list of ids in a query string is a worse answer than an honest verb.\n remove: (ids) =>\n admin.http\n .post<{ data: { deleted: number } }>(`${base}/files/delete`, { ids })\n .then((body) => body.data.deleted),\n\n removeOne: (id) => admin.http.delete<void>(`${base}/files/${id}`),\n\n edit: (id, operations) =>\n admin.http.post<{ data: MediaFile }>(`${base}/files/${id}/edit`, operations).then(data),\n\n copy: (id) => admin.http.post<{ data: MediaFile }>(`${base}/files/${id}/copy`).then(data),\n\n restoreOriginal: (id) =>\n admin.http.post<{ data: MediaFile }>(`${base}/files/${id}/restore-original`).then(data),\n\n thumb(file, width, height, fit = 'cover') {\n if (!file.thumb) {\n return null\n }\n\n const url = new URL(file.thumb, window.location.origin)\n url.searchParams.set('w', String(width))\n if (height) {\n url.searchParams.set('h', String(height))\n }\n url.searchParams.set('fit', fit)\n\n return url.pathname + url.search\n },\n }\n}\n\nfunction csrfToken(): string | null {\n const cookie = document.cookie.split('; ').find((part) => part.startsWith('XSRF-TOKEN='))\n\n return cookie ? decodeURIComponent(cookie.slice('XSRF-TOKEN='.length)) : null\n}\n","/** Bytes as somebody would say them: 32 MB, not 33 554 432. */\nexport function readable(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n let size = bytes\n let unit = 0\n\n while (size >= 1024 && unit < units.length - 1) {\n size /= 1024\n unit++\n }\n\n return `${size >= 10 || unit === 0 ? Math.round(size) : size.toFixed(1)} ${units[unit]}`\n}\n\n/** The file name out of a stored key, for a file the library can no longer be asked about. */\nexport function nameFromPath(path: string): string {\n return path.split('/').pop() || path\n}\n","<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { WxTree, type TreeDropEvent } from '@webx-ui/core'\nimport type { MediaDirectory } from './types'\n\n/**\n * The folders, on the left.\n *\n * The root carries a translated label rather than the title it was given in the database: it is\n * the library itself, and an editor should not have to know it is a row.\n */\nconst props = defineProps<{\n directories: MediaDirectory[]\n}>()\n\nconst selected = defineModel<number | null>('selected', { default: null })\n\nconst emit = defineEmits<{\n move: [id: number, parentId: number]\n}>()\n\nconst t = useTranslate('webx-media')\n\ntype Node = MediaDirectory & { label: string }\n\n// The tree rearranges this list itself when something is dropped, and then says so; the answer\n// from the server replaces it a moment later. A computed would have nothing to rearrange.\nconst nodes = ref<Node[]>([])\nconst expanded = ref<number[]>([])\n\nwatch(\n () => props.directories,\n (directories) => {\n nodes.value = directories.map(label)\n expanded.value = keysOf(directories)\n },\n { immediate: true, deep: true },\n)\n\nfunction label(directory: MediaDirectory): Node {\n return {\n ...directory,\n label: directory.is_root ? t('manager.root') : directory.title,\n children: (directory.children ?? []).map(label),\n }\n}\n\nfunction keysOf(directories: MediaDirectory[]): number[] {\n return directories.flatMap((directory) => [directory.id, ...keysOf(directory.children ?? [])])\n}\n\nfunction onDrop(event: TreeDropEvent<Node>): void {\n const parent = event.zone === 'inside' ? event.target : event.parent\n\n if (parent) {\n emit('move', event.node.id, parent.id)\n }\n}\n</script>\n\n<template>\n <wx-tree\n v-model:selected=\"selected\"\n v-model:expanded=\"expanded\"\n v-model=\"nodes\"\n class=\"wx-media-tree\"\n node-key=\"id\"\n draggable\n show-lines\n @drop=\"onDrop\"\n />\n</template>\n\n<style>\n.wx-media-tree {\n min-width: 0;\n overflow: auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { toast, WxEmpty, WxFileCard, WxSelectionArea } from '@webx-ui/core'\nimport type { MediaApi } from './api'\nimport type { MediaFile } from './types'\n\n/**\n * The files, as cards, with a rubber band over them.\n *\n * Previews come from the server's thumbnail endpoint rather than the files themselves: a grid of\n * forty photographs is forty full-size images otherwise, and on a phone that is the difference\n * between a screen and a wait.\n */\nconst props = defineProps<{\n files: MediaFile[]\n api: MediaApi\n query?: string\n /** A picker takes one file; the manager selects to act on a batch. */\n single?: boolean\n}>()\n\nconst selected = defineModel<number[]>('selected', { default: () => [] })\n\nconst emit = defineEmits<{\n open: [file: MediaFile]\n rename: [file: MediaFile, name: string]\n edit: [file: MediaFile]\n remove: [file: MediaFile]\n}>()\n\nconst t = useTranslate('webx-media')\n\nconst empty = computed(() =>\n props.query ? t('manager.empty-search', { query: props.query }) : t('manager.empty'),\n)\n\nfunction copied(): void {\n toast.success(t('manager.link-copied'))\n}\n\n/**\n * The clipboard is not always there: it is missing outside a secure context, and an iframe has\n * to be granted it. Rather than let the card fail silently, try the old way — and if that is\n * refused too, put the address on screen, where it can at least be copied by hand.\n */\nfunction copyFailed(file: MediaFile): void {\n const area = document.createElement('textarea')\n area.value = file.url\n area.setAttribute('readonly', '')\n area.style.position = 'fixed'\n area.style.opacity = '0'\n document.body.appendChild(area)\n area.select()\n\n let done = false\n\n try {\n done = document.execCommand('copy')\n } catch {\n done = false\n }\n\n area.remove()\n\n if (done) {\n copied()\n\n return\n }\n\n toast.danger(`${t('errors.copy')} ${file.url}`, { duration: 0 })\n}\n</script>\n\n<template>\n <wx-selection-area\n v-if=\"files.length > 0\"\n v-slot=\"{ isSelected }\"\n v-model=\"selected\"\n class=\"wx-media-grid\"\n :multiple=\"!single\"\n >\n <wx-file-card\n v-for=\"file in files\"\n :key=\"file.id\"\n v-wx-select=\"file.id\"\n class=\"wx-media-grid__card\"\n :name=\"file.name\"\n :url=\"file.url\"\n :thumbnail=\"api.thumb(file, 320, 320) ?? undefined\"\n :type=\"file.mime\"\n :selected=\"isSelected(file.id)\"\n renamable\n :editable=\"file.editable\"\n removable\n copyable\n actions-menu\n :rename-label=\"t('manager.rename')\"\n :edit-label=\"t('manager.edit')\"\n :remove-label=\"t('manager.delete')\"\n :remove-confirm-text=\"t('dialogs.delete-file', { name: file.name })\"\n :cancel-label=\"t('manager.cancel')\"\n :save-label=\"t('manager.save')\"\n :download-url=\"file.source ?? file.url\"\n :copy-label=\"t('manager.copy-link')\"\n :download-label=\"t('manager.download')\"\n :copied-label=\"t('manager.link-copied')\"\n @rename=\"(name) => emit('rename', file, name)\"\n @edit=\"emit('edit', file)\"\n @remove=\"emit('remove', file)\"\n @copy=\"copied\"\n @copy-error=\"copyFailed(file)\"\n @dblclick=\"emit('open', file)\"\n />\n </wx-selection-area>\n\n <wx-empty v-else :description=\"empty\" />\n</template>\n\n<style>\n.wx-media-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n gap: var(--wx-space-12);\n align-content: start;\n min-height: 0;\n overflow: auto;\n padding: var(--wx-space-2);\n /* A rubber band over names would otherwise select the names. */\n user-select: none;\n}\n\n.wx-media--compact .wx-media-grid {\n grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));\n gap: var(--wx-space-8);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { WxAction, WxActions, WxDropdown, WxDropdownItem, WxInput } from '@webx-ui/core'\nimport type { MediaKind } from './types'\n\n/**\n * One row of icons over the files.\n *\n * Actions rather than labelled buttons: the row has to survive a phone and a picker dialog half\n * the width of a window, and a row of words does not. What each icon means is a tooltip away,\n * and what is chosen is said in the icon's own label.\n */\nconst props = defineProps<{\n canUpload: boolean\n canManage: boolean\n selected: number\n /** The caller asked for one kind of file, so there is nothing to filter by. */\n fixedType?: boolean\n /** Narrow screens get the folders behind a button instead of beside the files. */\n compact: boolean\n}>()\n\nconst search = defineModel<string>('search', { default: '' })\nconst type = defineModel<MediaKind | 'all'>('type', { default: 'all' })\nconst sort = defineModel<string>('sort', { default: '-created_at' })\n\nconst emit = defineEmits<{\n upload: []\n move: []\n remove: []\n folders: []\n}>()\n\nconst t = useTranslate('webx-media')\n\nconst types = computed<{ value: MediaKind | 'all'; label: string }[]>(() => [\n { value: 'all', label: t('manager.all-types') },\n ...(['image', 'video', 'audio', 'document', 'other'] as MediaKind[]).map((kind) => ({\n value: kind,\n label: t(`manager.${kind}`),\n })),\n])\n\nconst sorts = computed(() => [\n { value: '-created_at', label: t('manager.sort-newest') },\n { value: 'created_at', label: t('manager.sort-oldest') },\n { value: 'name', label: t('manager.sort-name') },\n { value: '-size', label: t('manager.sort-size') },\n])\n\nconst typeLabel = computed(() => types.value.find((one) => one.value === type.value)?.label ?? '')\nconst sortLabel = computed(() => sorts.value.find((one) => one.value === sort.value)?.label ?? '')\n</script>\n\n<template>\n <header class=\"wx-media-toolbar\">\n <!--\n `sidebar`, the same icon the shell shows and hides its own column with: the gesture is\n the same one, so the sign is the same one. A folder would only say \"folder\", which is\n also what the button further along this row would be saying.\n -->\n <wx-action\n v-if=\"compact\"\n icon=\"sidebar\"\n :title=\"t('manager.folders')\"\n size=\"sm\"\n @click=\"emit('folders')\"\n />\n\n <wx-input\n v-model=\"search\"\n class=\"wx-media-toolbar__search\"\n :placeholder=\"t('manager.search')\"\n clearable\n size=\"sm\"\n />\n\n <wx-dropdown v-if=\"!fixedType\">\n <template #trigger>\n <wx-action icon=\"filter\" :title=\"`${t('manager.all-types')}: ${typeLabel}`\" size=\"sm\" />\n </template>\n\n <wx-dropdown-item\n v-for=\"option in types\"\n :key=\"String(option.value)\"\n :active=\"option.value === type\"\n @click=\"type = option.value\"\n >\n {{ option.label }}\n </wx-dropdown-item>\n </wx-dropdown>\n\n <wx-dropdown>\n <template #trigger>\n <!-- Not `type=\"sort\"`: in this set that means reordering by hand, and its icon is a drag handle. -->\n <wx-action icon=\"arrow-down\" :title=\"sortLabel\" size=\"sm\" />\n </template>\n\n <wx-dropdown-item\n v-for=\"option in sorts\"\n :key=\"option.value\"\n :active=\"option.value === sort\"\n @click=\"sort = option.value\"\n >\n {{ option.label }}\n </wx-dropdown-item>\n </wx-dropdown>\n\n <wx-action\n v-if=\"canUpload\"\n type=\"upload\"\n :title=\"t('manager.upload')\"\n size=\"sm\"\n @click=\"emit('upload')\"\n />\n\n <!-- Kept in the row rather than added to it: the icons are always here and simply cannot\n be used while nothing is selected, so the grid never moves down a line. -->\n <wx-actions v-if=\"canManage\" size=\"sm\">\n <wx-action\n icon=\"folder-move\"\n :title=\"t('manager.move')\"\n :disabled=\"props.selected === 0\"\n @click=\"emit('move')\"\n />\n <wx-action\n type=\"remove\"\n :title=\"t('manager.delete')\"\n :disabled=\"props.selected === 0\"\n @click=\"emit('remove')\"\n />\n </wx-actions>\n </header>\n</template>\n\n<style>\n.wx-media-toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-6);\n}\n\n.wx-media-toolbar__search {\n flex: 1 1 180px;\n min-width: 120px;\n}\n\n.wx-media-toolbar__count {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n margin-inline-start: auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxSelect, WxSpace } from '@webx-ui/core'\nimport type { MediaDirectory } from './types'\n\n/**\n * Where to put the selected files.\n *\n * A list of folders rather than a box to type in: an editor knows a folder by its name and its\n * place in the tree, and has no reason to ever learn that it also has a number.\n */\nconst props = defineProps<{\n directories: MediaDirectory[]\n /** The folder they are in now, which is not worth offering. */\n from?: number | null\n count: number\n}>()\n\nconst { open, resolve, dismiss } = useModal<number>()\n\nconst t = useTranslate('webx-media')\n\nconst options = computed(() =>\n flatten(props.directories).filter((option) => option.value !== props.from),\n)\nconst target = ref<number | null>(options.value[0]?.value ?? null)\n\nfunction flatten(directories: MediaDirectory[]): { value: number; label: string }[] {\n return directories.flatMap((directory) => [\n {\n value: directory.id,\n // Indented, so the tree is still readable once it is a flat list.\n label:\n ' '.repeat(directory.depth * 3) + (directory.is_root ? t('manager.root') : directory.title),\n },\n ...flatten(directory.children ?? []),\n ])\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('manager.move-to')\" :width=\"420\">\n <wx-select v-model=\"target\" :options=\"options\" />\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('manager.cancel') }}</wx-button>\n <wx-button type=\"primary\" :disabled=\"target === null\" @click=\"resolve(target!)\">\n {{ t('manager.move') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxInput, WxSpace } from '@webx-ui/core'\n\n/**\n * A name, asked for properly.\n *\n * `window.prompt` is not an option: a browser is free to refuse it — an embedded frame usually\n * does — and then the button simply does nothing, which is exactly how \"new folder\" looked.\n */\nconst props = withDefaults(\n defineProps<{\n title: string\n label?: string\n value?: string\n confirmText?: string\n }>(),\n { label: undefined, value: '', confirmText: undefined },\n)\n\nconst { open, resolve, dismiss } = useModal<string>()\n\nconst t = useTranslate('webx-media')\n\nconst name = ref(props.value)\nconst input = useTemplateRef<HTMLElement>('input')\n\nonMounted(async () => {\n await nextTick()\n input.value?.querySelector('input')?.focus()\n})\n\nfunction submit(): void {\n const value = name.value.trim()\n\n if (value !== '') {\n resolve(value)\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"title\" :width=\"420\">\n <div ref=\"input\">\n <!-- The dialog's title says what is being named, so the field carries the same words as a\n placeholder and as its accessible name rather than repeating them above itself. -->\n <wx-input\n v-model=\"name\"\n :placeholder=\"label ?? t('manager.folder-name')\"\n :aria-label=\"label ?? t('manager.folder-name')\"\n @keyup.enter=\"submit\"\n />\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('manager.cancel') }}</wx-button>\n <wx-button type=\"primary\" :disabled=\"name.trim() === ''\" @click=\"submit\">\n {{ confirmText ?? t('manager.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * What this package says, in English.\n *\n * The same keys `webx-ui/module-media` ships as `lang/en/*.php`, and the same ones every other\n * language there has. Kept here so a manager placed by hand, with no WebX UI server behind it,\n * still has words — and so a key never reaches the screen when a translation is missing.\n */\nexport const mediaMessages: Record<string, Messages> = {\n module: {\n title: 'Files',\n },\n manager: {\n root: 'Library',\n search: 'Search files',\n 'new-folder': 'New folder',\n 'folder-name': 'Folder name',\n rename: 'Rename',\n move: 'Move',\n 'move-to': 'Move to…',\n delete: 'Delete',\n upload: 'Upload',\n 'upload-hint': 'Drop files here or choose them',\n select: 'Select',\n selected: ':count selected',\n empty: 'This folder is empty',\n 'empty-search': 'Nothing matches :query',\n 'all-types': 'All types',\n image: 'Images',\n video: 'Video',\n audio: 'Audio',\n document: 'Documents',\n other: 'Other',\n 'sort-newest': 'Newest first',\n 'sort-oldest': 'Oldest first',\n 'sort-name': 'By name',\n 'sort-size': 'Largest first',\n 'duplicate-added': 'That file was already in this folder',\n uploaded: ':count file(s) added',\n cancel: 'Cancel',\n save: 'Save',\n folders: 'Folders',\n edit: 'Edit',\n 'copy-link': 'Copy the link',\n download: 'Download',\n 'link-copied': 'Link copied',\n 'status-files': 'Files: :count',\n 'status-selected': 'selected: :count',\n 'status-size': 'size: :size',\n pick: 'Choose (:count)',\n chosen: 'Chosen :count of :max',\n },\n // The picture field on a form, which is the module used from outside its own section.\n field: {\n select: 'Choose a picture',\n edit: 'Captions',\n replace: 'Replace',\n captions: 'Captions',\n 'alt-hint': 'What is on the picture, for a reader who cannot see it',\n 'title-hint': 'A caption shown on hover',\n 'no-preview': 'No preview',\n 'clear-title': 'Clear the field?',\n 'clear-text': 'The file stays in the library — only this record stops using it.',\n 'clear-confirm': 'Clear',\n add: 'Add',\n empty: 'Nothing chosen yet',\n reorder: 'Reorder',\n open: 'Open in the library',\n missing: 'No longer in the library',\n 'remove-title': 'Take it out of the list?',\n 'remove-text': 'The file stays in the library — only this record stops using it.',\n 'remove-confirm': 'Remove',\n count: ':count of :max',\n 'at-least': 'At least :count',\n },\n // The image editor is a component of the design system, so its words are props rather than\n // lines in the dictionary — the manager hands it these when it opens it.\n editor: {\n title: 'Edit picture',\n save: 'Save',\n cancel: 'Cancel',\n reset: 'Reset',\n 'rotate-left': 'Turn left',\n 'rotate-right': 'Turn right',\n 'flip-horizontal': 'Mirror across',\n 'flip-vertical': 'Mirror down',\n ratio: 'Ratio',\n crop: 'Crop',\n output: 'Output',\n 'output-hint': 'The size of the picture you will get',\n width: 'Width',\n height: 'Height',\n free: 'Free',\n original: 'Original',\n error: 'This picture could not be loaded',\n },\n dialogs: {\n 'delete-files-title': 'Delete :count file(s)?',\n 'delete-files-text': 'Anywhere they are already used, they will stop opening.',\n 'delete-folder-title': 'Delete the folder :title?',\n 'delete-folder-text': 'It goes for good, and so does whatever is inside it.',\n 'delete-folder-contents': 'It holds :files file(s) and :directories folder(s).',\n 'delete-folder-warning':\n 'Everything inside goes too, and pictures already placed in content will stop opening.',\n confirm: 'Delete',\n 'delete-file': 'Delete :name?',\n },\n errors: {\n 'directory-not-empty': 'This folder is not empty.',\n 'root-immutable': 'The library root cannot be moved or deleted.',\n 'directory-into-itself': 'A folder cannot be moved into itself.',\n 'not-an-image': 'This file is not an image.',\n 'image-too-large': 'This image is larger than :megapixels megapixels.',\n upload: 'That file was not accepted.',\n copy: 'The clipboard is not available here. The address:',\n 'unsupported-type': 'Only these can be uploaded: :types.',\n 'file-too-large': 'The file is larger than :size MB.',\n 'too-many-files': 'No more than :count files at a time.',\n 'file-not-found': 'There is no such file in the library.',\n },\n files: {\n copy: ':name (copy)',\n },\n validation: {\n title: 'name',\n parent_id: 'parent folder',\n directory_id: 'folder',\n files: 'files',\n item: 'File :number: :message',\n shape: 'This is not a file from the library.',\n accept: 'This field takes :kind only.',\n localized: 'A field of files cannot be translated — the captions inside it are.',\n kind: {\n image: 'images',\n video: 'video',\n audio: 'audio',\n document: 'documents',\n other: 'other files',\n },\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { mediaMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/**\n * Put this package's English under the panel's dictionary, once per panel.\n *\n * The server's translations land on top of it when they arrive. What this prevents is the half\n * second — or the whole session, in a panel assembled without a server — where a screen shows\n * `manager.upload` instead of a word.\n */\nexport function useMediaMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-media', mediaMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref, useTemplateRef, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n openImageEditor,\n toast,\n useElementWidth,\n WxAction,\n WxActions,\n WxDrawer,\n WxPagination,\n} from '@webx-ui/core'\nimport DirectoryTree from './DirectoryTree.vue'\nimport FileGrid from './FileGrid.vue'\nimport MediaToolbar from './MediaToolbar.vue'\nimport MoveDialog from './MoveDialog.vue'\nimport NameDialog from './NameDialog.vue'\nimport { createMediaApi } from './api'\nimport { readable } from './format'\nimport { useMediaMessages } from './i18n'\nimport type { MediaDirectory, MediaFile, MediaKind, MediaPage } from './types'\n\n/**\n * The library: folders on the left, files on the right, a row of icons over both.\n *\n * Everything the screen does is one request and a reload of the part that changed — there is no\n * cache here on purpose. A library is edited by several people, and a panel that believes its\n * own copy shows a folder that was emptied an hour ago.\n *\n * The width that decides the layout is this component's own, not the window's: the same manager\n * is a page, half of a picker dialog, and a phone.\n */\nconst props = withDefaults(\n defineProps<{\n /** A picker hands one file back instead of managing the library. */\n picking?: boolean\n /** Only these kinds can be chosen, when picking. */\n accept?: MediaKind | null\n /**\n * A picker that takes several files at once.\n *\n * What this switches on is the selection the manager already has for its batch operations:\n * the rubber band, the tap that toggles, the run held with shift. A picker with a way of\n * marking files of its own would be a second thing to learn for the very same job.\n */\n multiple?: boolean\n /** The most that may be held at once, when picking several. */\n max?: number | null\n /**\n * Drawn as a section of the panel rather than inside a picker.\n *\n * The screen around it then carries the two things a section carries — the main action and\n * the views of the list — so the toolbar stops offering an upload icon and a kind filter\n * of its own. In a dialog there is no screen around it, and it keeps both.\n */\n inPage?: boolean\n }>(),\n { picking: false, accept: null, multiple: false, max: null, inPage: false },\n)\n\nconst emit = defineEmits<{ pick: [file: MediaFile]; selection: [files: MediaFile[]] }>()\n\nconst admin = useAdmin()\nconst api = createMediaApi(admin)\nuseMediaMessages()\n\nconst t = useTranslate('webx-media')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\n/**\n * The image editor's words, in the language the panel is being read in.\n *\n * Read when the editor is opened rather than kept in a computed: the dialog is mounted outside\n * the app, so it is handed plain strings once and nothing re-renders it afterwards.\n */\nfunction editorLabels(): Record<string, string> {\n return {\n title: t('editor.title'),\n saveLabel: t('editor.save'),\n cancelLabel: t('editor.cancel'),\n resetLabel: t('editor.reset'),\n rotateLeftLabel: t('editor.rotate-left'),\n rotateRightLabel: t('editor.rotate-right'),\n flipHorizontalLabel: t('editor.flip-horizontal'),\n flipVerticalLabel: t('editor.flip-vertical'),\n ratioLabel: t('editor.ratio'),\n cropLabel: t('editor.crop'),\n outputLabel: t('editor.output'),\n outputHint: t('editor.output-hint'),\n widthLabel: t('editor.width'),\n heightLabel: t('editor.height'),\n freeLabel: t('editor.free'),\n originalLabel: t('editor.original'),\n errorText: t('editor.error'),\n }\n}\n\nconst root = useTemplateRef<HTMLElement>('root')\nconst width = useElementWidth(root)\nconst compact = computed(() => width.value > 0 && width.value < 640)\n\nconst directories = ref<MediaDirectory[]>([])\nconst current = ref<number | null>(null)\nconst page = ref<MediaPage | null>(null)\nconst selected = ref<number[]>([])\nconst search = ref('')\n/*\n * What the caller asked for is not a default the person can put back: a field that wants a\n * picture would otherwise be handed a PDF, and find out when the page renders.\n */\nconst type = defineModel<MediaKind | 'all'>('type', { default: 'all' })\n\nif (props.accept) type.value = props.accept\n\nwatch(\n () => props.accept,\n (kind) => {\n type.value = kind ?? 'all'\n },\n)\nconst sort = ref('-created_at')\nconst busy = ref(false)\nconst foldersOpen = ref(false)\nconst picker = useTemplateRef<HTMLInputElement>('picker')\n\nconst rows = computed(() => page.value?.data ?? [])\nconst canManage = computed(() => admin.can('media.manage'))\nconst canUpload = computed(() => admin.can('media.upload') || canManage.value)\nconst folder = computed(() => find(current.value))\n/** Picking one file is the one case the band is off for; the manager itself always has it. */\nconst picksOne = computed(() => props.picking && !props.multiple)\n\n/**\n * The limit belongs to the field, so the dialog keeps to it.\n *\n * Trimmed rather than refused: what the person sees is that the grid stops taking more, and the\n * count under it says how far they have got. Finding out on save that a field takes ten is not\n * a limit, it is a rejected form.\n */\nwatch(selected, (ids) => {\n if (props.max !== null && props.max > 0 && ids.length > props.max) {\n selected.value = ids.slice(0, props.max)\n\n return\n }\n\n // In the order they are on screen, not the order they were clicked in: what comes back is a\n // gallery, and its order is the one the person was looking at.\n emit(\n 'selection',\n rows.value.filter((file) => ids.includes(file.id)),\n )\n})\n\nonMounted(load)\n\nwatch([current, type, sort], () => loadFiles(1))\nwatch(\n search,\n debounce(() => loadFiles(1), 300),\n)\nwatch(compact, (narrow) => {\n if (!narrow) {\n foldersOpen.value = false\n }\n})\n\nasync function load(): Promise<void> {\n directories.value = await api.directories()\n current.value ??= directories.value[0]?.id ?? null\n await loadFiles(1)\n}\n\nasync function loadFiles(to = page.value?.meta.current_page ?? 1): Promise<void> {\n busy.value = true\n\n try {\n page.value = await api.files({\n directory_id: search.value ? null : current.value,\n q: search.value,\n type: type.value === 'all' ? null : type.value,\n sort: sort.value,\n page: to,\n })\n selected.value = []\n } finally {\n busy.value = false\n }\n}\n\nfunction choose(): void {\n picker.value?.click()\n}\n\n/**\n * Uploading says what happened in a toast and nowhere else.\n *\n * A list of what was just added under the toolbar is a second place to look and a thing to\n * dismiss; the files themselves appear in the grid a moment later, which is the answer.\n */\nasync function upload(event: Event): Promise<void> {\n const input = event.target as HTMLInputElement\n const chosen = Array.from(input.files ?? [])\n\n // Cleared straight away, so choosing the same file twice in a row still counts as a change.\n input.value = ''\n\n if (chosen.length === 0 || current.value === null) {\n return\n }\n\n try {\n const stored = await api.upload(current.value, chosen)\n const duplicates = stored.filter((file) => file.duplicate).length\n\n if (stored.length > duplicates) {\n toast.success(t('manager.uploaded', { count: stored.length - duplicates }))\n }\n\n if (duplicates > 0) {\n toast.info(t('manager.duplicate-added'))\n }\n\n await Promise.all([load(), loadFiles(1)])\n } catch (error) {\n toast.danger(message(error, t('errors.upload')))\n }\n}\n\nconst askName = createModal<\n string,\n { title: string; label?: string; value?: string; confirmText?: string }\n>(NameDialog)\n\nasync function createFolder(): Promise<void> {\n const title = await askName({\n title: t('manager.new-folder'),\n confirmText: t('manager.new-folder'),\n })\n\n if (title === undefined || current.value === null) {\n return\n }\n\n await api.createDirectory(current.value, title)\n await load()\n}\n\nasync function renameFolder(): Promise<void> {\n const target = folder.value\n\n if (!target || target.is_root) {\n return\n }\n\n const title = await askName({\n title: t('manager.rename'),\n value: target.title,\n confirmText: t('manager.save'),\n })\n\n if (title !== undefined) {\n await api.renameDirectory(target.id, title)\n await load()\n }\n}\n\nasync function deleteFolder(): Promise<void> {\n const target = folder.value\n\n if (!target || target.is_root) {\n return\n }\n\n // Asked before anything is tried, because a folder is a folder whether or not it holds\n // anything (§14.2) — and asked a second time, with the counts the server sent back, when it\n // turns out that what goes with it is somebody's article illustrations.\n const first = await confirm({\n title: t('dialogs.delete-folder-title', { title: target.title }),\n message: t('dialogs.delete-folder-text'),\n confirmText: t('dialogs.confirm'),\n cancelText: t('manager.cancel'),\n tone: 'danger',\n })\n\n if (!first) {\n return\n }\n\n try {\n await api.deleteDirectory(target.id)\n } catch (error) {\n const counts = countsOf(error)\n\n if (!counts) {\n toast.danger(message(error))\n\n return\n }\n\n const agreed = await confirm({\n title: t('dialogs.delete-folder-title', { title: target.title }),\n message: `${t('dialogs.delete-folder-contents', counts)} ${t('dialogs.delete-folder-warning')}`,\n confirmText: t('dialogs.confirm'),\n cancelText: t('manager.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) {\n return\n }\n\n await api.deleteDirectory(target.id, true)\n }\n\n current.value = null\n await load()\n}\n\nasync function moveFolder(id: number, parentId: number): Promise<void> {\n try {\n await api.moveDirectory(id, parentId)\n } catch (error) {\n toast.danger(message(error, t('errors.directory-into-itself')))\n }\n\n await load()\n}\n\nconst askWhereTo = createModal<\n number,\n { directories: MediaDirectory[]; from: number | null; count: number }\n>(MoveDialog)\n\nasync function moveSelected(): Promise<void> {\n const to = await askWhereTo({\n directories: directories.value,\n from: current.value,\n count: selected.value.length,\n })\n\n if (to === undefined) {\n return\n }\n\n await api.move([...selected.value], to)\n await Promise.all([load(), loadFiles()])\n}\n\nasync function removeSelected(): Promise<void> {\n const ids = [...selected.value]\n\n const agreed = await confirm({\n title: t('dialogs.delete-files-title', { count: ids.length }),\n message: t('dialogs.delete-files-text'),\n confirmText: t('dialogs.confirm'),\n cancelText: t('manager.cancel'),\n tone: 'danger',\n })\n\n if (agreed) {\n await api.remove(ids)\n await Promise.all([load(), loadFiles()])\n }\n}\n\nasync function rename(file: MediaFile, name: string): Promise<void> {\n await api.rename(file.id, name)\n await loadFiles()\n}\n\nasync function remove(file: MediaFile): Promise<void> {\n await api.removeOne(file.id)\n await Promise.all([load(), loadFiles()])\n}\n\n/**\n * The editor works on a preview and hands back a blob; the server is told the operations\n * instead and applies them to the original, at full size.\n *\n * Filters and adjustments are switched off for the same reason: they cannot be said as an\n * operation, and a picture that is quietly smaller than it was is worse than a button that is\n * not there.\n */\nasync function edit(file: MediaFile): Promise<void> {\n // `source`, not `url`: the editor draws the picture onto a canvas and writes that canvas out,\n // and a browser refuses to do that for bytes from another origin without CORS headers — which\n // is exactly what a CDN in front of the library is. `url` stays what everything that only\n // looks at the picture uses.\n const result = await openImageEditor({\n src: file.source ?? file.url,\n fileName: file.file_name,\n filters: false,\n // The editor is a component of the design system: its words are props with English\n // defaults, not lines in the panel's dictionary. Whoever opens it is the one who knows\n // what language the panel is being read in.\n ...editorLabels(),\n })\n\n if (!result) {\n return\n }\n\n await api.edit(file.id, {\n // The editor's frame is read against the picture as it is on screen — already turned — and\n // the server applies the operations in that order for the same reason.\n crop: result.crop,\n rotate: (((result.rotation % 360) + 360) % 360) as 0 | 90 | 180 | 270,\n flip: result.flipX ? 'horizontal' : result.flipY ? 'vertical' : undefined,\n resize: { width: result.width, height: result.height },\n })\n\n // Both addresses — the picture and its preview — carry the new version, so what is on screen\n // a moment later is what was just saved rather than what the browser kept.\n await loadFiles()\n}\n\n/**\n * A double-click means \"this one, now\".\n *\n * While several are being picked it means nothing extra: the first click of it has already put\n * the file in the selection, and the dialog ends on its own button instead.\n */\nfunction open(file: MediaFile): void {\n if (!props.multiple) {\n emit('pick', file)\n }\n}\n\nfunction find(id: number | null): MediaDirectory | null {\n const walk = (nodes: MediaDirectory[]): MediaDirectory | null => {\n for (const node of nodes) {\n if (node.id === id) {\n return node\n }\n\n const deeper = walk(node.children ?? [])\n\n if (deeper) {\n return deeper\n }\n }\n\n return null\n }\n\n return id === null ? null : walk(directories.value)\n}\n\nfunction countsOf(error: unknown): { files: number; directories: number } | null {\n const body = (\n error as { body?: { code?: string; counts?: { files: number; directories: number } } }\n )?.body\n\n return body?.code === 'directory_not_empty' ? (body.counts ?? null) : null\n}\n\n/* The page's own upload button opens the same file dialog the toolbar's icon does. */\ndefineExpose({ upload: choose })\n\nfunction debounce(run: () => void, wait: number): () => void {\n let timer: ReturnType<typeof setTimeout> | undefined\n\n return () => {\n clearTimeout(timer)\n timer = setTimeout(run, wait)\n }\n}\n</script>\n\n<template>\n <div ref=\"root\" class=\"wx-media\" :class=\"{ 'wx-media--compact': compact }\">\n <aside v-if=\"!compact\" class=\"wx-media__folders\">\n <directory-tree v-model:selected=\"current\" :directories=\"directories\" @move=\"moveFolder\" />\n\n <wx-actions v-if=\"canManage\" size=\"sm\" align=\"start\">\n <wx-action type=\"add\" :title=\"t('manager.new-folder')\" @click=\"createFolder\" />\n <wx-action\n type=\"edit\"\n :title=\"t('manager.rename')\"\n :disabled=\"!folder || folder.is_root\"\n @click=\"renameFolder\"\n />\n <wx-action\n type=\"remove\"\n :title=\"t('manager.delete')\"\n :disabled=\"!folder || folder.is_root\"\n @click=\"deleteFolder\"\n />\n </wx-actions>\n </aside>\n\n <section class=\"wx-media__files\">\n <!--\n Uploading is offered while picking too. Most of the time the picture somebody is looking\n for is the one on their desk, and a picker that can only choose from what is already\n there sends them off to the library, to the upload, and back to a form they then have to\n find again.\n -->\n <media-toolbar\n v-model:search=\"search\"\n v-model:type=\"type\"\n v-model:sort=\"sort\"\n :can-upload=\"canUpload && !inPage\"\n :fixed-type=\"accept !== null || inPage\"\n :can-manage=\"canManage\"\n :selected=\"selected.length\"\n :compact=\"compact\"\n @upload=\"choose\"\n @move=\"moveSelected\"\n @remove=\"removeSelected\"\n @folders=\"foldersOpen = true\"\n />\n\n <!--\n `accept` here is a hint to the file dialog, not a rule: what may be uploaded at all is\n the server's list of extensions, and it says so in a language the person reads.\n -->\n <input\n ref=\"picker\"\n type=\"file\"\n multiple\n hidden\n :accept=\"accept === null ? undefined : `${accept}/*`\"\n @change=\"upload\"\n />\n\n <file-grid\n v-model:selected=\"selected\"\n :files=\"rows\"\n :api=\"api\"\n :query=\"search\"\n :single=\"picksOne\"\n @rename=\"rename\"\n @edit=\"edit\"\n @remove=\"remove\"\n @open=\"open\"\n />\n\n <footer class=\"wx-media__status\">\n <span>{{ t('manager.status-files', { count: page?.stats.files ?? 0 }) }}</span>\n <span v-if=\"selected.length > 0\">\n {{ t('manager.status-selected', { count: selected.length }) }}\n </span>\n <span>{{ t('manager.status-size', { size: readable(page?.stats.size ?? 0) }) }}</span>\n </footer>\n\n <wx-pagination\n v-if=\"page && page.meta.last_page > 1\"\n :page=\"page.meta.current_page\"\n :per-page=\"page.meta.per_page\"\n :total=\"page.meta.total\"\n :last-page=\"page.meta.last_page\"\n :disabled=\"busy\"\n size=\"sm\"\n @change=\"({ page: to }) => loadFiles(to)\"\n />\n </section>\n\n <!-- On a narrow screen the folders are a drawer: a tree beside a grid leaves room for\n neither, and an editor on a phone is looking for one thing at a time. -->\n <wx-drawer v-model:open=\"foldersOpen\" side=\"left\" :title=\"t('manager.folders')\" :size=\"280\">\n <div class=\"wx-media__drawer\">\n <directory-tree\n v-model:selected=\"current\"\n :directories=\"directories\"\n @move=\"moveFolder\"\n @update:selected=\"foldersOpen = false\"\n />\n\n <wx-actions v-if=\"canManage\" size=\"sm\" align=\"start\">\n <wx-action type=\"add\" :title=\"t('manager.new-folder')\" @click=\"createFolder\" />\n <wx-action\n type=\"edit\"\n :title=\"t('manager.rename')\"\n :disabled=\"!folder || folder.is_root\"\n @click=\"renameFolder\"\n />\n <wx-action\n type=\"remove\"\n :title=\"t('manager.delete')\"\n :disabled=\"!folder || folder.is_root\"\n @click=\"deleteFolder\"\n />\n </wx-actions>\n </div>\n </wx-drawer>\n </div>\n</template>\n\n<style>\n.wx-media {\n display: grid;\n grid-template-columns: minmax(180px, 240px) minmax(0, 1fr);\n gap: var(--wx-gap, var(--wx-space-16));\n min-height: 0;\n height: 100%;\n}\n\n.wx-media--compact {\n grid-template-columns: minmax(0, 1fr);\n gap: var(--wx-space-8);\n}\n\n.wx-media__folders {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n min-height: 0;\n border-right: 1px solid var(--wx-border-muted);\n padding-right: var(--wx-space-12);\n}\n\n/* The drawer hands its content the edge of the screen; the tree and its actions want air. */\n.wx-media__drawer {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-10);\n padding-block: var(--wx-space-4);\n}\n\n.wx-media__status {\n display: flex;\n flex-wrap: wrap;\n gap: var(--wx-space-8);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n border-top: 1px solid var(--wx-border-muted);\n padding-top: var(--wx-space-6);\n}\n\n.wx-media__files {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-10);\n min-height: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxSpace } from '@webx-ui/core'\nimport MediaManager from './MediaManager.vue'\nimport type { MediaFile, MediaKind } from './types'\n\n/**\n * The library as a dialog, for a form that needs a picture.\n *\n * The same manager, not a second one: a picker that browses differently from the manager is a\n * second thing to learn and a second thing to keep working.\n */\nwithDefaults(\n defineProps<{\n accept?: MediaKind | null\n title?: string\n /** Opened to manage the library rather than to choose from it. */\n manage?: boolean\n /** Several files at once, handed back as a list. */\n multiple?: boolean\n /** The most that may be chosen, when choosing several. */\n max?: number | null\n }>(),\n { accept: 'image', title: undefined, manage: false, multiple: false, max: null },\n)\n\nconst { open, resolve, dismiss } = useModal<MediaFile | MediaFile[]>()\n\nconst t = useTranslate('webx-media')\n\n/**\n * What is marked in the grid right now.\n *\n * A dialog that takes several files needs a button to end on, and the button needs to say how\n * many it would hand back — a double-click cannot mean \"these ten\".\n */\nconst chosen = ref<MediaFile[]>([])\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"title ?? t('module.title')\" :width=\"960\">\n <div class=\"wx-media-picker\">\n <media-manager\n :picking=\"!manage\"\n :accept=\"manage ? null : accept\"\n :multiple=\"multiple && !manage\"\n :max=\"max\"\n @pick=\"(file) => resolve(file)\"\n @selection=\"(files) => (chosen = files)\"\n />\n </div>\n\n <template v-if=\"multiple && !manage\" #footer>\n <wx-space size=\"sm\">\n <!-- The count is what tells somebody they have reached the limit: the grid stops\n taking more, and this is where it says so. -->\n <span v-if=\"max !== null && max > 0\" class=\"wx-media-picker__count\">\n {{ t('manager.chosen', { count: chosen.length, max }) }}\n </span>\n\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('manager.cancel') }}</wx-button>\n <wx-button\n class=\"wx-media-picker__confirm\"\n type=\"primary\"\n :disabled=\"chosen.length === 0\"\n @click=\"resolve(chosen)\"\n >\n {{ t('manager.pick', { count: chosen.length }) }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style>\n.wx-media-picker {\n height: min(70vh, 560px);\n min-height: 0;\n}\n\n.wx-media-picker__count {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n align-self: center;\n}\n</style>\n","import { createModal } from '@webx-ui/core'\nimport MediaPicker from './MediaPicker.vue'\nimport type { MediaFile, MediaKind } from './types'\n\nexport interface MediaPickerOptions {\n /** Narrow what can be chosen; `null` offers everything. */\n accept?: MediaKind | null\n title?: string\n}\n\nexport interface MediaFilesOptions extends MediaPickerOptions {\n /**\n * The most that may be chosen at once.\n *\n * The dialog keeps to it, and so does the server: the limit lives in the field's schema, and\n * a request that never opened a dialog has to meet it too.\n */\n max?: number | null\n}\n\n/**\n * The library, opened from code, resolving with the file that was chosen.\n *\n * A form does not need to own a dialog for this — `const file = await openMediaPicker()` is the\n * whole integration, and `undefined` means the person closed it.\n */\nexport const openMediaPicker = createModal<MediaFile, MediaPickerOptions>(MediaPicker)\n\n/**\n * The same library, resolving with everything that was chosen.\n *\n * A second function rather than a flag on the first one, because the answer is a different\n * shape: `MediaFile[]` here against `MediaFile` there, said in the signature instead of in a\n * conditional type nobody can read. `undefined` still means the dialog was closed.\n */\nexport const openMediaFiles = createModal<MediaFile[], MediaFilesOptions>(MediaPicker, {\n props: { multiple: true },\n})\n\n/**\n * The whole library as a dialog, from anywhere.\n *\n * The same manager, the same folders, the same uploads — for a screen that needs to put files in\n * order without navigating away from what it was doing. It resolves when a file is opened, and\n * with `undefined` when the dialog is simply closed.\n */\nexport const openMediaLibrary = createModal<MediaFile, Omit<MediaPickerOptions, 'accept'>>(\n MediaPicker,\n { props: { manage: true } },\n)\n","<script setup lang=\"ts\">\nimport { computed, inject, ref, watch } from 'vue'\nimport { adminKey, useTranslate, type AdminContext } from '@webx-ui/module-admin'\nimport {\n WxAction,\n WxActions,\n WxButton,\n WxFileCard,\n WxInput,\n WxPopconfirm,\n WxPopover,\n WxSortableList,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { createMediaApi, type MediaApi } from './api'\nimport { nameFromPath, readable } from './format'\nimport { openMediaFiles, openMediaLibrary } from './openMediaPicker'\nimport { useMediaMessages } from './i18n'\nimport type { MediaAspect, MediaFile, MediaKind, MediaValue } from './types'\n\n/**\n * Several files on a form, in an order somebody chose.\n *\n * One component under `wx-gallery`, `wx-file` and `wx-files`: what differs between them is a\n * layout and a limit, and everything else — picking, ordering, captions, the card of a file\n * that is no longer there — is the same work. The three names exist for the person choosing a\n * field type, not for the code.\n *\n * What is stored is `{ path, alt?, title? }` per item and the order they are in. Everything\n * else on screen — the preview, the name, the size — is asked of the library on every read,\n * because the address of a file is not a thing worth writing into a thousand records.\n */\nconst props = withDefaults(\n defineProps<{\n label?: string\n /** Cards side by side for pictures, a line each for documents. */\n layout?: 'grid' | 'rows'\n /** Narrow what the library offers. The server checks the same thing on the way in. */\n accept?: MediaKind | null\n /** Off where a caption is noise — a row of logos, a set of downloads. */\n captions?: boolean\n max?: number | null\n min?: number | null\n /** A fixed number of columns instead of as many as fit. */\n columns?: number | null\n /** Shape of a thumbnail in the grid: a preset, or anything CSS reads as a ratio. */\n aspect?: MediaAspect | null\n /** One file, kept as a list of one — what `wx-file` is. */\n single?: boolean\n disabled?: boolean\n }>(),\n {\n label: undefined,\n layout: 'grid',\n accept: null,\n captions: true,\n max: null,\n min: null,\n columns: null,\n aspect: null,\n single: false,\n disabled: false,\n },\n)\n\nconst model = defineModel<MediaValue[]>({ default: () => [] })\n\nuseMediaMessages()\n\nconst t = useTranslate('webx-media')\n\n/*\n * The panel, when there is one. A field has to draw itself without it — a demo page or a test\n * has no library to ask — so this is `inject` rather than `useAdmin()`, which throws.\n */\nconst admin: AdminContext | null = inject(adminKey, null)\nconst api: MediaApi | null = admin ? createMediaApi(admin) : null\n\n/** The presets, spelled the way CSS wants them. Anything else is passed through as given. */\nconst RATIOS: Record<string, string> = {\n '16/9': '16 / 9',\n '4/3': '4 / 3',\n '1/1': '1 / 1',\n}\n\nconst items = computed<MediaValue[]>({\n get: () => (Array.isArray(model.value) ? model.value : []),\n set: (next) => {\n model.value = next\n },\n})\n\nconst limit = computed(() => (props.single ? 1 : (props.max ?? null)))\n\nconst full = computed(() => limit.value !== null && items.value.length >= limit.value)\n\n/* ------------------------------------------------------------------ the library --- */\n\n/*\n * What the library says about each key on screen.\n *\n * A key that is not in here yet is a key nothing has been asked about — the field draws what\n * it can from the key itself. A key mapped to `null` is one the library has answered about:\n * the file is gone, and that is what the card says.\n */\nconst files = ref<Record<string, MediaFile | null>>({})\n\nconst asking = new Set<string>()\n\nasync function resolve(paths: string[]): Promise<void> {\n if (!api) return\n\n const wanted = paths.filter((path) => !(path in files.value) && !asking.has(path))\n\n if (wanted.length === 0) return\n\n wanted.forEach((path) => asking.add(path))\n\n const found = await Promise.all(\n wanted.map(async (path) => [path, await api.fileByPath(path)] as const),\n )\n\n wanted.forEach((path) => asking.delete(path))\n files.value = { ...files.value, ...Object.fromEntries(found) }\n}\n\nwatch(\n () => items.value.map((item) => item.path),\n (paths) => void resolve(paths.filter(Boolean)),\n { immediate: true },\n)\n\n/** What was just chosen is already known; asking the library about it again is a round trip. */\nfunction remember(chosen: MediaFile[]): void {\n files.value = { ...files.value, ...Object.fromEntries(chosen.map((file) => [file.path, file])) }\n}\n\nfunction fileOf(item: MediaValue): MediaFile | null | undefined {\n return files.value[item.path]\n}\n\n/** Only an answered question counts as broken: unasked is not the same as missing. */\nfunction broken(item: MediaValue): boolean {\n return fileOf(item) === null\n}\n\nfunction nameOf(item: MediaValue): string {\n return fileOf(item)?.name ?? nameFromPath(item.path)\n}\n\n/*\n * Previews come from the thumbnail the server already made, never from the file itself: a\n * gallery of forty photographs drawn from the originals is forty originals.\n */\nfunction thumbOf(item: MediaValue): string | undefined {\n const file = fileOf(item)\n\n return file && api ? (api.thumb(file, 320, 320) ?? undefined) : undefined\n}\n\nfunction metaOf(item: MediaValue): string {\n const file = fileOf(item)\n\n if (!file) return ''\n\n const size = readable(file.size)\n\n return file.width && file.height ? `${file.width}×${file.height} · ${size}` : size\n}\n\n/* ------------------------------------------------------------------ editing --- */\n\nconst editing = ref<number | null>(null)\n\nfunction keyFor(item: MediaValue, index: number): string {\n // The same file twice in one list is unusual but not forbidden, and two rows under one key\n // is a list Vue cannot tell apart.\n const first = items.value.findIndex((other) => other.path === item.path)\n\n return first === index ? item.path : `${item.path}#${index}`\n}\n\n/**\n * The captions have to be readable as a record even when what was stored is the plain string a\n * column held before the site had a second language.\n */\nfunction words(index: number, key: 'alt' | 'title'): LocalizedValue | string {\n return items.value[index]?.[key] ?? {}\n}\n\nfunction setWords(index: number, key: 'alt' | 'title', next: unknown): void {\n const current = items.value[index]\n\n if (!current) return\n\n const list = [...items.value]\n list[index] = { ...current, [key]: next as LocalizedValue }\n items.value = list\n}\n\nasync function add(): Promise<void> {\n if (props.disabled) return\n\n const room = limit.value === null ? null : limit.value - items.value.length\n const chosen = await openMediaFiles({ accept: props.accept, max: room })\n\n if (!chosen || chosen.length === 0) return\n\n remember(chosen)\n\n const known = new Set(items.value.map((item) => item.path))\n const added = chosen\n .filter((file) => !known.has(file.path))\n .map<MediaValue>((file) => ({ path: file.path }))\n\n // Only the key and the order are kept: the address is worked out on every read, and writing\n // it into the value would be a copy that goes stale the moment the library moves.\n items.value = props.single ? added.slice(0, 1) : [...items.value, ...added]\n}\n\n/** Out of this record, not out of the library — the file is very likely used elsewhere. */\nfunction remove(index: number): void {\n items.value = items.value.filter((_, at) => at !== index)\n editing.value = null\n}\n\n/**\n * The library, over the form.\n *\n * Cropping a file from here would crop it everywhere it is used, so the field does not offer\n * it — it offers the place where those consequences are visible. What comes back from that\n * dialog is ignored; what matters is that the picture may have changed, so it is asked about\n * again.\n */\nasync function openLibrary(item: MediaValue): Promise<void> {\n await openMediaLibrary({})\n\n const rest = { ...files.value }\n delete rest[item.path]\n files.value = rest\n\n void resolve([item.path])\n}\n\n/* ------------------------------------------------------------------ the frame --- */\n\nconst style = computed(() => {\n const columns = props.columns\n ? `repeat(${props.columns}, minmax(0, 1fr))`\n : 'repeat(auto-fill, minmax(140px, 1fr))'\n\n return props.aspect\n ? {\n '--wx-media-list-columns': columns,\n '--wx-media-list-aspect': RATIOS[props.aspect] ?? props.aspect,\n }\n : { '--wx-media-list-columns': columns }\n})\n\nconst hint = computed(() => {\n const count = items.value.length\n\n if (props.min && count < props.min) return t('field.at-least', { count: props.min })\n\n return limit.value === null ? '' : t('field.count', { count, max: limit.value })\n})\n</script>\n\n<template>\n <div\n class=\"wx-media-list\"\n :class=\"[`wx-media-list--${layout}`, { 'is-disabled': disabled }]\"\n :style=\"style\"\n >\n <span v-if=\"label\" class=\"wx-media-list__label\">{{ label }}</span>\n\n <wx-sortable-list\n v-model=\"items\"\n plain\n :disabled=\"disabled\"\n :handle=\"layout === 'grid' ? 'row' : 'grip'\"\n :item-key=\"keyFor\"\n :item-label=\"nameOf\"\n :drag-label=\"t('field.reorder')\"\n :empty-text=\"t('field.empty')\"\n >\n <template #default=\"{ item, index }\">\n <div\n class=\"wx-media-list__cell\"\n :class=\"{ 'is-broken': broken(item), 'is-busy': editing === index }\"\n >\n <wx-file-card\n :name=\"nameOf(item)\"\n :thumbnail=\"thumbOf(item)\"\n :type=\"fileOf(item)?.mime\"\n :size=\"layout === 'rows' ? 'sm' : 'md'\"\n >\n <template #meta>\n <span v-if=\"broken(item)\" class=\"wx-media-list__missing\">\n {{ t('field.missing') }}\n </span>\n <span v-else>{{ metaOf(item) }}</span>\n </template>\n </wx-file-card>\n\n <div v-if=\"!disabled\" class=\"wx-media-list__tools\">\n <wx-actions size=\"sm\">\n <!--\n The captions are asked for beside the picture they describe, the same way the\n single field asks for them: what they are about has to stay in sight while\n they are being written.\n -->\n <wx-popover\n v-if=\"captions\"\n :open=\"editing === index\"\n :title=\"t('field.captions')\"\n :width=\"360\"\n side=\"top\"\n teleport\n @update:open=\"(open: boolean) => (editing = open ? index : null)\"\n >\n <template #trigger>\n <wx-action type=\"edit\" :title=\"t('field.captions')\" />\n </template>\n\n <div class=\"wx-media-list__captions\">\n <div class=\"wx-media-list__caption\">\n <span class=\"wx-media-list__caption-label\">alt</span>\n <wx-input\n localized\n :model-value=\"words(index, 'alt')\"\n :placeholder=\"t('field.alt-hint')\"\n @update:model-value=\"(next: unknown) => setWords(index, 'alt', next)\"\n />\n </div>\n\n <div class=\"wx-media-list__caption\">\n <span class=\"wx-media-list__caption-label\">title</span>\n <wx-input\n localized\n :model-value=\"words(index, 'title')\"\n :placeholder=\"t('field.title-hint')\"\n @update:model-value=\"(next: unknown) => setWords(index, 'title', next)\"\n />\n </div>\n\n <div class=\"wx-media-list__captions-footer\">\n <wx-button size=\"sm\" type=\"primary\" @click=\"editing = null\">\n {{ t('manager.save') }}\n </wx-button>\n </div>\n </div>\n </wx-popover>\n\n <wx-action\n v-if=\"!broken(item)\"\n type=\"goto\"\n :title=\"t('field.open')\"\n @click=\"openLibrary(item)\"\n />\n\n <wx-popconfirm\n :title=\"t('field.remove-title')\"\n :description=\"t('field.remove-text')\"\n :confirm-text=\"t('field.remove-confirm')\"\n :cancel-text=\"t('manager.cancel')\"\n confirm-type=\"danger\"\n side=\"top\"\n @confirm=\"remove(index)\"\n >\n <template #trigger>\n <wx-action type=\"remove\" :title=\"t('field.remove-confirm')\" />\n </template>\n </wx-popconfirm>\n </wx-actions>\n </div>\n </div>\n </template>\n </wx-sortable-list>\n\n <div class=\"wx-media-list__footer\">\n <wx-button size=\"sm\" variant=\"outline\" :disabled=\"disabled || full\" @click=\"add\">\n {{ t('field.add') }}\n </wx-button>\n\n <span v-if=\"hint\" class=\"wx-media-list__hint\">{{ hint }}</span>\n </div>\n </div>\n</template>\n\n<style>\n.wx-media-list {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-8);\n}\n\n.wx-media-list__label {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-media-list__footer {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n}\n\n.wx-media-list__hint {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n}\n\n/*\n * The list brings its own rules with it, and they are written from here through three classes\n * rather than one: a component of the design system carries scoped styles, and a scoped\n * selector is a class plus an attribute — a single class from outside ties with it and loses to\n * whichever stylesheet happens to come last.\n */\n.wx-media-list--grid .wx-sortable-list .wx-sortable-list__body {\n display: grid;\n grid-template-columns: var(--wx-media-list-columns);\n gap: var(--wx-space-8);\n}\n\n.wx-media-list--grid .wx-sortable-list .wx-sortable-list__row {\n display: block;\n padding: 0;\n border-top: none;\n}\n\n.wx-media-list--grid .wx-sortable-list .wx-sortable-list__empty {\n grid-column: 1 / -1;\n padding-inline: 0;\n}\n\n.wx-media-list__cell {\n position: relative;\n min-width: 0;\n}\n\n/* The shape of the frame is the field's to choose; the card's own 4/3 is the default. */\n.wx-media-list--grid .wx-media-list__cell > .wx-file-card > .wx-file-card__preview {\n aspect-ratio: var(--wx-media-list-aspect, 4 / 3);\n}\n\n/*\n * A document in a list is a line, not a card standing on end: the picture shrinks to a mark at\n * the start of the row and the name runs along it.\n */\n.wx-media-list--rows .wx-media-list__cell {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n}\n\n.wx-media-list--rows .wx-media-list__cell > .wx-file-card {\n flex: 1 1 auto;\n flex-direction: row;\n align-items: center;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n.wx-media-list--rows .wx-media-list__cell > .wx-file-card > .wx-file-card__preview {\n flex: 0 0 auto;\n width: 48px;\n aspect-ratio: 1 / 1;\n}\n\n.wx-media-list--rows .wx-media-list__cell > .wx-file-card > .wx-file-card__body {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* The card centres its name under the picture; beside one it belongs at the start of the line. */\n.wx-media-list--rows .wx-media-list__cell .wx-file-card__name,\n.wx-media-list--rows .wx-media-list__cell .wx-file-card__meta {\n text-align: start;\n}\n\n/* A key the library no longer answers about: visible, and with the button to take it out. */\n.wx-media-list__cell.is-broken > .wx-file-card > .wx-file-card__preview {\n border-color: var(--wx-color-danger);\n background: var(--wx-color-danger-soft);\n}\n\n.wx-media-list__missing {\n color: var(--wx-color-danger);\n}\n\n.wx-media-list__tools {\n display: flex;\n padding: 2px;\n border: 1px solid var(--wx-border-muted);\n border-radius: var(--wx-radius-sm);\n background: color-mix(in srgb, var(--wx-bg-surface) 88%, transparent);\n backdrop-filter: blur(2px);\n}\n\n.wx-media-list--grid .wx-media-list__tools {\n position: absolute;\n top: var(--wx-space-8);\n right: var(--wx-space-8);\n}\n\n/*\n * Out of the way until the pointer arrives — but only where one can arrive. A touch screen\n * never hovers, and buttons waiting for a hover that cannot happen are buttons nobody reaches.\n */\n@media (hover: hover) {\n .wx-media-list--grid .wx-media-list__tools {\n opacity: 0;\n transition: opacity var(--wx-duration-fast) var(--wx-easing-standard);\n }\n\n /*\n * `is-busy` among them because the captions panel is teleported: while it is up neither the\n * pointer nor the focus is inside the cell, and the buttons would fade out from under it.\n */\n .wx-media-list--grid .wx-media-list__cell:hover .wx-media-list__tools,\n .wx-media-list--grid .wx-media-list__cell:focus-within .wx-media-list__tools,\n .wx-media-list--grid .wx-media-list__cell.is-busy .wx-media-list__tools {\n opacity: 1;\n }\n}\n\n.wx-media-list__captions {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n width: 100%;\n}\n\n.wx-media-list__caption {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n\n.wx-media-list__caption-label {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n}\n\n.wx-media-list__captions-footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--wx-space-8);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport MediaList from './MediaList.vue'\nimport type { MediaValue } from './types'\n\n/**\n * `wx-file`: one document, on a card that says what it is.\n *\n * The same list underneath, kept to one item. What is stored is a single value rather than a\n * list of one, because that is what a template reading `props.file.url` expects — the shape of\n * the value follows the field, not the component that draws it.\n *\n * The props of the list underneath are not declared here on purpose — see `GalleryField`.\n */\ndefineOptions({ name: 'WxFileField', inheritAttrs: false })\n\nconst model = defineModel<MediaValue | null>({ default: null })\n\nconst list = computed<MediaValue[]>({\n get: () => (model.value ? [model.value] : []),\n set: (next) => {\n model.value = next[0] ?? null\n },\n})\n</script>\n\n<template>\n <media-list v-bind=\"$attrs\" v-model=\"list\" layout=\"rows\" single />\n</template>\n","<script setup lang=\"ts\">\nimport MediaList from './MediaList.vue'\nimport type { MediaValue } from './types'\n\n/**\n * `wx-files`: a list of attachments, one to a line.\n *\n * The props of the list underneath are not declared here on purpose — see `GalleryField`.\n */\ndefineOptions({ name: 'WxFilesField', inheritAttrs: false })\n\nconst model = defineModel<MediaValue[]>({ default: () => [] })\n</script>\n\n<template>\n <media-list v-bind=\"$attrs\" v-model=\"model\" layout=\"rows\" />\n</template>\n","<script setup lang=\"ts\">\nimport MediaList from './MediaList.vue'\nimport type { MediaValue } from './types'\n\n/**\n * `wx-gallery`: a grid of pictures in an order somebody chose.\n *\n * The props of the list underneath are deliberately not declared here. Vue turns an undeclared\n * Boolean prop into `false` rather than leaving it `undefined`, so a wrapper that declares them\n * and passes them on hands the child `false` everywhere the caller asked for nothing — and half\n * the behaviour switches itself off silently. Attributes travel as they were given.\n */\ndefineOptions({ name: 'WxGalleryField', inheritAttrs: false })\n\nconst model = defineModel<MediaValue[]>({ default: () => [] })\n</script>\n\n<template>\n <!-- `accept` last and fixed: a gallery of `.zip` files is not a gallery. -->\n <media-list v-bind=\"$attrs\" v-model=\"model\" layout=\"grid\" accept=\"image\" />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport {\n WxAction,\n WxActions,\n WxButton,\n WxImage,\n WxInput,\n WxPopconfirm,\n WxPopover,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport { createMediaApi } from './api'\nimport { openMediaPicker } from './openMediaPicker'\nimport { useMediaMessages } from './i18n'\nimport type { MediaAspect, MediaKind, MediaValue } from './types'\n\n/**\n * A picture on a form, with the words this entity uses for it.\n *\n * Two states and nothing in between: an empty frame that opens the library, and the picture\n * with what can be done to it. `alt` and `title` live here rather than on the file, because one\n * picture used by two articles needs two captions — and they are localized for the same reason\n * everything else on a form is.\n *\n * What the field stores is the key and those words. The address is worked out when the page is\n * drawn, so moving the library to another disk changes nothing written before.\n */\nconst props = withDefaults(\n defineProps<{\n label?: string\n accept?: MediaKind | null\n /** Off for a decorative picture, where a caption is noise. */\n captions?: boolean\n /**\n * Shape of the frame: one of the presets, any `width / height`, or `null` for a fixed\n * height instead.\n *\n * A field that holds a cover and a field that holds an avatar are not the same shape, and\n * the frame should say which before anything is in it.\n */\n aspect?: MediaAspect | null\n /** Height of the frame when there is no aspect — a number in pixels, or any CSS length. */\n height?: number | string\n /**\n * Widest the frame gets — a number in pixels, or any CSS length. Full width by default;\n * an avatar at the full width of a form is a poster, and with an aspect the height follows.\n */\n width?: number | string | null\n disabled?: boolean\n }>(),\n {\n label: undefined,\n accept: 'image',\n captions: true,\n aspect: '16/9',\n height: 220,\n width: null,\n disabled: false,\n },\n)\n\nconst value = defineModel<MediaValue | null>({ default: null })\n\nuseMediaMessages()\n\nconst t = useTranslate('webx-media')\n\nconst api = createMediaApi(useAdmin())\n\n/** The presets, spelled the way CSS wants them. Anything else is passed through as given. */\nconst RATIOS: Record<string, string> = {\n '16/9': '16 / 9',\n '4/3': '4 / 3',\n '1/1': '1 / 1',\n}\n\nconst editing = ref(false)\n\n/*\n * What a record stores is the key. The address beside it is only there when the picture was\n * chosen in this session, so a form opened on something saved last week has a key and nothing\n * to draw — and the library is the only thing that knows where that key currently lives.\n */\nconst resolved = ref<Record<string, string>>({})\n\nwatch(\n () => value.value?.path,\n async (path) => {\n if (!path || value.value?.url || resolved.value[path]) return\n\n const file = await api.fileByPath(path)\n\n if (file) resolved.value = { ...resolved.value, [path]: file.url }\n },\n { immediate: true },\n)\n\nconst preview = computed(() => {\n const current = value.value\n\n if (!current) return null\n\n return current.url ?? resolved.value[current.path] ?? null\n})\n\nconst length = (size: number | string): string => (typeof size === 'number' ? `${size}px` : size)\n\nconst frameStyle = computed(() => ({\n ...(props.aspect\n ? { aspectRatio: RATIOS[props.aspect] ?? props.aspect }\n : { height: length(props.height) }),\n ...(props.width == null ? {} : { width: '100%', maxWidth: length(props.width) }),\n}))\n\n/**\n * `alt` and `title` are edited through the field, so they have to be readable as a record even\n * when what was stored is the plain string a column held before the site had a second language.\n */\nfunction words(key: 'alt' | 'title'): LocalizedValue | string {\n return value.value?.[key] ?? {}\n}\n\nfunction setWords(key: 'alt' | 'title', next: unknown): void {\n if (!value.value) return\n\n value.value = { ...value.value, [key]: next as LocalizedValue }\n}\n\nasync function choose(): Promise<void> {\n if (props.disabled) return\n\n const file = await openMediaPicker({ accept: props.accept })\n\n if (!file) return\n\n /*\n * Remembered against the key as well as put on the value. The address on the value is the\n * session's own and does not survive a save — the server stores the key and answers with\n * what it stored — and the key has not changed, so the watcher above does not fire either.\n * Without this the picture vanishes from the field the moment the form is saved and comes\n * back only on a reload.\n */\n resolved.value = { ...resolved.value, [file.path]: file.url }\n\n // The captions belong to the entity, so picking a different picture keeps them: usually the\n // words are still right and retyping them in three languages is the punishment for a swap.\n value.value = { ...value.value, path: file.path, url: file.url }\n}\n\n/** Clears the field. The file stays in the library — it is very likely used somewhere else. */\nfunction clear(): void {\n value.value = null\n editing.value = false\n}\n</script>\n\n<template>\n <div class=\"wx-media-field\">\n <span v-if=\"label\" class=\"wx-media-field__label\">{{ label }}</span>\n\n <div v-if=\"!value\" class=\"wx-media-field__frame is-empty\" :style=\"frameStyle\">\n <button type=\"button\" class=\"wx-media-field__pick\" :disabled=\"disabled\" @click=\"choose\">\n {{ t('field.select') }}\n </button>\n </div>\n\n <div v-else class=\"wx-media-field__frame\" :style=\"frameStyle\">\n <!--\n The picture and the buttons are wrapped in elements of our own rather than styled\n through their class: a component of the design system carries scoped rules, and a scoped\n rule is a class plus an attribute, so a single class from outside loses to it — silently,\n and only in a browser.\n\n The picture is also the way to swap it. A button labelled \"replace\" inside the captions\n popover is a second place to look for something the picture itself already offers.\n -->\n <button\n type=\"button\"\n class=\"wx-media-field__picture\"\n :disabled=\"disabled\"\n :title=\"t('field.replace')\"\n @click=\"choose\"\n >\n <wx-image v-if=\"preview\" :src=\"preview\" fit=\"contain\" width=\"100%\" height=\"100%\" />\n <span v-else class=\"wx-media-field__missing\">{{ t('field.no-preview') }}</span>\n </button>\n\n <div v-if=\"!disabled\" class=\"wx-media-field__bar\">\n <wx-actions size=\"sm\">\n <!--\n The captions are asked for where the picture is, not in a dialog over the whole form:\n what they describe has to stay in sight while they are being written.\n -->\n <wx-popover\n v-if=\"captions\"\n v-model:open=\"editing\"\n :title=\"t('field.captions')\"\n :width=\"360\"\n side=\"top\"\n teleport\n >\n <template #trigger>\n <wx-action type=\"edit\" :title=\"t('field.edit')\" />\n </template>\n\n <div class=\"wx-media-field__captions\">\n <div class=\"wx-media-field__caption\">\n <label class=\"wx-media-field__caption-label\" for=\"wx-media-alt\">alt</label>\n <wx-input\n id=\"wx-media-alt\"\n localized\n :model-value=\"words('alt')\"\n :placeholder=\"t('field.alt-hint')\"\n @update:model-value=\"(next) => setWords('alt', next)\"\n />\n </div>\n\n <div class=\"wx-media-field__caption\">\n <label class=\"wx-media-field__caption-label\" for=\"wx-media-title\">title</label>\n <wx-input\n id=\"wx-media-title\"\n localized\n :model-value=\"words('title')\"\n :placeholder=\"t('field.title-hint')\"\n @update:model-value=\"(next) => setWords('title', next)\"\n />\n </div>\n\n <div class=\"wx-media-field__captions-footer\">\n <wx-button size=\"sm\" type=\"primary\" @click=\"editing = false\">\n {{ t('manager.save') }}\n </wx-button>\n </div>\n </div>\n </wx-popover>\n\n <!--\n Clearing the field is not deleting the file: the same picture is very likely used by\n another record, and this is the one place somebody would expect otherwise.\n -->\n <wx-popconfirm\n :title=\"t('field.clear-title')\"\n :description=\"t('field.clear-text')\"\n :confirm-text=\"t('field.clear-confirm')\"\n :cancel-text=\"t('manager.cancel')\"\n confirm-type=\"danger\"\n side=\"top\"\n @confirm=\"clear\"\n >\n <template #trigger>\n <wx-action type=\"remove\" :title=\"t('field.clear-confirm')\" />\n </template>\n </wx-popconfirm>\n </wx-actions>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n.wx-media-field {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-6);\n}\n\n.wx-media-field__label {\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-media-field__frame {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n padding: var(--wx-space-8);\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n overflow: hidden;\n}\n\n.wx-media-field__frame.is-empty {\n border-style: dashed;\n padding: 0;\n}\n\n.wx-media-field__pick {\n width: 100%;\n height: 100%;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--wx-text-placeholder);\n font-family: inherit;\n font-size: var(--wx-font-size-md);\n cursor: pointer;\n transition: color var(--wx-duration-fast) var(--wx-easing-standard);\n}\n\n.wx-media-field__pick:hover:not(:disabled) {\n color: var(--wx-text-muted);\n}\n\n.wx-media-field__pick:disabled {\n cursor: not-allowed;\n}\n\n.wx-media-field__picture {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n min-width: 0;\n padding: 0;\n border: none;\n /* Inscribed in the frame, so rounded a step less than it — concentric, not parallel. */\n border-radius: var(--wx-radius-xs);\n overflow: hidden;\n background: transparent;\n cursor: pointer;\n}\n\n.wx-media-field__picture:disabled {\n cursor: not-allowed;\n}\n\n.wx-media-field__missing {\n color: var(--wx-text-placeholder);\n font-size: var(--wx-font-size-sm);\n}\n\n/* On the picture, out of the way of it: the frame is the preview, not a toolbar. */\n.wx-media-field__bar {\n position: absolute;\n /* Clear of the frame's inner edge rather than sitting on it. */\n bottom: var(--wx-space-16);\n left: 50%;\n transform: translateX(-50%);\n padding: var(--wx-space-4);\n border-radius: var(--wx-radius-control);\n background: var(--wx-bg-surface);\n box-shadow: var(--wx-shadow-sm);\n}\n\n/* A label belongs to the field under it, so it sits closer to that than to the next one. */\n.wx-media-field__captions {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n width: 100%;\n}\n\n.wx-media-field__caption {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-2);\n}\n\n.wx-media-field__caption-label {\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-xs);\n}\n\n.wx-media-field__captions-footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--wx-space-8);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate, WxListScreen, type ScreenAction } from '@webx-ui/module-admin'\nimport { WxCard, type TabItem, type TabValue } from '@webx-ui/core'\nimport MediaManager from './MediaManager.vue'\nimport { useMediaMessages } from './i18n'\nimport type { MediaKind } from './types'\n\n/**\n * The library as a page of the panel.\n *\n * It looks like the other lists: the section's name on its own line, the one action the\n * section exists for beside it, the kinds of file as the views of the list, and the manager\n * under them in a card.\n *\n * Uploading is that action, so it is a filled primary button with a word on it rather than the\n * third grey icon in a row of six (§18). Blue and not green: in this system green means\n * \"it worked\", and a green button says \"uploaded\" where it means \"upload\". The library brings\n * its own card because the card is what scrolls here.\n */\nconst context = useAdmin()\nuseMediaMessages()\n\nconst t = useTranslate('webx-media')\n\nconst manager = useTemplateRef<{ upload: () => void }>('manager')\nconst kind = ref<TabValue>('all')\n/* The tabs speak in tab values; the manager speaks in kinds of file. */\nconst fileKind = computed(() => kind.value as MediaKind | 'all')\n\nconst canUpload = computed(() => context.can('media.upload') || context.can('media.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'media')?.title ??\n t('module.title'),\n)\n\n/* The same kinds the toolbar used to hide in a dropdown; here they are what the list is. */\nconst views = computed<TabItem[]>(() => [\n { value: 'all', label: t('manager.all-types') },\n ...(['image', 'video', 'audio', 'document', 'other'] as MediaKind[]).map((one) => ({\n value: one,\n label: t(`manager.${one}`),\n })),\n])\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canUpload.value\n ? [\n {\n key: 'upload',\n label: t('manager.upload'),\n icon: 'upload',\n primary: true,\n run: () => manager.value?.upload(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <wx-list-screen\n v-model:view=\"kind\"\n :title=\"title\"\n :views=\"views\"\n :actions=\"actions\"\n :card=\"false\"\n fill\n >\n <wx-card class=\"wx-media-page\" padding=\"md\">\n <media-manager ref=\"manager\" :type=\"fileKind\" in-page />\n </wx-card>\n </wx-list-screen>\n</template>\n\n<style>\n.wx-media-page {\n height: 100%;\n min-height: 0;\n}\n\n/* The card is the page: its body is what scrolls, not the page behind it. */\n.wx-media-page > .wx-card__body {\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport FileField from './FileField.vue'\nimport FilesField from './FilesField.vue'\nimport GalleryField from './GalleryField.vue'\nimport MediaField from './MediaField.vue'\nimport MediaPage from './MediaPage.vue'\nimport { mediaMessages } from './messages'\nimport { openMediaPicker } from './openMediaPicker'\n\nexport interface MediaOptions {\n /** Where the section lives inside the panel. */\n path?: string\n}\n\n/**\n * The file manager as a section of the panel.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: a section shows up when both halves are installed, and stays out of the way when\n * only one is.\n */\nexport function media(options: MediaOptions = {}): AdminModule {\n const path = options.path ?? '/media'\n\n return {\n id: 'media',\n path,\n routes: [\n {\n path,\n name: 'webx.media',\n component: MediaPage,\n },\n ],\n // What a screen means by these four: the fields over the library. The server registers the\n // same names for the values it stores. Three of them are one component with a layout and a\n // limit — the names are what an author picks from a list, and nobody picks \"media with\n // `multiple: true` and `accept: document`\".\n types: {\n 'wx-media': { component: MediaField, kind: 'field' },\n 'wx-gallery': { component: GalleryField, kind: 'field', wide: true },\n 'wx-file': { component: FileField, kind: 'field' },\n 'wx-files': { component: FilesField, kind: 'field', wide: true },\n },\n /**\n * Where the panel's own fields get a picture from. `wx-rich-text` lives in\n * `module-admin`, which cannot reach the library — the dependency runs this way — so the\n * module that has the files hands the way in, and a panel without a file manager simply\n * has no image button in its editors.\n */\n pickImage: async () => {\n const file = await openMediaPicker({ accept: 'image' })\n\n // The key travels with the address, and it is the key that is kept: a document written\n // today has to still find its pictures after the library moves disks.\n return file ? { url: file.url, path: file.path } : null\n },\n }\n}\n\nexport { mediaMessages }\nexport { createMediaApi, type MediaApi } from './api'\nexport {\n openMediaFiles,\n openMediaLibrary,\n openMediaPicker,\n type MediaFilesOptions,\n type MediaPickerOptions,\n} from './openMediaPicker'\nexport { default as WxMediaManager } from './MediaManager.vue'\nexport { default as WxMediaPage } from './MediaPage.vue'\nexport { default as WxMediaPicker } from './MediaPicker.vue'\nexport { default as WxMediaField } from './MediaField.vue'\nexport { default as WxGalleryField } from './GalleryField.vue'\nexport { default as WxFileField } from './FileField.vue'\nexport { default as WxFilesField } from './FilesField.vue'\nexport type {\n DirectoryNotEmpty,\n EditOperations,\n FileQuery,\n MediaDirectory,\n MediaFile,\n MediaAspect,\n MediaKind,\n MediaPage,\n MediaValue,\n} from './types'\n"],"names":["createMediaApi","admin","base","data","body","parentId","title","id","force","query","path","directoryId","files","onProgress","file","resolve","reject","request","token","csrfToken","event","answer","name","ids","operations","width","height","fit","url","cookie","part","readable","bytes","units","size","unit","nameFromPath","props","__props","selected","_useModel","emit","__emit","t","useTranslate","nodes","ref","expanded","watch","directories","label","keysOf","directory","onDrop","parent","_createBlock","_unref","WxTree","$event","empty","computed","copied","toast","copyFailed","area","done","WxSelectionArea","_withCtx","isSelected","_createElementBlock","_Fragment","_renderList","WxFileCard","_directive_wx_select","WxEmpty","search","type","sort","types","kind","sorts","typeLabel","one","sortLabel","_openBlock","_hoisted_1","WxAction","_createVNode","WxInput","WxDropdown","option","WxDropdownItem","_createTextVNode","_toDisplayString","WxActions","open","dismiss","useModal","options","flatten","target","WxDialog","WxSpace","WxButton","_cache","WxSelect","input","useTemplateRef","onMounted","nextTick","submit","value","_createElementVNode","mediaMessages","seeded","useMediaMessages","i18n","useAdmin","api","message","useErrorText","editorLabels","root","useElementWidth","compact","current","page","busy","foldersOpen","picker","rows","canManage","canUpload","folder","find","picksOne","load","loadFiles","debounce","narrow","to","choose","upload","chosen","stored","duplicates","error","askName","createModal","NameDialog","createFolder","renameFolder","deleteFolder","confirm","counts","countsOf","moveFolder","askWhereTo","MoveDialog","moveSelected","removeSelected","rename","remove","edit","result","openImageEditor","walk","node","deeper","__expose","run","wait","timer","_normalizeClass","DirectoryTree","_hoisted_2","MediaToolbar","FileGrid","_hoisted_4","_hoisted_5","WxPagination","WxDrawer","_hoisted_6","MediaManager","openMediaPicker","MediaPicker","openMediaFiles","openMediaLibrary","model","inject","adminKey","RATIOS","items","next","limit","full","asking","paths","wanted","found","item","remember","fileOf","broken","nameOf","thumbOf","metaOf","editing","keyFor","index","other","words","key","setWords","list","add","room","known","added","_","at","openLibrary","rest","style","columns","hint","count","WxSortableList","_hoisted_3","WxPopover","_hoisted_7","_hoisted_8","WxPopconfirm","_hoisted_9","_hoisted_10","MediaList","_mergeProps","_ctx","resolved","preview","length","frameStyle","clear","WxImage","context","manager","fileKind","module","views","actions","WxListScreen","WxCard","media","MediaPage","MediaField","GalleryField","FileField","FilesField"],"mappings":"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"}