@strapi/upload 5.51.0 → 5.51.1
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/admin/future/pages/Assets/AssetsPage.js +198 -129
- package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/future/pages/Assets/AssetsPage.mjs +199 -130
- package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js +158 -19
- package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +159 -20
- package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.js +212 -37
- package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +213 -38
- package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js +252 -0
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs +250 -0
- package/dist/admin/future/pages/Assets/components/BulkActionsBar.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js +209 -0
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs +207 -0
- package/dist/admin/future/pages/Assets/components/BulkMoveDialog.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js +111 -36
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs +113 -38
- package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.js +120 -13
- package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.mjs +120 -13
- package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js +71 -0
- package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs +69 -0
- package/dist/admin/future/pages/Assets/components/Dnd/buildDragSet.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/computeValidDropTargets.js +34 -0
- package/dist/admin/future/pages/Assets/components/Dnd/computeValidDropTargets.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/computeValidDropTargets.mjs +32 -0
- package/dist/admin/future/pages/Assets/components/Dnd/computeValidDropTargets.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/dndIds.js +15 -0
- package/dist/admin/future/pages/Assets/components/Dnd/dndIds.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/dndIds.mjs +13 -1
- package/dist/admin/future/pages/Assets/components/Dnd/dndIds.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js +19 -25
- package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs +19 -25
- package/dist/admin/future/pages/Assets/components/Dnd/useAssetDnd.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/Dnd/useFolderTreeDroppable.js +35 -0
- package/dist/admin/future/pages/Assets/components/Dnd/useFolderTreeDroppable.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/Dnd/useFolderTreeDroppable.mjs +33 -0
- package/dist/admin/future/pages/Assets/components/Dnd/useFolderTreeDroppable.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js +120 -22
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs +121 -23
- package/dist/admin/future/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
- package/dist/admin/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.js +22 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.mjs +19 -0
- package/dist/admin/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.js +165 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.js.map +1 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.mjs +163 -0
- package/dist/admin/future/pages/Assets/components/SortMenu.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js +59 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs +55 -0
- package/dist/admin/future/pages/Assets/hooks/useAssetSelection.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.js +124 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.js.map +1 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.mjs +120 -0
- package/dist/admin/future/pages/Assets/hooks/useListSort.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/listQueryKey.js +29 -0
- package/dist/admin/future/pages/Assets/utils/listQueryKey.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/listQueryKey.mjs +27 -0
- package/dist/admin/future/pages/Assets/utils/listQueryKey.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/mergeMixedList.js +84 -0
- package/dist/admin/future/pages/Assets/utils/mergeMixedList.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/mergeMixedList.mjs +81 -0
- package/dist/admin/future/pages/Assets/utils/mergeMixedList.mjs.map +1 -0
- package/dist/admin/future/pages/Assets/utils/selection.js +104 -0
- package/dist/admin/future/pages/Assets/utils/selection.js.map +1 -0
- package/dist/admin/future/pages/Assets/utils/selection.mjs +94 -0
- package/dist/admin/future/pages/Assets/utils/selection.mjs.map +1 -0
- package/dist/admin/future/services/assets.js +33 -1
- package/dist/admin/future/services/assets.js.map +1 -1
- package/dist/admin/future/services/assets.mjs +33 -2
- package/dist/admin/future/services/assets.mjs.map +1 -1
- package/dist/admin/future/services/folders.js +3 -3
- package/dist/admin/future/services/folders.js.map +1 -1
- package/dist/admin/future/services/folders.mjs +3 -3
- package/dist/admin/future/services/folders.mjs.map +1 -1
- package/dist/admin/future/utils/buildDragSetFromSelection.js +33 -0
- package/dist/admin/future/utils/buildDragSetFromSelection.js.map +1 -0
- package/dist/admin/future/utils/buildDragSetFromSelection.mjs +31 -0
- package/dist/admin/future/utils/buildDragSetFromSelection.mjs.map +1 -0
- package/dist/admin/future/utils/canDropItemOnFolder.js +4 -0
- package/dist/admin/future/utils/canDropItemOnFolder.js.map +1 -1
- package/dist/admin/future/utils/canDropItemOnFolder.mjs +4 -0
- package/dist/admin/future/utils/canDropItemOnFolder.mjs.map +1 -1
- package/dist/admin/future/utils/flattenFolderStructure.js +28 -0
- package/dist/admin/future/utils/flattenFolderStructure.js.map +1 -0
- package/dist/admin/future/utils/flattenFolderStructure.mjs +26 -0
- package/dist/admin/future/utils/flattenFolderStructure.mjs.map +1 -0
- package/dist/admin/future/utils/formatMoveSuccessMessage.js +19 -0
- package/dist/admin/future/utils/formatMoveSuccessMessage.js.map +1 -0
- package/dist/admin/future/utils/formatMoveSuccessMessage.mjs +17 -0
- package/dist/admin/future/utils/formatMoveSuccessMessage.mjs.map +1 -0
- package/dist/admin/future/utils/getFolderLabel.js +31 -0
- package/dist/admin/future/utils/getFolderLabel.js.map +1 -0
- package/dist/admin/future/utils/getFolderLabel.mjs +29 -0
- package/dist/admin/future/utils/getFolderLabel.mjs.map +1 -0
- package/dist/admin/src/future/pages/Assets/components/AssetsTable.d.ts +7 -1
- package/dist/admin/src/future/pages/Assets/components/BulkActionsBar.d.ts +1 -0
- package/dist/admin/src/future/pages/Assets/components/BulkMoveDialog.d.ts +13 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/AssetsDndProvider.d.ts +2 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/DragOverlayChip.d.ts +2 -2
- package/dist/admin/src/future/pages/Assets/components/Dnd/buildDragSet.d.ts +17 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/computeValidDropTargets.d.ts +7 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/dndIds.d.ts +3 -0
- package/dist/admin/src/future/pages/Assets/components/Dnd/useAssetDnd.d.ts +3 -1
- package/dist/admin/src/future/pages/Assets/components/Dnd/useFolderTreeDroppable.d.ts +19 -0
- package/dist/admin/src/future/pages/Assets/components/FolderTree/useSpringLoadedExpand.d.ts +6 -0
- package/dist/admin/src/future/pages/Assets/components/SortMenu.d.ts +17 -0
- package/dist/admin/src/future/pages/Assets/hooks/useAssetSelection.d.ts +39 -0
- package/dist/admin/src/future/pages/Assets/hooks/useListSort.d.ts +47 -0
- package/dist/admin/src/future/pages/Assets/utils/listQueryKey.d.ts +30 -0
- package/dist/admin/src/future/pages/Assets/utils/mergeMixedList.d.ts +40 -0
- package/dist/admin/src/future/pages/Assets/utils/selection.d.ts +50 -0
- package/dist/admin/src/future/services/assets.d.ts +4 -1
- package/dist/admin/src/future/services/folders.d.ts +4 -1
- package/dist/admin/src/future/types/dnd.d.ts +5 -0
- package/dist/admin/src/future/utils/buildDragSetFromSelection.d.ts +11 -0
- package/dist/admin/src/future/utils/canDropItemOnFolder.d.ts +1 -1
- package/dist/admin/src/future/utils/flattenFolderStructure.d.ts +18 -0
- package/dist/admin/src/future/utils/formatMoveSuccessMessage.d.ts +14 -0
- package/dist/admin/src/future/utils/getFolderLabel.d.ts +11 -0
- package/dist/admin/translations/en.json.js +33 -0
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +33 -0
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/shared/contracts/folders.d.ts +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useListSort.mjs","sources":["../../../../../../admin/src/future/pages/Assets/hooks/useListSort.ts"],"sourcesContent":["import { useQueryParams } from '@strapi/admin/strapi-admin';\n\n/**\n * Sort state for the assets list, backed by URL query params so the current\n * ordering is shareable and survives Back/refresh:\n *\n * - `?sort=<rule>` — a single whitelisted rule. Omitted → default (most\n * recent updates).\n * - `?folders=mixed` — folders interleaved with files. Omitted → folders on top.\n *\n * The date rules and the direction rules are mutually exclusive: exactly one\n * rule is active at a time (one checkmark total, like the design mock). Picking\n * an option in one group clears the other; clearing the active option falls\n * back to the default — an unsorted list is not a state the UI offers.\n */\n\nexport const SORT_BY_RULES = {\n oldestUploads: 'createdAt:ASC',\n mostRecentUpdates: 'updatedAt:DESC',\n} as const;\n\nexport const SORT_DIRECTION_RULES = {\n nameAsc: 'name:ASC',\n nameDesc: 'name:DESC',\n sizeAsc: 'size:ASC',\n sizeDesc: 'size:DESC',\n} as const;\n\nexport type SortByKey = keyof typeof SORT_BY_RULES;\nexport type SortDirectionKey = keyof typeof SORT_DIRECTION_RULES;\nexport type FoldersPosition = 'top' | 'mixed';\n\nconst DEFAULT_SORT_BY: SortByKey = 'mostRecentUpdates';\n\nconst SORT_BY_BY_RULE = Object.fromEntries(\n Object.entries(SORT_BY_RULES).map(([key, rule]) => [rule, key])\n) as Record<string, SortByKey>;\n\nconst SORT_DIRECTION_BY_RULE = Object.fromEntries(\n Object.entries(SORT_DIRECTION_RULES).map(([key, rule]) => [rule, key])\n) as Record<string, SortDirectionKey>;\n\ninterface ParsedSort {\n sortBy: SortByKey | null;\n direction: SortDirectionKey | null;\n /** False when the state is the fallback default (no recognized rule in the URL). */\n isExplicit: boolean;\n}\n\n/** Parses `?sort=` — the first recognized rule wins, unknown rules are dropped. */\nconst parseSortParam = (raw: string | undefined): ParsedSort => {\n for (const rule of (raw ?? '').split(',')) {\n if (rule in SORT_BY_BY_RULE) {\n return { sortBy: SORT_BY_BY_RULE[rule], direction: null, isExplicit: true };\n }\n if (rule in SORT_DIRECTION_BY_RULE) {\n return { sortBy: null, direction: SORT_DIRECTION_BY_RULE[rule], isExplicit: true };\n }\n }\n\n return { sortBy: DEFAULT_SORT_BY, direction: null, isExplicit: false };\n};\n\nconst serializeSort = (sortBy: SortByKey | null, direction: SortDirectionKey | null): string => {\n const rules: (string | null)[] = [\n sortBy && SORT_BY_RULES[sortBy],\n direction && SORT_DIRECTION_RULES[direction],\n ];\n\n return rules.filter((rule): rule is string => Boolean(rule)).join(',');\n};\n\nexport interface ListSort {\n sortBy: SortByKey | null;\n direction: SortDirectionKey | null;\n foldersPosition: FoldersPosition;\n /**\n * The active rule for the files query, e.g. `updatedAt:DESC` — always a\n * single rule since the sort groups are mutually exclusive.\n */\n assetsSort: string;\n /**\n * Rule applicable to the folders query. In the default state (no sort in the\n * URL) folders keep their alphabetical order so the band matches the sidebar\n * tree; an explicit user sort applies, except size rules (folders have no\n * `size` column), which fall back to alphabetical.\n */\n foldersSort: string;\n setSortBy: (key: SortByKey | null) => void;\n setDirection: (key: SortDirectionKey | null) => void;\n setFoldersPosition: (position: FoldersPosition) => void;\n}\n\nexport const useListSort = (): ListSort => {\n const [{ query }, setQuery] = useQueryParams<{ sort?: string; folders?: string }>();\n\n const { sortBy, direction, isExplicit } = parseSortParam(query?.sort);\n const foldersPosition: FoldersPosition = query?.folders === 'mixed' ? 'mixed' : 'top';\n\n const writeSort = (nextSortBy: SortByKey | null, nextDirection: SortDirectionKey | null) => {\n // Exactly one active rule: clearing the active one falls back to default.\n if (nextSortBy === null && nextDirection === null) {\n nextSortBy = DEFAULT_SORT_BY;\n }\n\n const serialized = serializeSort(nextSortBy, nextDirection);\n\n if (nextSortBy === DEFAULT_SORT_BY && nextDirection === null) {\n setQuery({ sort: '' }, 'remove');\n } else {\n setQuery({ sort: serialized });\n }\n };\n\n // Groups are mutually exclusive — picking in one clears the other.\n const setSortBy = (key: SortByKey | null) => writeSort(key, null);\n const setDirection = (key: SortDirectionKey | null) => writeSort(null, key);\n\n const setFoldersPosition = (position: FoldersPosition) => {\n if (position === 'mixed') {\n setQuery({ folders: 'mixed' });\n } else {\n setQuery({ folders: '' }, 'remove');\n }\n };\n\n const assetsSort = serializeSort(sortBy, direction);\n const folderRuleCandidates: (string | null)[] = [\n sortBy && SORT_BY_RULES[sortBy],\n direction && !direction.startsWith('size') ? SORT_DIRECTION_RULES[direction] : null,\n ];\n const folderRules = folderRuleCandidates.filter((rule): rule is string => Boolean(rule));\n // Default state keeps folders alphabetical (matches the sidebar tree) — only\n // an explicit user sort reorders the folders band.\n const foldersSort = isExplicit && folderRules.length > 0 ? folderRules.join(',') : 'name:ASC';\n\n return {\n sortBy,\n direction,\n foldersPosition,\n assetsSort,\n foldersSort,\n setSortBy,\n setDirection,\n setFoldersPosition,\n };\n};\n"],"names":["SORT_BY_RULES","oldestUploads","mostRecentUpdates","SORT_DIRECTION_RULES","nameAsc","nameDesc","sizeAsc","sizeDesc","DEFAULT_SORT_BY","SORT_BY_BY_RULE","Object","fromEntries","entries","map","key","rule","SORT_DIRECTION_BY_RULE","parseSortParam","raw","split","sortBy","direction","isExplicit","serializeSort","rules","filter","Boolean","join","useListSort","query","setQuery","useQueryParams","sort","foldersPosition","folders","writeSort","nextSortBy","nextDirection","serialized","setSortBy","setDirection","setFoldersPosition","position","assetsSort","folderRuleCandidates","startsWith","folderRules","foldersSort","length"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fingerprint for "which asset list the user is looking at".
|
|
5
|
+
*
|
|
6
|
+
* Selection clears when this key changes (see ClearSelectionOnChange in AssetsPage).
|
|
7
|
+
*
|
|
8
|
+
* Hybrid rule
|
|
9
|
+
* - Folder, search, sort, filter changes → new key → selection clears.
|
|
10
|
+
* - Switching table/grid view does NOT change the key — both views render the
|
|
11
|
+
* same list, so the selection survives the toggle.
|
|
12
|
+
* - Infinite scroll (load more) does NOT change any segment → selection persists.
|
|
13
|
+
*
|
|
14
|
+
* Search/filter use stable defaults until their header controls are wired;
|
|
15
|
+
* follow-up PRs only pass real values — no selection logic changes needed.
|
|
16
|
+
*/ /**
|
|
17
|
+
* Builds a stable fingerprint for the current asset list query.
|
|
18
|
+
*
|
|
19
|
+
* Selection clears when this key changes. Infinite scroll does not change any
|
|
20
|
+
* segment, so selection persists across load-more.
|
|
21
|
+
*/ const getListQueryKey = ({ folderId, search, sort, filter })=>JSON.stringify({
|
|
22
|
+
folderId,
|
|
23
|
+
search,
|
|
24
|
+
sort,
|
|
25
|
+
filter
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
exports.getListQueryKey = getListQueryKey;
|
|
29
|
+
//# sourceMappingURL=listQueryKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listQueryKey.js","sources":["../../../../../../admin/src/future/pages/Assets/utils/listQueryKey.ts"],"sourcesContent":["/**\n * Fingerprint for \"which asset list the user is looking at\".\n *\n * Selection clears when this key changes (see ClearSelectionOnChange in AssetsPage).\n *\n * Hybrid rule\n * - Folder, search, sort, filter changes → new key → selection clears.\n * - Switching table/grid view does NOT change the key — both views render the\n * same list, so the selection survives the toggle.\n * - Infinite scroll (load more) does NOT change any segment → selection persists.\n *\n * Search/filter use stable defaults until their header controls are wired;\n * follow-up PRs only pass real values — no selection logic changes needed.\n */\nexport interface ListQueryKeyInput {\n folderId: number | null;\n /** Empty string until search UI ships. */\n search: string;\n /** Null until sort UI ships. */\n sort: string | null;\n /** Null until filter UI ships. */\n filter: string | null;\n}\n\n/**\n * Builds a stable fingerprint for the current asset list query.\n *\n * Selection clears when this key changes. Infinite scroll does not change any\n * segment, so selection persists across load-more.\n */\nexport const getListQueryKey = ({ folderId, search, sort, filter }: ListQueryKeyInput): string =>\n JSON.stringify({ folderId, search, sort, filter });\n"],"names":["getListQueryKey","folderId","search","sort","filter","JSON","stringify"],"mappings":";;AAAA;;;;;;;;;;;;;;;;;;AA6BC,IACM,MAAMA,eAAAA,GAAkB,CAAC,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,MAAM,EAAqB,GACnFC,IAAAA,CAAKC,SAAS,CAAC;AAAEL,QAAAA,QAAAA;AAAUC,QAAAA,MAAAA;AAAQC,QAAAA,IAAAA;AAAMC,QAAAA;KAAO;;;;"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fingerprint for "which asset list the user is looking at".
|
|
3
|
+
*
|
|
4
|
+
* Selection clears when this key changes (see ClearSelectionOnChange in AssetsPage).
|
|
5
|
+
*
|
|
6
|
+
* Hybrid rule
|
|
7
|
+
* - Folder, search, sort, filter changes → new key → selection clears.
|
|
8
|
+
* - Switching table/grid view does NOT change the key — both views render the
|
|
9
|
+
* same list, so the selection survives the toggle.
|
|
10
|
+
* - Infinite scroll (load more) does NOT change any segment → selection persists.
|
|
11
|
+
*
|
|
12
|
+
* Search/filter use stable defaults until their header controls are wired;
|
|
13
|
+
* follow-up PRs only pass real values — no selection logic changes needed.
|
|
14
|
+
*/ /**
|
|
15
|
+
* Builds a stable fingerprint for the current asset list query.
|
|
16
|
+
*
|
|
17
|
+
* Selection clears when this key changes. Infinite scroll does not change any
|
|
18
|
+
* segment, so selection persists across load-more.
|
|
19
|
+
*/ const getListQueryKey = ({ folderId, search, sort, filter })=>JSON.stringify({
|
|
20
|
+
folderId,
|
|
21
|
+
search,
|
|
22
|
+
sort,
|
|
23
|
+
filter
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export { getListQueryKey };
|
|
27
|
+
//# sourceMappingURL=listQueryKey.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listQueryKey.mjs","sources":["../../../../../../admin/src/future/pages/Assets/utils/listQueryKey.ts"],"sourcesContent":["/**\n * Fingerprint for \"which asset list the user is looking at\".\n *\n * Selection clears when this key changes (see ClearSelectionOnChange in AssetsPage).\n *\n * Hybrid rule\n * - Folder, search, sort, filter changes → new key → selection clears.\n * - Switching table/grid view does NOT change the key — both views render the\n * same list, so the selection survives the toggle.\n * - Infinite scroll (load more) does NOT change any segment → selection persists.\n *\n * Search/filter use stable defaults until their header controls are wired;\n * follow-up PRs only pass real values — no selection logic changes needed.\n */\nexport interface ListQueryKeyInput {\n folderId: number | null;\n /** Empty string until search UI ships. */\n search: string;\n /** Null until sort UI ships. */\n sort: string | null;\n /** Null until filter UI ships. */\n filter: string | null;\n}\n\n/**\n * Builds a stable fingerprint for the current asset list query.\n *\n * Selection clears when this key changes. Infinite scroll does not change any\n * segment, so selection persists across load-more.\n */\nexport const getListQueryKey = ({ folderId, search, sort, filter }: ListQueryKeyInput): string =>\n JSON.stringify({ folderId, search, sort, filter });\n"],"names":["getListQueryKey","folderId","search","sort","filter","JSON","stringify"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;AA6BC,IACM,MAAMA,eAAAA,GAAkB,CAAC,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,MAAM,EAAqB,GACnFC,IAAAA,CAAKC,SAAS,CAAC;AAAEL,QAAAA,QAAAA;AAAUC,QAAAA,MAAAA;AAAQC,QAAAA,IAAAA;AAAMC,QAAAA;KAAO;;;;"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const getComparableValue = (record, field)=>{
|
|
4
|
+
switch(field){
|
|
5
|
+
case 'createdAt':
|
|
6
|
+
case 'updatedAt':
|
|
7
|
+
return record[field] ? new Date(record[field]).getTime() : 0;
|
|
8
|
+
case 'size':
|
|
9
|
+
// Folders have no size — they compare as 0 (start of ascending, end of descending).
|
|
10
|
+
return record.size ?? 0;
|
|
11
|
+
case 'name':
|
|
12
|
+
default:
|
|
13
|
+
return (record.name ?? '').toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Builds a comparator from comma-separated sort rules (`updatedAt:DESC,name:ASC`).
|
|
18
|
+
* Rules apply in order; the first non-equal field decides. Client-side
|
|
19
|
+
* approximation of the server ordering — good enough to slot folders between
|
|
20
|
+
* files that were sorted server-side by the same rules.
|
|
21
|
+
*/ const buildComparator = (sort)=>{
|
|
22
|
+
const rules = sort.split(',').map((rule)=>rule.trim()).filter(Boolean).map((rule)=>{
|
|
23
|
+
const [field, direction] = rule.split(':');
|
|
24
|
+
return {
|
|
25
|
+
field,
|
|
26
|
+
desc: direction?.toUpperCase() === 'DESC'
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
return (a, b)=>{
|
|
30
|
+
for (const { field, desc } of rules){
|
|
31
|
+
const va = getComparableValue(a, field);
|
|
32
|
+
const vb = getComparableValue(b, field);
|
|
33
|
+
let cmp;
|
|
34
|
+
if (typeof va === 'string' || typeof vb === 'string') {
|
|
35
|
+
cmp = String(va) < String(vb) ? -1 : String(va) > String(vb) ? 1 : 0;
|
|
36
|
+
} else {
|
|
37
|
+
cmp = va - vb;
|
|
38
|
+
}
|
|
39
|
+
if (cmp !== 0) {
|
|
40
|
+
return desc ? -cmp : cmp;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Interleaves the complete folder list into the (possibly partially loaded)
|
|
48
|
+
* asset stream according to the active sort. Ties order folders first.
|
|
49
|
+
*/ const mergeMixedList = ({ folders, assets, sort, hasNextPage })=>{
|
|
50
|
+
const compare = buildComparator(sort);
|
|
51
|
+
const sortedFolders = [
|
|
52
|
+
...folders
|
|
53
|
+
].sort(compare);
|
|
54
|
+
const lastAsset = assets[assets.length - 1];
|
|
55
|
+
const placeableFolders = !hasNextPage || !lastAsset ? hasNextPage ? [] // nothing loaded yet — wait for the first page
|
|
56
|
+
: sortedFolders : sortedFolders.filter((folder)=>compare(folder, lastAsset) <= 0);
|
|
57
|
+
const items = [];
|
|
58
|
+
let folderIndex = 0;
|
|
59
|
+
for (const asset of assets){
|
|
60
|
+
while(folderIndex < placeableFolders.length && compare(placeableFolders[folderIndex], asset) <= 0){
|
|
61
|
+
items.push({
|
|
62
|
+
kind: 'folder',
|
|
63
|
+
folder: placeableFolders[folderIndex]
|
|
64
|
+
});
|
|
65
|
+
folderIndex += 1;
|
|
66
|
+
}
|
|
67
|
+
items.push({
|
|
68
|
+
kind: 'asset',
|
|
69
|
+
asset
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
while(folderIndex < placeableFolders.length){
|
|
73
|
+
items.push({
|
|
74
|
+
kind: 'folder',
|
|
75
|
+
folder: placeableFolders[folderIndex]
|
|
76
|
+
});
|
|
77
|
+
folderIndex += 1;
|
|
78
|
+
}
|
|
79
|
+
return items;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
exports.buildComparator = buildComparator;
|
|
83
|
+
exports.mergeMixedList = mergeMixedList;
|
|
84
|
+
//# sourceMappingURL=mergeMixedList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeMixedList.js","sources":["../../../../../../admin/src/future/pages/Assets/utils/mergeMixedList.ts"],"sourcesContent":["import type { File } from '../../../../../../shared/contracts/files';\nimport type { Folder } from '../../../../../../shared/contracts/folders';\n\nexport type MixedItem = { kind: 'folder'; folder: Folder } | { kind: 'asset'; asset: File };\n\ntype SortableRecord = {\n name?: string | null;\n createdAt?: string | null;\n updatedAt?: string | null;\n size?: number | null;\n};\n\nconst getComparableValue = (record: SortableRecord, field: string): number | string => {\n switch (field) {\n case 'createdAt':\n case 'updatedAt':\n return record[field] ? new Date(record[field]!).getTime() : 0;\n case 'size':\n // Folders have no size — they compare as 0 (start of ascending, end of descending).\n return record.size ?? 0;\n case 'name':\n default:\n return (record.name ?? '').toLowerCase();\n }\n};\n\n/**\n * Builds a comparator from comma-separated sort rules (`updatedAt:DESC,name:ASC`).\n * Rules apply in order; the first non-equal field decides. Client-side\n * approximation of the server ordering — good enough to slot folders between\n * files that were sorted server-side by the same rules.\n */\nexport const buildComparator = (sort: string) => {\n const rules = sort\n .split(',')\n .map((rule) => rule.trim())\n .filter(Boolean)\n .map((rule) => {\n const [field, direction] = rule.split(':');\n return { field, desc: direction?.toUpperCase() === 'DESC' };\n });\n\n return (a: SortableRecord, b: SortableRecord): number => {\n for (const { field, desc } of rules) {\n const va = getComparableValue(a, field);\n const vb = getComparableValue(b, field);\n\n let cmp: number;\n if (typeof va === 'string' || typeof vb === 'string') {\n cmp = String(va) < String(vb) ? -1 : String(va) > String(vb) ? 1 : 0;\n } else {\n cmp = va - vb;\n }\n\n if (cmp !== 0) {\n return desc ? -cmp : cmp;\n }\n }\n\n return 0;\n };\n};\n\ninterface MergeMixedListParams {\n folders: Folder[];\n assets: File[];\n /** Combined sort rules used for the assets query. */\n sort: string;\n /**\n * More asset pages exist. A folder may only be placed once we know no\n * not-yet-loaded asset sorts before it — i.e. it compares ≤ the last loaded\n * asset. Later pages release the remaining folders progressively.\n */\n hasNextPage: boolean;\n}\n\n/**\n * Interleaves the complete folder list into the (possibly partially loaded)\n * asset stream according to the active sort. Ties order folders first.\n */\nexport const mergeMixedList = ({\n folders,\n assets,\n sort,\n hasNextPage,\n}: MergeMixedListParams): MixedItem[] => {\n const compare = buildComparator(sort);\n\n const sortedFolders = [...folders].sort(compare);\n\n const lastAsset = assets[assets.length - 1];\n const placeableFolders =\n !hasNextPage || !lastAsset\n ? hasNextPage\n ? [] // nothing loaded yet — wait for the first page\n : sortedFolders\n : sortedFolders.filter((folder) => compare(folder, lastAsset) <= 0);\n\n const items: MixedItem[] = [];\n let folderIndex = 0;\n\n for (const asset of assets) {\n while (\n folderIndex < placeableFolders.length &&\n compare(placeableFolders[folderIndex], asset) <= 0\n ) {\n items.push({ kind: 'folder', folder: placeableFolders[folderIndex] });\n folderIndex += 1;\n }\n items.push({ kind: 'asset', asset });\n }\n\n while (folderIndex < placeableFolders.length) {\n items.push({ kind: 'folder', folder: placeableFolders[folderIndex] });\n folderIndex += 1;\n }\n\n return items;\n};\n"],"names":["getComparableValue","record","field","Date","getTime","size","name","toLowerCase","buildComparator","sort","rules","split","map","rule","trim","filter","Boolean","direction","desc","toUpperCase","a","b","va","vb","cmp","String","mergeMixedList","folders","assets","hasNextPage","compare","sortedFolders","lastAsset","length","placeableFolders","folder","items","folderIndex","asset","push","kind"],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const getComparableValue = (record, field)=>{
|
|
2
|
+
switch(field){
|
|
3
|
+
case 'createdAt':
|
|
4
|
+
case 'updatedAt':
|
|
5
|
+
return record[field] ? new Date(record[field]).getTime() : 0;
|
|
6
|
+
case 'size':
|
|
7
|
+
// Folders have no size — they compare as 0 (start of ascending, end of descending).
|
|
8
|
+
return record.size ?? 0;
|
|
9
|
+
case 'name':
|
|
10
|
+
default:
|
|
11
|
+
return (record.name ?? '').toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Builds a comparator from comma-separated sort rules (`updatedAt:DESC,name:ASC`).
|
|
16
|
+
* Rules apply in order; the first non-equal field decides. Client-side
|
|
17
|
+
* approximation of the server ordering — good enough to slot folders between
|
|
18
|
+
* files that were sorted server-side by the same rules.
|
|
19
|
+
*/ const buildComparator = (sort)=>{
|
|
20
|
+
const rules = sort.split(',').map((rule)=>rule.trim()).filter(Boolean).map((rule)=>{
|
|
21
|
+
const [field, direction] = rule.split(':');
|
|
22
|
+
return {
|
|
23
|
+
field,
|
|
24
|
+
desc: direction?.toUpperCase() === 'DESC'
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
return (a, b)=>{
|
|
28
|
+
for (const { field, desc } of rules){
|
|
29
|
+
const va = getComparableValue(a, field);
|
|
30
|
+
const vb = getComparableValue(b, field);
|
|
31
|
+
let cmp;
|
|
32
|
+
if (typeof va === 'string' || typeof vb === 'string') {
|
|
33
|
+
cmp = String(va) < String(vb) ? -1 : String(va) > String(vb) ? 1 : 0;
|
|
34
|
+
} else {
|
|
35
|
+
cmp = va - vb;
|
|
36
|
+
}
|
|
37
|
+
if (cmp !== 0) {
|
|
38
|
+
return desc ? -cmp : cmp;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Interleaves the complete folder list into the (possibly partially loaded)
|
|
46
|
+
* asset stream according to the active sort. Ties order folders first.
|
|
47
|
+
*/ const mergeMixedList = ({ folders, assets, sort, hasNextPage })=>{
|
|
48
|
+
const compare = buildComparator(sort);
|
|
49
|
+
const sortedFolders = [
|
|
50
|
+
...folders
|
|
51
|
+
].sort(compare);
|
|
52
|
+
const lastAsset = assets[assets.length - 1];
|
|
53
|
+
const placeableFolders = !hasNextPage || !lastAsset ? hasNextPage ? [] // nothing loaded yet — wait for the first page
|
|
54
|
+
: sortedFolders : sortedFolders.filter((folder)=>compare(folder, lastAsset) <= 0);
|
|
55
|
+
const items = [];
|
|
56
|
+
let folderIndex = 0;
|
|
57
|
+
for (const asset of assets){
|
|
58
|
+
while(folderIndex < placeableFolders.length && compare(placeableFolders[folderIndex], asset) <= 0){
|
|
59
|
+
items.push({
|
|
60
|
+
kind: 'folder',
|
|
61
|
+
folder: placeableFolders[folderIndex]
|
|
62
|
+
});
|
|
63
|
+
folderIndex += 1;
|
|
64
|
+
}
|
|
65
|
+
items.push({
|
|
66
|
+
kind: 'asset',
|
|
67
|
+
asset
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
while(folderIndex < placeableFolders.length){
|
|
71
|
+
items.push({
|
|
72
|
+
kind: 'folder',
|
|
73
|
+
folder: placeableFolders[folderIndex]
|
|
74
|
+
});
|
|
75
|
+
folderIndex += 1;
|
|
76
|
+
}
|
|
77
|
+
return items;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export { buildComparator, mergeMixedList };
|
|
81
|
+
//# sourceMappingURL=mergeMixedList.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeMixedList.mjs","sources":["../../../../../../admin/src/future/pages/Assets/utils/mergeMixedList.ts"],"sourcesContent":["import type { File } from '../../../../../../shared/contracts/files';\nimport type { Folder } from '../../../../../../shared/contracts/folders';\n\nexport type MixedItem = { kind: 'folder'; folder: Folder } | { kind: 'asset'; asset: File };\n\ntype SortableRecord = {\n name?: string | null;\n createdAt?: string | null;\n updatedAt?: string | null;\n size?: number | null;\n};\n\nconst getComparableValue = (record: SortableRecord, field: string): number | string => {\n switch (field) {\n case 'createdAt':\n case 'updatedAt':\n return record[field] ? new Date(record[field]!).getTime() : 0;\n case 'size':\n // Folders have no size — they compare as 0 (start of ascending, end of descending).\n return record.size ?? 0;\n case 'name':\n default:\n return (record.name ?? '').toLowerCase();\n }\n};\n\n/**\n * Builds a comparator from comma-separated sort rules (`updatedAt:DESC,name:ASC`).\n * Rules apply in order; the first non-equal field decides. Client-side\n * approximation of the server ordering — good enough to slot folders between\n * files that were sorted server-side by the same rules.\n */\nexport const buildComparator = (sort: string) => {\n const rules = sort\n .split(',')\n .map((rule) => rule.trim())\n .filter(Boolean)\n .map((rule) => {\n const [field, direction] = rule.split(':');\n return { field, desc: direction?.toUpperCase() === 'DESC' };\n });\n\n return (a: SortableRecord, b: SortableRecord): number => {\n for (const { field, desc } of rules) {\n const va = getComparableValue(a, field);\n const vb = getComparableValue(b, field);\n\n let cmp: number;\n if (typeof va === 'string' || typeof vb === 'string') {\n cmp = String(va) < String(vb) ? -1 : String(va) > String(vb) ? 1 : 0;\n } else {\n cmp = va - vb;\n }\n\n if (cmp !== 0) {\n return desc ? -cmp : cmp;\n }\n }\n\n return 0;\n };\n};\n\ninterface MergeMixedListParams {\n folders: Folder[];\n assets: File[];\n /** Combined sort rules used for the assets query. */\n sort: string;\n /**\n * More asset pages exist. A folder may only be placed once we know no\n * not-yet-loaded asset sorts before it — i.e. it compares ≤ the last loaded\n * asset. Later pages release the remaining folders progressively.\n */\n hasNextPage: boolean;\n}\n\n/**\n * Interleaves the complete folder list into the (possibly partially loaded)\n * asset stream according to the active sort. Ties order folders first.\n */\nexport const mergeMixedList = ({\n folders,\n assets,\n sort,\n hasNextPage,\n}: MergeMixedListParams): MixedItem[] => {\n const compare = buildComparator(sort);\n\n const sortedFolders = [...folders].sort(compare);\n\n const lastAsset = assets[assets.length - 1];\n const placeableFolders =\n !hasNextPage || !lastAsset\n ? hasNextPage\n ? [] // nothing loaded yet — wait for the first page\n : sortedFolders\n : sortedFolders.filter((folder) => compare(folder, lastAsset) <= 0);\n\n const items: MixedItem[] = [];\n let folderIndex = 0;\n\n for (const asset of assets) {\n while (\n folderIndex < placeableFolders.length &&\n compare(placeableFolders[folderIndex], asset) <= 0\n ) {\n items.push({ kind: 'folder', folder: placeableFolders[folderIndex] });\n folderIndex += 1;\n }\n items.push({ kind: 'asset', asset });\n }\n\n while (folderIndex < placeableFolders.length) {\n items.push({ kind: 'folder', folder: placeableFolders[folderIndex] });\n folderIndex += 1;\n }\n\n return items;\n};\n"],"names":["getComparableValue","record","field","Date","getTime","size","name","toLowerCase","buildComparator","sort","rules","split","map","rule","trim","filter","Boolean","direction","desc","toUpperCase","a","b","va","vb","cmp","String","mergeMixedList","folders","assets","hasNextPage","compare","sortedFolders","lastAsset","length","placeableFolders","folder","items","folderIndex","asset","push","kind"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure, view-agnostic selection logic for the future Media Library multi-select.
|
|
5
|
+
*
|
|
6
|
+
* Selection covers both assets and folders. Since their numeric ids live in
|
|
7
|
+
* different tables (and can collide), items are tracked as namespaced keys
|
|
8
|
+
* (`asset:1` / `folder:1`). Views build their rendered order as a key array so
|
|
9
|
+
* range / select-all work across the folder and asset sections seamlessly.
|
|
10
|
+
*/ const assetKey = (id)=>`asset:${id}`;
|
|
11
|
+
const folderKey = (id)=>`folder:${id}`;
|
|
12
|
+
/** Extracts the numeric ids of one kind from a set of item keys. */ const getIdsOfKind = (keys, kind)=>{
|
|
13
|
+
const ids = new Set();
|
|
14
|
+
keys.forEach((key)=>{
|
|
15
|
+
const [keyKind, id] = key.split(':');
|
|
16
|
+
if (keyKind === kind) {
|
|
17
|
+
ids.add(Number(id));
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return ids;
|
|
21
|
+
};
|
|
22
|
+
const createEmptySelection = ()=>({
|
|
23
|
+
selectedKeys: new Set(),
|
|
24
|
+
anchorKey: null
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Additive toggle — adds the key if absent, removes it otherwise, without
|
|
28
|
+
* touching the rest of the selection. Used by the item checkbox and Cmd/Ctrl+click.
|
|
29
|
+
* The toggled key becomes the new anchor.
|
|
30
|
+
*/ const toggleSelection = (state, key)=>{
|
|
31
|
+
const selectedKeys = new Set(state.selectedKeys);
|
|
32
|
+
if (selectedKeys.has(key)) {
|
|
33
|
+
selectedKeys.delete(key);
|
|
34
|
+
} else {
|
|
35
|
+
selectedKeys.add(key);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
selectedKeys,
|
|
39
|
+
anchorKey: key
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Shift+click — selects the contiguous range between the current anchor and the
|
|
44
|
+
* target in render order, replacing the current selection. The anchor stays put.
|
|
45
|
+
*
|
|
46
|
+
* Falls back to a single selection when there is no usable anchor (e.g. first
|
|
47
|
+
* click was a Shift+click, or the anchor is no longer in the list).
|
|
48
|
+
*/ const selectRange = (state, orderedKeys, targetKey)=>{
|
|
49
|
+
const targetIndex = orderedKeys.indexOf(targetKey);
|
|
50
|
+
if (targetIndex === -1) {
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
const anchorIndex = state.anchorKey === null ? -1 : orderedKeys.indexOf(state.anchorKey);
|
|
54
|
+
if (anchorIndex === -1) {
|
|
55
|
+
return {
|
|
56
|
+
selectedKeys: new Set([
|
|
57
|
+
targetKey
|
|
58
|
+
]),
|
|
59
|
+
anchorKey: targetKey
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const start = Math.min(anchorIndex, targetIndex);
|
|
63
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
64
|
+
return {
|
|
65
|
+
selectedKeys: new Set(orderedKeys.slice(start, end + 1)),
|
|
66
|
+
anchorKey: state.anchorKey
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Select-all — selects every key in the provided ordered array. Used by the header
|
|
71
|
+
* checkbox when not everything is already selected.
|
|
72
|
+
*/ const selectAll = (orderedKeys)=>({
|
|
73
|
+
selectedKeys: new Set(orderedKeys),
|
|
74
|
+
anchorKey: orderedKeys.length > 0 ? orderedKeys[orderedKeys.length - 1] : null
|
|
75
|
+
});
|
|
76
|
+
const clearSelection = ()=>createEmptySelection();
|
|
77
|
+
/**
|
|
78
|
+
* Derives the header checkbox state: everything selected, some (indeterminate),
|
|
79
|
+
* or none. Only considers the currently-rendered items (orderedKeys).
|
|
80
|
+
*/ const getSelectAllState = (selectedKeys, orderedKeys)=>{
|
|
81
|
+
if (orderedKeys.length === 0) {
|
|
82
|
+
return {
|
|
83
|
+
allSelected: false,
|
|
84
|
+
isIndeterminate: false
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const selectedCount = orderedKeys.reduce((count, key)=>selectedKeys.has(key) ? count + 1 : count, 0);
|
|
88
|
+
const allSelected = selectedCount === orderedKeys.length;
|
|
89
|
+
return {
|
|
90
|
+
allSelected,
|
|
91
|
+
isIndeterminate: selectedCount > 0 && !allSelected
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
exports.assetKey = assetKey;
|
|
96
|
+
exports.clearSelection = clearSelection;
|
|
97
|
+
exports.createEmptySelection = createEmptySelection;
|
|
98
|
+
exports.folderKey = folderKey;
|
|
99
|
+
exports.getIdsOfKind = getIdsOfKind;
|
|
100
|
+
exports.getSelectAllState = getSelectAllState;
|
|
101
|
+
exports.selectAll = selectAll;
|
|
102
|
+
exports.selectRange = selectRange;
|
|
103
|
+
exports.toggleSelection = toggleSelection;
|
|
104
|
+
//# sourceMappingURL=selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.js","sources":["../../../../../../admin/src/future/pages/Assets/utils/selection.ts"],"sourcesContent":["/**\n * Pure, view-agnostic selection logic for the future Media Library multi-select.\n *\n * Selection covers both assets and folders. Since their numeric ids live in\n * different tables (and can collide), items are tracked as namespaced keys\n * (`asset:1` / `folder:1`). Views build their rendered order as a key array so\n * range / select-all work across the folder and asset sections seamlessly.\n */\n\nexport type SelectableKind = 'asset' | 'folder';\n\nexport type ItemKey = `${SelectableKind}:${number}`;\n\nexport const assetKey = (id: number): ItemKey => `asset:${id}`;\n\nexport const folderKey = (id: number): ItemKey => `folder:${id}`;\n\n/** Extracts the numeric ids of one kind from a set of item keys. */\nexport const getIdsOfKind = (keys: Set<ItemKey>, kind: SelectableKind): Set<number> => {\n const ids = new Set<number>();\n\n keys.forEach((key) => {\n const [keyKind, id] = key.split(':');\n if (keyKind === kind) {\n ids.add(Number(id));\n }\n });\n\n return ids;\n};\n\nexport interface SelectionState {\n /** Currently selected item keys (assets and folders). */\n selectedKeys: Set<ItemKey>;\n /** Anchor used as the start of a Shift+click range. */\n anchorKey: ItemKey | null;\n}\n\nexport const createEmptySelection = (): SelectionState => ({\n selectedKeys: new Set<ItemKey>(),\n anchorKey: null,\n});\n\n/**\n * Additive toggle — adds the key if absent, removes it otherwise, without\n * touching the rest of the selection. Used by the item checkbox and Cmd/Ctrl+click.\n * The toggled key becomes the new anchor.\n */\nexport const toggleSelection = (state: SelectionState, key: ItemKey): SelectionState => {\n const selectedKeys = new Set(state.selectedKeys);\n\n if (selectedKeys.has(key)) {\n selectedKeys.delete(key);\n } else {\n selectedKeys.add(key);\n }\n\n return { selectedKeys, anchorKey: key };\n};\n\n/**\n * Shift+click — selects the contiguous range between the current anchor and the\n * target in render order, replacing the current selection. The anchor stays put.\n *\n * Falls back to a single selection when there is no usable anchor (e.g. first\n * click was a Shift+click, or the anchor is no longer in the list).\n */\nexport const selectRange = (\n state: SelectionState,\n orderedKeys: ItemKey[],\n targetKey: ItemKey\n): SelectionState => {\n const targetIndex = orderedKeys.indexOf(targetKey);\n\n if (targetIndex === -1) {\n return state;\n }\n\n const anchorIndex = state.anchorKey === null ? -1 : orderedKeys.indexOf(state.anchorKey);\n\n if (anchorIndex === -1) {\n return { selectedKeys: new Set<ItemKey>([targetKey]), anchorKey: targetKey };\n }\n\n const start = Math.min(anchorIndex, targetIndex);\n const end = Math.max(anchorIndex, targetIndex);\n\n return {\n selectedKeys: new Set(orderedKeys.slice(start, end + 1)),\n anchorKey: state.anchorKey,\n };\n};\n\n/**\n * Select-all — selects every key in the provided ordered array. Used by the header\n * checkbox when not everything is already selected.\n */\nexport const selectAll = (orderedKeys: ItemKey[]): SelectionState => ({\n selectedKeys: new Set(orderedKeys),\n anchorKey: orderedKeys.length > 0 ? orderedKeys[orderedKeys.length - 1] : null,\n});\n\nexport const clearSelection = (): SelectionState => createEmptySelection();\n\nexport interface SelectAllState {\n allSelected: boolean;\n isIndeterminate: boolean;\n}\n\n/**\n * Derives the header checkbox state: everything selected, some (indeterminate),\n * or none. Only considers the currently-rendered items (orderedKeys).\n */\nexport const getSelectAllState = (\n selectedKeys: Set<ItemKey>,\n orderedKeys: ItemKey[]\n): SelectAllState => {\n if (orderedKeys.length === 0) {\n return { allSelected: false, isIndeterminate: false };\n }\n\n const selectedCount = orderedKeys.reduce(\n (count, key) => (selectedKeys.has(key) ? count + 1 : count),\n 0\n );\n const allSelected = selectedCount === orderedKeys.length;\n\n return {\n allSelected,\n isIndeterminate: selectedCount > 0 && !allSelected,\n };\n};\n"],"names":["assetKey","id","folderKey","getIdsOfKind","keys","kind","ids","Set","forEach","key","keyKind","split","add","Number","createEmptySelection","selectedKeys","anchorKey","toggleSelection","state","has","delete","selectRange","orderedKeys","targetKey","targetIndex","indexOf","anchorIndex","start","Math","min","end","max","slice","selectAll","length","clearSelection","getSelectAllState","allSelected","isIndeterminate","selectedCount","reduce","count"],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, view-agnostic selection logic for the future Media Library multi-select.
|
|
3
|
+
*
|
|
4
|
+
* Selection covers both assets and folders. Since their numeric ids live in
|
|
5
|
+
* different tables (and can collide), items are tracked as namespaced keys
|
|
6
|
+
* (`asset:1` / `folder:1`). Views build their rendered order as a key array so
|
|
7
|
+
* range / select-all work across the folder and asset sections seamlessly.
|
|
8
|
+
*/ const assetKey = (id)=>`asset:${id}`;
|
|
9
|
+
const folderKey = (id)=>`folder:${id}`;
|
|
10
|
+
/** Extracts the numeric ids of one kind from a set of item keys. */ const getIdsOfKind = (keys, kind)=>{
|
|
11
|
+
const ids = new Set();
|
|
12
|
+
keys.forEach((key)=>{
|
|
13
|
+
const [keyKind, id] = key.split(':');
|
|
14
|
+
if (keyKind === kind) {
|
|
15
|
+
ids.add(Number(id));
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
return ids;
|
|
19
|
+
};
|
|
20
|
+
const createEmptySelection = ()=>({
|
|
21
|
+
selectedKeys: new Set(),
|
|
22
|
+
anchorKey: null
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Additive toggle — adds the key if absent, removes it otherwise, without
|
|
26
|
+
* touching the rest of the selection. Used by the item checkbox and Cmd/Ctrl+click.
|
|
27
|
+
* The toggled key becomes the new anchor.
|
|
28
|
+
*/ const toggleSelection = (state, key)=>{
|
|
29
|
+
const selectedKeys = new Set(state.selectedKeys);
|
|
30
|
+
if (selectedKeys.has(key)) {
|
|
31
|
+
selectedKeys.delete(key);
|
|
32
|
+
} else {
|
|
33
|
+
selectedKeys.add(key);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
selectedKeys,
|
|
37
|
+
anchorKey: key
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Shift+click — selects the contiguous range between the current anchor and the
|
|
42
|
+
* target in render order, replacing the current selection. The anchor stays put.
|
|
43
|
+
*
|
|
44
|
+
* Falls back to a single selection when there is no usable anchor (e.g. first
|
|
45
|
+
* click was a Shift+click, or the anchor is no longer in the list).
|
|
46
|
+
*/ const selectRange = (state, orderedKeys, targetKey)=>{
|
|
47
|
+
const targetIndex = orderedKeys.indexOf(targetKey);
|
|
48
|
+
if (targetIndex === -1) {
|
|
49
|
+
return state;
|
|
50
|
+
}
|
|
51
|
+
const anchorIndex = state.anchorKey === null ? -1 : orderedKeys.indexOf(state.anchorKey);
|
|
52
|
+
if (anchorIndex === -1) {
|
|
53
|
+
return {
|
|
54
|
+
selectedKeys: new Set([
|
|
55
|
+
targetKey
|
|
56
|
+
]),
|
|
57
|
+
anchorKey: targetKey
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const start = Math.min(anchorIndex, targetIndex);
|
|
61
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
62
|
+
return {
|
|
63
|
+
selectedKeys: new Set(orderedKeys.slice(start, end + 1)),
|
|
64
|
+
anchorKey: state.anchorKey
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Select-all — selects every key in the provided ordered array. Used by the header
|
|
69
|
+
* checkbox when not everything is already selected.
|
|
70
|
+
*/ const selectAll = (orderedKeys)=>({
|
|
71
|
+
selectedKeys: new Set(orderedKeys),
|
|
72
|
+
anchorKey: orderedKeys.length > 0 ? orderedKeys[orderedKeys.length - 1] : null
|
|
73
|
+
});
|
|
74
|
+
const clearSelection = ()=>createEmptySelection();
|
|
75
|
+
/**
|
|
76
|
+
* Derives the header checkbox state: everything selected, some (indeterminate),
|
|
77
|
+
* or none. Only considers the currently-rendered items (orderedKeys).
|
|
78
|
+
*/ const getSelectAllState = (selectedKeys, orderedKeys)=>{
|
|
79
|
+
if (orderedKeys.length === 0) {
|
|
80
|
+
return {
|
|
81
|
+
allSelected: false,
|
|
82
|
+
isIndeterminate: false
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const selectedCount = orderedKeys.reduce((count, key)=>selectedKeys.has(key) ? count + 1 : count, 0);
|
|
86
|
+
const allSelected = selectedCount === orderedKeys.length;
|
|
87
|
+
return {
|
|
88
|
+
allSelected,
|
|
89
|
+
isIndeterminate: selectedCount > 0 && !allSelected
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { assetKey, clearSelection, createEmptySelection, folderKey, getIdsOfKind, getSelectAllState, selectAll, selectRange, toggleSelection };
|
|
94
|
+
//# sourceMappingURL=selection.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.mjs","sources":["../../../../../../admin/src/future/pages/Assets/utils/selection.ts"],"sourcesContent":["/**\n * Pure, view-agnostic selection logic for the future Media Library multi-select.\n *\n * Selection covers both assets and folders. Since their numeric ids live in\n * different tables (and can collide), items are tracked as namespaced keys\n * (`asset:1` / `folder:1`). Views build their rendered order as a key array so\n * range / select-all work across the folder and asset sections seamlessly.\n */\n\nexport type SelectableKind = 'asset' | 'folder';\n\nexport type ItemKey = `${SelectableKind}:${number}`;\n\nexport const assetKey = (id: number): ItemKey => `asset:${id}`;\n\nexport const folderKey = (id: number): ItemKey => `folder:${id}`;\n\n/** Extracts the numeric ids of one kind from a set of item keys. */\nexport const getIdsOfKind = (keys: Set<ItemKey>, kind: SelectableKind): Set<number> => {\n const ids = new Set<number>();\n\n keys.forEach((key) => {\n const [keyKind, id] = key.split(':');\n if (keyKind === kind) {\n ids.add(Number(id));\n }\n });\n\n return ids;\n};\n\nexport interface SelectionState {\n /** Currently selected item keys (assets and folders). */\n selectedKeys: Set<ItemKey>;\n /** Anchor used as the start of a Shift+click range. */\n anchorKey: ItemKey | null;\n}\n\nexport const createEmptySelection = (): SelectionState => ({\n selectedKeys: new Set<ItemKey>(),\n anchorKey: null,\n});\n\n/**\n * Additive toggle — adds the key if absent, removes it otherwise, without\n * touching the rest of the selection. Used by the item checkbox and Cmd/Ctrl+click.\n * The toggled key becomes the new anchor.\n */\nexport const toggleSelection = (state: SelectionState, key: ItemKey): SelectionState => {\n const selectedKeys = new Set(state.selectedKeys);\n\n if (selectedKeys.has(key)) {\n selectedKeys.delete(key);\n } else {\n selectedKeys.add(key);\n }\n\n return { selectedKeys, anchorKey: key };\n};\n\n/**\n * Shift+click — selects the contiguous range between the current anchor and the\n * target in render order, replacing the current selection. The anchor stays put.\n *\n * Falls back to a single selection when there is no usable anchor (e.g. first\n * click was a Shift+click, or the anchor is no longer in the list).\n */\nexport const selectRange = (\n state: SelectionState,\n orderedKeys: ItemKey[],\n targetKey: ItemKey\n): SelectionState => {\n const targetIndex = orderedKeys.indexOf(targetKey);\n\n if (targetIndex === -1) {\n return state;\n }\n\n const anchorIndex = state.anchorKey === null ? -1 : orderedKeys.indexOf(state.anchorKey);\n\n if (anchorIndex === -1) {\n return { selectedKeys: new Set<ItemKey>([targetKey]), anchorKey: targetKey };\n }\n\n const start = Math.min(anchorIndex, targetIndex);\n const end = Math.max(anchorIndex, targetIndex);\n\n return {\n selectedKeys: new Set(orderedKeys.slice(start, end + 1)),\n anchorKey: state.anchorKey,\n };\n};\n\n/**\n * Select-all — selects every key in the provided ordered array. Used by the header\n * checkbox when not everything is already selected.\n */\nexport const selectAll = (orderedKeys: ItemKey[]): SelectionState => ({\n selectedKeys: new Set(orderedKeys),\n anchorKey: orderedKeys.length > 0 ? orderedKeys[orderedKeys.length - 1] : null,\n});\n\nexport const clearSelection = (): SelectionState => createEmptySelection();\n\nexport interface SelectAllState {\n allSelected: boolean;\n isIndeterminate: boolean;\n}\n\n/**\n * Derives the header checkbox state: everything selected, some (indeterminate),\n * or none. Only considers the currently-rendered items (orderedKeys).\n */\nexport const getSelectAllState = (\n selectedKeys: Set<ItemKey>,\n orderedKeys: ItemKey[]\n): SelectAllState => {\n if (orderedKeys.length === 0) {\n return { allSelected: false, isIndeterminate: false };\n }\n\n const selectedCount = orderedKeys.reduce(\n (count, key) => (selectedKeys.has(key) ? count + 1 : count),\n 0\n );\n const allSelected = selectedCount === orderedKeys.length;\n\n return {\n allSelected,\n isIndeterminate: selectedCount > 0 && !allSelected,\n };\n};\n"],"names":["assetKey","id","folderKey","getIdsOfKind","keys","kind","ids","Set","forEach","key","keyKind","split","add","Number","createEmptySelection","selectedKeys","anchorKey","toggleSelection","state","has","delete","selectRange","orderedKeys","targetKey","targetIndex","indexOf","anchorIndex","start","Math","min","end","max","slice","selectAll","length","clearSelection","getSelectAllState","allSelected","isIndeterminate","selectedCount","reduce","count"],"mappings":"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"}
|
|
@@ -153,11 +153,43 @@ const assetsApi = api.uploadApi.injectEndpoints({
|
|
|
153
153
|
id: 'LIST'
|
|
154
154
|
}
|
|
155
155
|
]
|
|
156
|
+
}),
|
|
157
|
+
/**
|
|
158
|
+
* Permanently delete several assets and/or folders in one request. Same
|
|
159
|
+
* endpoint as the legacy bulk remove (`POST /upload/actions/bulk-delete`,
|
|
160
|
+
* handled by `admin-folder-file.deleteMany`) so server behaviour is
|
|
161
|
+
* unchanged — folder deletion cascades to everything inside.
|
|
162
|
+
*/ bulkDeleteItems: builder.mutation({
|
|
163
|
+
query: ({ fileIds, folderIds })=>({
|
|
164
|
+
url: '/upload/actions/bulk-delete',
|
|
165
|
+
method: 'POST',
|
|
166
|
+
data: {
|
|
167
|
+
fileIds,
|
|
168
|
+
folderIds
|
|
169
|
+
}
|
|
170
|
+
}),
|
|
171
|
+
invalidatesTags: [
|
|
172
|
+
{
|
|
173
|
+
type: 'Asset',
|
|
174
|
+
id: 'LIST'
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: 'Folder',
|
|
178
|
+
id: 'LIST'
|
|
179
|
+
},
|
|
180
|
+
// The sidebar FolderTree reads /upload/folder-structure, which carries
|
|
181
|
+
// its own tag — without it, deleted folders linger in the tree.
|
|
182
|
+
{
|
|
183
|
+
type: 'Folder',
|
|
184
|
+
id: 'STRUCTURE'
|
|
185
|
+
}
|
|
186
|
+
]
|
|
156
187
|
})
|
|
157
188
|
})
|
|
158
189
|
});
|
|
159
|
-
const { useGetAssetsQuery, useGetAssetQuery, useUpdateAssetMutation, useReplaceAssetMutation, useDeleteAssetMutation } = assetsApi;
|
|
190
|
+
const { useGetAssetsQuery, useGetAssetQuery, useUpdateAssetMutation, useReplaceAssetMutation, useDeleteAssetMutation, useBulkDeleteItemsMutation } = assetsApi;
|
|
160
191
|
|
|
192
|
+
exports.useBulkDeleteItemsMutation = useBulkDeleteItemsMutation;
|
|
161
193
|
exports.useDeleteAssetMutation = useDeleteAssetMutation;
|
|
162
194
|
exports.useGetAssetQuery = useGetAssetQuery;
|
|
163
195
|
exports.useGetAssetsQuery = useGetAssetsQuery;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.js","sources":["../../../../admin/src/future/services/assets.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type {\n GetFiles,\n File,\n Pagination,\n UploadFileInfo,\n AssetWithPopulatedCreatedBy,\n} from '../../../../shared/contracts/files';\n\ninterface GetAssetsParams {\n page?: number;\n pageSize?: number;\n folder?: number | null;\n sort?: string;\n}\n\ninterface GetAssetsResponse {\n results: File[];\n pagination: Pagination;\n}\n\ninterface UpdateAssetArgs {\n id: number;\n fileInfo: Partial<UploadFileInfo>;\n}\n\ninterface ReplaceAssetArgs {\n id: number;\n // `File` is shadowed in this module by the asset-file contract type; the\n // global browser File is what we need for FormData.\n file: globalThis.File;\n fileInfo?: Partial<UploadFileInfo>;\n}\n\nconst assetsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getAssets: builder.query<GetAssetsResponse, GetAssetsParams | void>({\n query: (params = {}) => {\n const { folder, ...rest } = params as GetAssetsParams;\n\n const queryParams: Record<string, unknown> = { ...rest };\n\n if (folder != null) {\n queryParams['filters'] = {\n $and: [{ folder: { id: folder } }],\n };\n } else {\n queryParams['filters'] = {\n $and: [{ folder: { id: { $null: true } } }],\n };\n }\n\n return {\n url: '/upload/files',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFiles.Response['data']) => response,\n providesTags: (result) =>\n result\n ? [\n ...result.results.map(({ id }) => ({ type: 'Asset' as const, id })),\n { type: 'Asset', id: 'LIST' },\n ]\n : [{ type: 'Asset', id: 'LIST' }],\n }),\n getAsset: builder.query<AssetWithPopulatedCreatedBy, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'GET',\n }),\n providesTags: (_result, _error, id) => [{ type: 'Asset' as const, id }],\n }),\n /**\n * Update the editable metadata of an existing asset.\n * Hits the legacy `POST /upload?id=<id>` endpoint which dispatches to\n * `admin-upload.updateFileInfo`.\n */\n updateAsset: builder.mutation<AssetWithPopulatedCreatedBy, UpdateAssetArgs>({\n query: ({ id, fileInfo }) => {\n const formData = new FormData();\n formData.append('fileInfo', JSON.stringify(fileInfo));\n\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Replace the binary content of an existing asset.\n * Hits `POST /upload?id=<id>` with a multipart body — the controller\n * dispatches to `admin-upload.replaceFile` when a `files` part is present.\n * Uses the standard axios baseQuery (no streaming) since we only ever\n * replace one file at a time and don't need per-byte progress here.\n */\n replaceAsset: builder.mutation<AssetWithPopulatedCreatedBy, ReplaceAssetArgs>({\n query: ({ id, file, fileInfo }) => {\n const formData = new FormData();\n formData.append('files', file);\n if (fileInfo) {\n formData.append('fileInfo', JSON.stringify(fileInfo));\n }\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Permanently delete an asset by id. Hits the same endpoint as the legacy\n * `useRemoveAsset` hook so server behaviour is unchanged.\n */\n deleteAsset: builder.mutation<unknown, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'DELETE',\n }),\n invalidatesTags: (_result, _error, id) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n }),\n});\n\nexport const {\n useGetAssetsQuery,\n useGetAssetQuery,\n useUpdateAssetMutation,\n useReplaceAssetMutation,\n useDeleteAssetMutation,\n} = assetsApi;\n"],"names":["assetsApi","uploadApi","injectEndpoints","endpoints","builder","getAssets","query","params","folder","rest","queryParams","$and","id","$null","url","method","config","transformResponse","response","providesTags","result","results","map","type","getAsset","_result","_error","updateAsset","mutation","fileInfo","formData","FormData","append","JSON","stringify","data","invalidatesTags","replaceAsset","file","deleteAsset","useGetAssetsQuery","useGetAssetQuery","useUpdateAssetMutation","useReplaceAssetMutation","useDeleteAssetMutation"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"assets.js","sources":["../../../../admin/src/future/services/assets.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type {\n GetFiles,\n File,\n Pagination,\n UploadFileInfo,\n AssetWithPopulatedCreatedBy,\n} from '../../../../shared/contracts/files';\n\ninterface GetAssetsParams {\n page?: number;\n pageSize?: number;\n folder?: number | null;\n sort?: string;\n}\n\ninterface GetAssetsResponse {\n results: File[];\n pagination: Pagination;\n}\n\ninterface UpdateAssetArgs {\n id: number;\n fileInfo: Partial<UploadFileInfo>;\n}\n\ninterface ReplaceAssetArgs {\n id: number;\n // `File` is shadowed in this module by the asset-file contract type; the\n // global browser File is what we need for FormData.\n file: globalThis.File;\n fileInfo?: Partial<UploadFileInfo>;\n}\n\nconst assetsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getAssets: builder.query<GetAssetsResponse, GetAssetsParams | void>({\n query: (params = {}) => {\n const { folder, ...rest } = params as GetAssetsParams;\n\n const queryParams: Record<string, unknown> = { ...rest };\n\n if (folder != null) {\n queryParams['filters'] = {\n $and: [{ folder: { id: folder } }],\n };\n } else {\n queryParams['filters'] = {\n $and: [{ folder: { id: { $null: true } } }],\n };\n }\n\n return {\n url: '/upload/files',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFiles.Response['data']) => response,\n providesTags: (result) =>\n result\n ? [\n ...result.results.map(({ id }) => ({ type: 'Asset' as const, id })),\n { type: 'Asset', id: 'LIST' },\n ]\n : [{ type: 'Asset', id: 'LIST' }],\n }),\n getAsset: builder.query<AssetWithPopulatedCreatedBy, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'GET',\n }),\n providesTags: (_result, _error, id) => [{ type: 'Asset' as const, id }],\n }),\n /**\n * Update the editable metadata of an existing asset.\n * Hits the legacy `POST /upload?id=<id>` endpoint which dispatches to\n * `admin-upload.updateFileInfo`.\n */\n updateAsset: builder.mutation<AssetWithPopulatedCreatedBy, UpdateAssetArgs>({\n query: ({ id, fileInfo }) => {\n const formData = new FormData();\n formData.append('fileInfo', JSON.stringify(fileInfo));\n\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Replace the binary content of an existing asset.\n * Hits `POST /upload?id=<id>` with a multipart body — the controller\n * dispatches to `admin-upload.replaceFile` when a `files` part is present.\n * Uses the standard axios baseQuery (no streaming) since we only ever\n * replace one file at a time and don't need per-byte progress here.\n */\n replaceAsset: builder.mutation<AssetWithPopulatedCreatedBy, ReplaceAssetArgs>({\n query: ({ id, file, fileInfo }) => {\n const formData = new FormData();\n formData.append('files', file);\n if (fileInfo) {\n formData.append('fileInfo', JSON.stringify(fileInfo));\n }\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Permanently delete an asset by id. Hits the same endpoint as the legacy\n * `useRemoveAsset` hook so server behaviour is unchanged.\n */\n deleteAsset: builder.mutation<unknown, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'DELETE',\n }),\n invalidatesTags: (_result, _error, id) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Permanently delete several assets and/or folders in one request. Same\n * endpoint as the legacy bulk remove (`POST /upload/actions/bulk-delete`,\n * handled by `admin-folder-file.deleteMany`) so server behaviour is\n * unchanged — folder deletion cascades to everything inside.\n */\n bulkDeleteItems: builder.mutation<unknown, { fileIds: number[]; folderIds: number[] }>({\n query: ({ fileIds, folderIds }) => ({\n url: '/upload/actions/bulk-delete',\n method: 'POST',\n data: { fileIds, folderIds },\n }),\n invalidatesTags: [\n { type: 'Asset' as const, id: 'LIST' },\n { type: 'Folder' as const, id: 'LIST' },\n // The sidebar FolderTree reads /upload/folder-structure, which carries\n // its own tag — without it, deleted folders linger in the tree.\n { type: 'Folder' as const, id: 'STRUCTURE' },\n ],\n }),\n }),\n});\n\nexport const {\n useGetAssetsQuery,\n useGetAssetQuery,\n useUpdateAssetMutation,\n useReplaceAssetMutation,\n useDeleteAssetMutation,\n useBulkDeleteItemsMutation,\n} = assetsApi;\n"],"names":["assetsApi","uploadApi","injectEndpoints","endpoints","builder","getAssets","query","params","folder","rest","queryParams","$and","id","$null","url","method","config","transformResponse","response","providesTags","result","results","map","type","getAsset","_result","_error","updateAsset","mutation","fileInfo","formData","FormData","append","JSON","stringify","data","invalidatesTags","replaceAsset","file","deleteAsset","bulkDeleteItems","fileIds","folderIds","useGetAssetsQuery","useGetAssetQuery","useUpdateAssetMutation","useReplaceAssetMutation","useDeleteAssetMutation","useBulkDeleteItemsMutation"],"mappings":"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"}
|