@strapi/upload 5.56.0 → 5.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/hooks/useTracking.js +2 -8
- package/dist/admin/hooks/useTracking.js.map +1 -1
- package/dist/admin/hooks/useTracking.mjs +3 -8
- package/dist/admin/hooks/useTracking.mjs.map +1 -1
- package/dist/admin/legacy/hooks/useTracking.js +3 -1
- package/dist/admin/legacy/hooks/useTracking.js.map +1 -1
- package/dist/admin/legacy/hooks/useTracking.mjs +3 -1
- package/dist/admin/legacy/hooks/useTracking.mjs.map +1 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js +2 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js.map +1 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs +2 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs.map +1 -1
- package/dist/admin/legacy/pages/App/MediaLibrary.js +2 -1
- package/dist/admin/legacy/pages/App/MediaLibrary.js.map +1 -1
- package/dist/admin/legacy/pages/App/MediaLibrary.mjs +2 -1
- package/dist/admin/legacy/pages/App/MediaLibrary.mjs.map +1 -1
- package/dist/admin/pages/Assets/AssetsPage.js +24 -12
- package/dist/admin/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/pages/Assets/AssetsPage.mjs +25 -13
- package/dist/admin/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetActions.js +326 -0
- package/dist/admin/pages/Assets/components/AssetActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/AssetActions.mjs +324 -0
- package/dist/admin/pages/Assets/components/AssetActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/AssetActionsMenu.js +28 -309
- package/dist/admin/pages/Assets/components/AssetActionsMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs +31 -312
- package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetContextMenu.js +79 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.mjs +77 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js +87 -5
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +89 -7
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +3 -3
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +3 -3
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js +62 -5
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs +60 -6
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsGrid.js +27 -4
- package/dist/admin/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsGrid.mjs +27 -4
- package/dist/admin/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsTable.js +27 -4
- package/dist/admin/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsTable.mjs +27 -4
- package/dist/admin/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js +77 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs +75 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/FolderActions.js +139 -0
- package/dist/admin/pages/Assets/components/FolderActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderActions.mjs +137 -0
- package/dist/admin/pages/Assets/components/FolderActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/FolderActionsMenu.js +25 -119
- package/dist/admin/pages/Assets/components/FolderActionsMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs +27 -121
- package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/FolderContextMenu.js +67 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.mjs +65 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js +139 -45
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs +140 -47
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js +115 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs +113 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.js +125 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.mjs +122 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.js +88 -63
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs +88 -63
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/SelectionActions.js +111 -0
- package/dist/admin/pages/Assets/components/SelectionActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionActions.mjs +109 -0
- package/dist/admin/pages/Assets/components/SelectionActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.js +82 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs +80 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.js +10 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.js.map +1 -1
- package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs +10 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.js +8 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs +8 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs.map +1 -1
- package/dist/admin/src/hooks/useTracking.d.ts +0 -7
- package/dist/admin/src/pages/Assets/components/AssetActions.d.ts +70 -0
- package/dist/admin/src/pages/Assets/components/AssetActionsMenu.d.ts +5 -14
- package/dist/admin/src/pages/Assets/components/AssetContextMenu.d.ts +27 -0
- package/dist/admin/src/pages/Assets/components/AssetDetails/useCropImg.d.ts +16 -0
- package/dist/admin/src/pages/Assets/components/CursorAnchoredMenu.d.ts +32 -0
- package/dist/admin/src/pages/Assets/components/FolderActions.d.ts +48 -0
- package/dist/admin/src/pages/Assets/components/FolderActionsMenu.d.ts +4 -8
- package/dist/admin/src/pages/Assets/components/FolderContextMenu.d.ts +21 -0
- package/dist/admin/src/pages/Assets/components/FolderTree/FolderTree.d.ts +5 -1
- package/dist/admin/src/pages/Assets/components/FolderTree/FolderTreeRowMenu.d.ts +25 -0
- package/dist/admin/src/pages/Assets/components/ItemContextMenu.d.ts +53 -0
- package/dist/admin/src/pages/Assets/components/MainAreaContextMenu.d.ts +15 -1
- package/dist/admin/src/pages/Assets/components/SelectionActions.d.ts +40 -0
- package/dist/admin/src/pages/Assets/components/SelectionContextMenu.d.ts +25 -0
- package/dist/admin/src/pages/Assets/hooks/useAssetSelection.d.ts +5 -0
- package/dist/admin/src/utils/mediaLibraryVersion.d.ts +18 -0
- package/dist/admin/translations/en.json.js +8 -3
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +8 -3
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/utils/mediaLibraryVersion.js +24 -0
- package/dist/admin/utils/mediaLibraryVersion.js.map +1 -0
- package/dist/admin/utils/mediaLibraryVersion.mjs +20 -0
- package/dist/admin/utils/mediaLibraryVersion.mjs.map +1 -0
- package/dist/server/controllers/admin-file.js +2 -2
- package/dist/server/controllers/admin-file.js.map +1 -1
- package/dist/server/controllers/admin-file.mjs +2 -2
- package/dist/server/controllers/admin-file.mjs.map +1 -1
- package/dist/server/controllers/admin-folder.js +5 -5
- package/dist/server/controllers/admin-folder.js.map +1 -1
- package/dist/server/controllers/admin-folder.mjs +5 -5
- package/dist/server/controllers/admin-folder.mjs.map +1 -1
- package/dist/server/controllers/content-api.js +30 -7
- package/dist/server/controllers/content-api.js.map +1 -1
- package/dist/server/controllers/content-api.mjs +30 -7
- package/dist/server/controllers/content-api.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/folder-file.js +4 -4
- package/dist/server/controllers/validation/admin/folder-file.js.map +1 -1
- package/dist/server/controllers/validation/admin/folder-file.mjs +2 -2
- package/dist/server/controllers/validation/admin/folder-file.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/folder.js +3 -4
- package/dist/server/controllers/validation/admin/folder.js.map +1 -1
- package/dist/server/controllers/validation/admin/folder.mjs +3 -4
- package/dist/server/controllers/validation/admin/folder.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/upload.js +1 -2
- package/dist/server/controllers/validation/admin/upload.js.map +1 -1
- package/dist/server/controllers/validation/admin/upload.mjs +1 -2
- package/dist/server/controllers/validation/admin/upload.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/utils.js +1 -2
- package/dist/server/controllers/validation/admin/utils.js.map +1 -1
- package/dist/server/controllers/validation/admin/utils.mjs +1 -2
- package/dist/server/controllers/validation/admin/utils.mjs.map +1 -1
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.js +2 -2
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.js.map +1 -1
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs +1 -1
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs.map +1 -1
- package/dist/server/routes/content-api.js +3 -3
- package/dist/server/routes/content-api.js.map +1 -1
- package/dist/server/routes/content-api.mjs +3 -3
- package/dist/server/routes/content-api.mjs.map +1 -1
- package/dist/server/routes/validation/upload.js +8 -0
- package/dist/server/routes/validation/upload.js.map +1 -1
- package/dist/server/routes/validation/upload.mjs +8 -0
- package/dist/server/routes/validation/upload.mjs.map +1 -1
- package/dist/server/services/api-upload-folder.js +3 -3
- package/dist/server/services/api-upload-folder.js.map +1 -1
- package/dist/server/services/api-upload-folder.mjs +3 -3
- package/dist/server/services/api-upload-folder.mjs.map +1 -1
- package/dist/server/services/extensions/utils.js +19 -15
- package/dist/server/services/extensions/utils.js.map +1 -1
- package/dist/server/services/extensions/utils.mjs +19 -15
- package/dist/server/services/extensions/utils.mjs.map +1 -1
- package/dist/server/services/file.js +2 -2
- package/dist/server/services/file.js.map +1 -1
- package/dist/server/services/file.mjs +1 -1
- package/dist/server/services/file.mjs.map +1 -1
- package/dist/server/services/folder.js +5 -5
- package/dist/server/services/folder.js.map +1 -1
- package/dist/server/services/folder.mjs +5 -5
- package/dist/server/services/folder.mjs.map +1 -1
- package/dist/server/services/provider.js +3 -4
- package/dist/server/services/provider.js.map +1 -1
- package/dist/server/services/provider.mjs +3 -4
- package/dist/server/services/provider.mjs.map +1 -1
- package/dist/server/services/upload.js +5 -6
- package/dist/server/services/upload.js.map +1 -1
- package/dist/server/services/upload.mjs +4 -5
- package/dist/server/services/upload.mjs.map +1 -1
- package/dist/server/services/weekly-metrics.js +2 -2
- package/dist/server/services/weekly-metrics.js.map +1 -1
- package/dist/server/services/weekly-metrics.mjs +2 -2
- package/dist/server/services/weekly-metrics.mjs.map +1 -1
- package/dist/server/src/controllers/admin-folder.d.ts.map +1 -1
- package/dist/server/src/controllers/content-api.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/folder.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/upload.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/utils.d.ts.map +1 -1
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/mcp/ambient-instance.d.ts.map +1 -1
- package/dist/server/src/routes/validation/upload.d.ts +4 -0
- package/dist/server/src/routes/validation/upload.d.ts.map +1 -1
- package/dist/server/src/services/extensions/utils.d.ts +33 -1
- package/dist/server/src/services/extensions/utils.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/provider.d.ts.map +1 -1
- package/dist/server/src/services/upload.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MainAreaContextMenu.mjs","sources":["../../../../../admin/src/pages/Assets/components/MainAreaContextMenu.tsx"],"sourcesContent":["import { useCallback, useEffect, useState, type CSSProperties } from 'react';\n\nimport { Menu } from '@strapi/design-system';\nimport { Files, Folder as FolderIcon, Link } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport { ActionsMenuContent } from './ActionsMenuContent';\n\n/**\n * The admin layout marks the element that scrolls the main column. Listening on\n * it rather than a wrapper of our own is what makes the whole column part of the\n * hit area: it carries a responsive `padding-bottom` (16 / 24 / 40px) that no\n * descendant can cover, and that strip is exactly where a right-click \"below the\n * list\" lands.\n */\nconst SCROLL_ROOT_SELECTOR = '[data-strapi-main-content]';\n\n/**\n * Subtrees that keep the browser's own context menu instead of opening the\n * create menu.\n *\n * `data-native-context-menu` is the explicit opt-out carried by the list items\n * themselves (asset cards, folder cards, table rows) and by the bulk actions\n * bar — right-clicking one of those must behave exactly as it does today. The\n * rest are the generic cases where the native menu is the useful one: links,\n * buttons and anything the user can type into or select text in.\n *\n * `thead` covers the table's header row, which is chrome rather than empty\n * background even though it carries no `data-native-context-menu` item.\n */\nconst NATIVE_CONTEXT_MENU_SELECTOR = [\n '[data-native-context-menu]',\n 'thead',\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n '[contenteditable=\"true\"]',\n].join(', ');\n\n/**\n * The menu is anchored to a zero-sized element parked at the cursor, because\n * the design system's `Menu` is a dropdown: it positions its content against a\n * trigger, and there is no real trigger for a right-click on empty space.\n *\n * Inline styles rather than a `styled` wrapper — the trigger renders a design\n * system `Button`, and losing the specificity race with its own class would put\n * a stray button on the page.\n */\nconst CURSOR_ANCHOR_STYLE: CSSProperties = {\n position: 'fixed',\n width: 0,\n height: 0,\n minWidth: 0,\n minHeight: 0,\n padding: 0,\n border: 0,\n opacity: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n};\n\nconst LOCATOR_STYLE: CSSProperties = { height: 0 };\n\ninterface CursorPosition {\n x: number;\n y: number;\n}\n\ninterface MainAreaContextMenuProps {\n /** Opens the \"new folder\" dialog in the folder currently on screen. */\n onCreateFolder: () => void;\n /** Opens the native file picker — same entry point as the header \"New\" menu. */\n onImportFiles: () => void;\n /** Opens the \"import from URL\" dialog. */\n onImportFromUrl: () => void;\n /**\n * RBAC gate — without `assets.create` there is nothing to offer, so the\n * right-click falls through to the browser. Required (not defaulted) so a\n * permission gate can't silently regress to permissive.\n */\n disabled: boolean;\n}\n\n/**\n * Drive/Dropbox-style right-click on the empty parts of the assets area: the\n * same creation actions the header \"New\" menu offers, acting on the folder\n * currently open.\n *\n * Renders no wrapper — it finds the scrolling column and listens there, so the\n * hit area is the whole column including the padding below the list.\n *\n * Deliberately background-only. Right-clicking an asset, a folder or a table\n * row is left alone — those carry `data-native-context-menu` and keep the\n * browser's menu (which is what you want over a thumbnail or a filename).\n */\nexport const MainAreaContextMenu = ({\n onCreateFolder,\n onImportFiles,\n onImportFromUrl,\n disabled,\n}: MainAreaContextMenuProps) => {\n const { formatMessage } = useIntl();\n const [position, setPosition] = useState<CursorPosition | null>(null);\n const [locator, setLocator] = useState<HTMLElement | null>(null);\n\n const anchorRef = useCallback((node: HTMLElement | null) => setLocator(node), []);\n\n useEffect(() => {\n const container = locator?.closest<HTMLElement>(SCROLL_ROOT_SELECTOR);\n\n if (!container || disabled) {\n return;\n }\n\n const handleContextMenu = (event: MouseEvent) => {\n if (!(event.target instanceof Element)) {\n return;\n }\n\n if (event.target.closest(NATIVE_CONTEXT_MENU_SELECTOR)) {\n return;\n }\n\n event.preventDefault();\n // A right-click while the menu is open reaches Radix's dismiss listener\n // first, which closes it; re-anchoring here reopens it under the new\n // cursor rather than leaving the user with a menu that just vanished.\n setPosition({ x: event.clientX, y: event.clientY });\n };\n\n container.addEventListener('contextmenu', handleContextMenu);\n\n return () => container.removeEventListener('contextmenu', handleContextMenu);\n }, [locator, disabled]);\n\n return (\n <>\n <div ref={anchorRef} style={LOCATOR_STYLE} aria-hidden />\n {/* Non-modal, like the row-level menus: a right-click elsewhere is both\n \"close this\" and \"open that\", which a modal layer would swallow.\n Skipped entirely without the permission — the anchor is a real\n (if invisible) button, and one that can never open is noise in the\n accessibility tree. */}\n {!disabled && (\n <Menu.Root\n modal={false}\n open={position !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPosition(null);\n }\n }}\n >\n <Menu.Trigger\n tabIndex={-1}\n // No visible content, so the name has to come from `aria-label` —\n // the trigger's `label` prop renders as button text, which would\n // defeat the point of an invisible anchor. Radix points the menu's\n // `aria-labelledby` at this element, so it names the menu too.\n endIcon={null}\n aria-label={formatMessage({\n id: getTranslationKey('list.context-menu.label'),\n defaultMessage: 'Media library actions',\n })}\n style={{ ...CURSOR_ANCHOR_STYLE, top: position?.y ?? 0, left: position?.x ?? 0 }}\n />\n <ActionsMenuContent\n popoverPlacement=\"bottom-start\"\n zIndex={2}\n minWidth=\"22rem\"\n // The anchor is invisible and sits wherever the cursor was, so\n // handing focus back to it on close would be a focus ring nobody can\n // see. Let it fall to the body instead.\n onCloseAutoFocus={(event) => event.preventDefault()}\n >\n <Menu.Item onSelect={onCreateFolder} startIcon={<FolderIcon />}>\n {formatMessage({\n id: getTranslationKey('folder.create.title'),\n defaultMessage: 'New folder',\n })}\n </Menu.Item>\n <Menu.Item onSelect={onImportFiles} startIcon={<Files />}>\n {formatMessage({\n id: getTranslationKey('import-files'),\n defaultMessage: 'File upload',\n })}\n </Menu.Item>\n <Menu.Item onSelect={onImportFromUrl} startIcon={<Link />}>\n {formatMessage({\n id: getTranslationKey('import-from-url'),\n defaultMessage: 'File upload from URL',\n })}\n </Menu.Item>\n </ActionsMenuContent>\n </Menu.Root>\n )}\n </>\n );\n};\n"],"names":["SCROLL_ROOT_SELECTOR","NATIVE_CONTEXT_MENU_SELECTOR","join","CURSOR_ANCHOR_STYLE","position","width","height","minWidth","minHeight","padding","border","opacity","overflow","pointerEvents","LOCATOR_STYLE","MainAreaContextMenu","onCreateFolder","onImportFiles","onImportFromUrl","disabled","formatMessage","useIntl","setPosition","useState","locator","setLocator","anchorRef","useCallback","node","useEffect","container","closest","handleContextMenu","event","target","Element","preventDefault","x","clientX","y","clientY","addEventListener","removeEventListener","_jsxs","_Fragment","_jsx","div","ref","style","aria-hidden","Menu","Root","modal","open","onOpenChange","Trigger","tabIndex","endIcon","aria-label","id","getTranslationKey","defaultMessage","top","left","ActionsMenuContent","popoverPlacement","zIndex","onCloseAutoFocus","Item","onSelect","startIcon","FolderIcon","Files","Link"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MainAreaContextMenu.mjs","sources":["../../../../../admin/src/pages/Assets/components/MainAreaContextMenu.tsx"],"sourcesContent":["import { useCallback, useEffect, useState, type CSSProperties, type ReactNode } from 'react';\n\nimport { Menu } from '@strapi/design-system';\nimport { Files, Folder as FolderIcon, Link } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport { ActionsMenuContent } from './ActionsMenuContent';\nimport { SelectionActions } from './SelectionActions';\n\nimport type { ItemLocations } from '../../../utils/itemLocations';\n\n/**\n * The admin layout marks the element that scrolls the main column. Listening on\n * it rather than a wrapper of our own is what makes the whole column part of the\n * hit area: it carries a responsive `padding-bottom` (16 / 24 / 40px) that no\n * descendant can cover, and that strip is exactly where a right-click \"below the\n * list\" lands.\n */\nconst SCROLL_ROOT_SELECTOR = '[data-strapi-main-content]';\n\n/**\n * Subtrees that keep the browser's own context menu instead of opening the\n * create menu.\n *\n * `data-native-context-menu` is the explicit opt-out carried by the list items\n * themselves (asset cards, folder cards, table rows) and by the bulk actions\n * bar — right-clicking one of those must behave exactly as it does today. The\n * rest are the generic cases where the native menu is the useful one: links,\n * buttons and anything the user can type into or select text in.\n *\n * `thead` covers the table's header row, which is chrome rather than empty\n * background even though it carries no `data-native-context-menu` item.\n */\nconst NATIVE_CONTEXT_MENU_SELECTOR = [\n '[data-native-context-menu]',\n 'thead',\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n '[contenteditable=\"true\"]',\n].join(', ');\n\n/**\n * The menu is anchored to a zero-sized element parked at the cursor, because\n * the design system's `Menu` is a dropdown: it positions its content against a\n * trigger, and there is no real trigger for a right-click on empty space.\n *\n * Inline styles rather than a `styled` wrapper — the trigger renders a design\n * system `Button`, and losing the specificity race with its own class would put\n * a stray button on the page.\n */\nconst CURSOR_ANCHOR_STYLE: CSSProperties = {\n position: 'fixed',\n width: 0,\n height: 0,\n minWidth: 0,\n minHeight: 0,\n padding: 0,\n border: 0,\n opacity: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n};\n\nconst LOCATOR_STYLE: CSSProperties = { height: 0 };\n\ninterface CursorPosition {\n x: number;\n y: number;\n}\n\ninterface MainAreaContextMenuProps {\n /** Opens the \"new folder\" dialog in the folder currently on screen. */\n onCreateFolder: () => void;\n /** Opens the native file picker — same entry point as the header \"New\" menu. */\n onImportFiles: () => void;\n /** Opens the \"import from URL\" dialog. */\n onImportFromUrl: () => void;\n /**\n * RBAC gate — without `assets.create` there is nothing to offer, so the\n * right-click falls through to the browser. Required (not defaulted) so a\n * permission gate can't silently regress to permissive.\n */\n disabled: boolean;\n /**\n * The element to listen on, found from where this renders. Defaults to the\n * scrolling main column.\n */\n containerSelector?: string;\n /**\n * Real location of every loaded row. Supplying it turns on the selection\n * section: with items selected, their bulk actions lead the menu, above the\n * creation ones. Omitted, the menu offers creation only — which is what the\n * folder tree wants, where the asset selection is not what you are pointing\n * at.\n */\n locations?: ItemLocations;\n}\n\n/**\n * Drive/Dropbox-style right-click on the empty parts of the assets area: the\n * same creation actions the header \"New\" menu offers, acting on the folder\n * currently open.\n *\n * Renders no wrapper — it finds the scrolling column and listens there, so the\n * hit area is the whole column including the padding below the list.\n *\n * Deliberately background-only. Right-clicking an asset, a folder or a table\n * row is left alone — those carry `data-native-context-menu` and keep the\n * browser's menu (which is what you want over a thumbnail or a filename).\n */\nexport const MainAreaContextMenu = ({\n onCreateFolder,\n onImportFiles,\n onImportFromUrl,\n disabled,\n containerSelector = SCROLL_ROOT_SELECTOR,\n locations,\n}: MainAreaContextMenuProps) => {\n const { formatMessage } = useIntl();\n const [position, setPosition] = useState<CursorPosition | null>(null);\n const [locator, setLocator] = useState<HTMLElement | null>(null);\n\n const anchorRef = useCallback((node: HTMLElement | null) => setLocator(node), []);\n\n useEffect(() => {\n const container = locator?.closest<HTMLElement>(containerSelector);\n\n if (!container || disabled) {\n return;\n }\n\n const handleContextMenu = (event: MouseEvent) => {\n if (!(event.target instanceof Element)) {\n return;\n }\n\n if (event.target.closest(NATIVE_CONTEXT_MENU_SELECTOR)) {\n return;\n }\n\n event.preventDefault();\n // A right-click while the menu is open reaches Radix's dismiss listener\n // first, which closes it; re-anchoring here reopens it under the new\n // cursor rather than leaving the user with a menu that just vanished.\n setPosition({ x: event.clientX, y: event.clientY });\n };\n\n container.addEventListener('contextmenu', handleContextMenu);\n\n return () => container.removeEventListener('contextmenu', handleContextMenu);\n }, [locator, disabled, containerSelector]);\n\n const createItems = (\n <>\n <Menu.Item onSelect={onCreateFolder} startIcon={<FolderIcon />}>\n {formatMessage({\n id: getTranslationKey('folder.create.title'),\n defaultMessage: 'New folder',\n })}\n </Menu.Item>\n <Menu.Item onSelect={onImportFiles} startIcon={<Files />}>\n {formatMessage({\n id: getTranslationKey('import-files'),\n defaultMessage: 'File upload',\n })}\n </Menu.Item>\n <Menu.Item onSelect={onImportFromUrl} startIcon={<Link />}>\n {formatMessage({\n id: getTranslationKey('import-from-url'),\n defaultMessage: 'File upload from URL',\n })}\n </Menu.Item>\n </>\n );\n\n /**\n * `selectionItems` leads the menu when something is selected, so the actions\n * that apply to the selection come before the ones that create new things.\n */\n const renderMenu = (selectionItems: ReactNode) => (\n <Menu.Root\n modal={false}\n open={position !== null}\n onOpenChange={(open) => {\n if (!open) {\n setPosition(null);\n }\n }}\n >\n <Menu.Trigger\n tabIndex={-1}\n // No visible content, so the name has to come from `aria-label` —\n // the trigger's `label` prop renders as button text, which would\n // defeat the point of an invisible anchor. Radix points the menu's\n // `aria-labelledby` at this element, so it names the menu too.\n endIcon={null}\n aria-label={formatMessage({\n id: getTranslationKey('list.context-menu.label'),\n defaultMessage: 'Media library actions',\n })}\n style={{ ...CURSOR_ANCHOR_STYLE, top: position?.y ?? 0, left: position?.x ?? 0 }}\n />\n <ActionsMenuContent\n popoverPlacement=\"bottom-start\"\n zIndex={2}\n minWidth=\"22rem\"\n // The anchor is invisible and sits wherever the cursor was, so\n // handing focus back to it on close would be a focus ring nobody can\n // see. Let it fall to the body instead.\n onCloseAutoFocus={(event) => event.preventDefault()}\n >\n {selectionItems}\n {createItems}\n </ActionsMenuContent>\n </Menu.Root>\n );\n\n return (\n <>\n <div ref={anchorRef} style={LOCATOR_STYLE} aria-hidden />\n {/* Non-modal, like the row-level menus: a right-click elsewhere is both\n \"close this\" and \"open that\", which a modal layer would swallow.\n Skipped entirely without the permission — the anchor is a real\n (if invisible) button, and one that can never open is noise in the\n accessibility tree. */}\n {!disabled &&\n (locations ? (\n <SelectionActions locations={locations}>\n {({ count, hasActions, items, dialogs }) => (\n <>\n {renderMenu(\n count > 0 && hasActions ? (\n <>\n {items}\n <Menu.Separator />\n </>\n ) : null\n )}\n {/* Outside the menu: Radix unmounts its content on close, and a\n dialog opened from an item has to outlive it. */}\n {dialogs}\n </>\n )}\n </SelectionActions>\n ) : (\n renderMenu(null)\n ))}\n </>\n );\n};\n"],"names":["SCROLL_ROOT_SELECTOR","NATIVE_CONTEXT_MENU_SELECTOR","join","CURSOR_ANCHOR_STYLE","position","width","height","minWidth","minHeight","padding","border","opacity","overflow","pointerEvents","LOCATOR_STYLE","MainAreaContextMenu","onCreateFolder","onImportFiles","onImportFromUrl","disabled","containerSelector","locations","formatMessage","useIntl","setPosition","useState","locator","setLocator","anchorRef","useCallback","node","useEffect","container","closest","handleContextMenu","event","target","Element","preventDefault","x","clientX","y","clientY","addEventListener","removeEventListener","createItems","_jsxs","_Fragment","_jsx","Menu","Item","onSelect","startIcon","FolderIcon","id","getTranslationKey","defaultMessage","Files","Link","renderMenu","selectionItems","Root","modal","open","onOpenChange","Trigger","tabIndex","endIcon","aria-label","style","top","left","ActionsMenuContent","popoverPlacement","zIndex","onCloseAutoFocus","div","ref","aria-hidden","SelectionActions","count","hasActions","items","dialogs","Separator"],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var designSystem = require('@strapi/design-system');
|
|
6
|
+
var icons = require('@strapi/icons');
|
|
7
|
+
var reactIntl = require('react-intl');
|
|
8
|
+
var useMediaLibraryPermissions = require('../../../hooks/useMediaLibraryPermissions.js');
|
|
9
|
+
var buildDragSetFromSelection = require('../../../utils/buildDragSetFromSelection.js');
|
|
10
|
+
var translations = require('../../../utils/translations.js');
|
|
11
|
+
var useAssetSelection = require('../hooks/useAssetSelection.js');
|
|
12
|
+
var useFolderNavigation = require('../hooks/useFolderNavigation.js');
|
|
13
|
+
var BulkMoveDialog = require('./BulkMoveDialog.js');
|
|
14
|
+
var DeleteItemsDialog = require('./DeleteItemsDialog.js');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The actions that apply to the current selection — move and delete — with no
|
|
18
|
+
* opinion about which menu shows them.
|
|
19
|
+
*
|
|
20
|
+
* Two menus offer them: the one a right-click on a selected item opens, and the
|
|
21
|
+
* background menu, which prepends them to its own creation actions whenever
|
|
22
|
+
* something is selected. Sharing the items keeps the two from drifting, and
|
|
23
|
+
* keeps one copy of the dialogs they open.
|
|
24
|
+
*
|
|
25
|
+
* Only move and delete: the rest of an item's menu has no meaning for a set —
|
|
26
|
+
* there is no one link to copy and no one file to replace.
|
|
27
|
+
*/ const SelectionActions = ({ locations, children })=>{
|
|
28
|
+
const { formatMessage } = reactIntl.useIntl();
|
|
29
|
+
// Move and delete are both `assets.update` server-side — one flag gates both.
|
|
30
|
+
const { canUpdate, isLoading: isLoadingPermissions } = useMediaLibraryPermissions.useMediaLibraryPermissions();
|
|
31
|
+
const { selectedIds, selectedFolderIds, clear } = useAssetSelection.useAssetSelection();
|
|
32
|
+
const { currentFolderId } = useFolderNavigation.useFolderNavigation();
|
|
33
|
+
const [isMoveOpen, setIsMoveOpen] = React.useState(false);
|
|
34
|
+
const [isDeleteOpen, setIsDeleteOpen] = React.useState(false);
|
|
35
|
+
const count = selectedIds.size + selectedFolderIds.size;
|
|
36
|
+
const isBusy = isMoveOpen || isDeleteOpen;
|
|
37
|
+
// `canUpdate` is `false` until the RBAC check settles. Reading it before then
|
|
38
|
+
// would hide the actions on first render, or close a menu before it paints.
|
|
39
|
+
const hasActions = isLoadingPermissions || canUpdate;
|
|
40
|
+
const moveItems = React.useMemo(()=>buildDragSetFromSelection.buildDragSetFromSelection(selectedIds, selectedFolderIds, locations, currentFolderId), [
|
|
41
|
+
selectedIds,
|
|
42
|
+
selectedFolderIds,
|
|
43
|
+
locations,
|
|
44
|
+
currentFolderId
|
|
45
|
+
]);
|
|
46
|
+
const items = /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
47
|
+
children: [
|
|
48
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Label, {
|
|
49
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
|
|
50
|
+
variant: "sigma",
|
|
51
|
+
textColor: "neutral600",
|
|
52
|
+
children: formatMessage({
|
|
53
|
+
id: translations.getTranslationKey('list.bulk-actions.selected-count'),
|
|
54
|
+
defaultMessage: '{count, plural, =1 {# item selected} other {# items selected}}'
|
|
55
|
+
}, {
|
|
56
|
+
count
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
}),
|
|
60
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Separator, {}),
|
|
61
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Item, {
|
|
62
|
+
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.ArrowRight, {}),
|
|
63
|
+
onSelect: ()=>setIsMoveOpen(true),
|
|
64
|
+
children: formatMessage({
|
|
65
|
+
id: translations.getTranslationKey('list.bulk-actions.move'),
|
|
66
|
+
defaultMessage: 'Move'
|
|
67
|
+
})
|
|
68
|
+
}),
|
|
69
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Item, {
|
|
70
|
+
startIcon: /*#__PURE__*/ jsxRuntime.jsx(icons.Trash, {}),
|
|
71
|
+
variant: "danger",
|
|
72
|
+
onSelect: ()=>setIsDeleteOpen(true),
|
|
73
|
+
children: formatMessage({
|
|
74
|
+
id: translations.getTranslationKey('list.bulk-actions.delete'),
|
|
75
|
+
defaultMessage: 'Delete'
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
const dialogs = /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
81
|
+
children: [
|
|
82
|
+
isMoveOpen && /*#__PURE__*/ jsxRuntime.jsx(BulkMoveDialog.BulkMoveDialog, {
|
|
83
|
+
open: true,
|
|
84
|
+
onClose: ()=>setIsMoveOpen(false),
|
|
85
|
+
items: moveItems,
|
|
86
|
+
onSuccess: clear
|
|
87
|
+
}),
|
|
88
|
+
isDeleteOpen && /*#__PURE__*/ jsxRuntime.jsx(DeleteItemsDialog.DeleteItemsDialog, {
|
|
89
|
+
open: true,
|
|
90
|
+
onClose: ()=>setIsDeleteOpen(false),
|
|
91
|
+
target: {
|
|
92
|
+
fileIds: Array.from(selectedIds),
|
|
93
|
+
folderIds: Array.from(selectedFolderIds)
|
|
94
|
+
},
|
|
95
|
+
onSuccess: clear
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
});
|
|
99
|
+
return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
|
|
100
|
+
children: children({
|
|
101
|
+
count,
|
|
102
|
+
hasActions,
|
|
103
|
+
items,
|
|
104
|
+
dialogs,
|
|
105
|
+
isBusy
|
|
106
|
+
})
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
exports.SelectionActions = SelectionActions;
|
|
111
|
+
//# sourceMappingURL=SelectionActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionActions.js","sources":["../../../../../admin/src/pages/Assets/components/SelectionActions.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\n\nimport { Menu, Typography } from '@strapi/design-system';\nimport { ArrowRight, Trash } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { useMediaLibraryPermissions } from '../../../hooks/useMediaLibraryPermissions';\nimport { buildDragSetFromSelection } from '../../../utils/buildDragSetFromSelection';\nimport { getTranslationKey } from '../../../utils/translations';\nimport { useAssetSelection } from '../hooks/useAssetSelection';\nimport { useFolderNavigation } from '../hooks/useFolderNavigation';\n\nimport { BulkMoveDialog } from './BulkMoveDialog';\nimport { DeleteItemsDialog } from './DeleteItemsDialog';\n\nimport type { ItemLocations } from '../../../utils/itemLocations';\n\ninterface SelectionActionsRender {\n /** How many items the actions would act on. Zero means nothing is selected. */\n count: number;\n /**\n * Whether there is anything to offer. False for a role that can read but not\n * update; true while the RBAC check is still settling, so a menu built on it\n * does not flash empty.\n */\n hasActions: boolean;\n /** The count label, a separator, then Move and Delete. */\n items: ReactNode;\n /** Rendered outside `Menu.Root` — a dialog has to outlive the menu that opened it. */\n dialogs: ReactNode;\n /** True while a dialog is open, for callers that unmount once nothing is on screen. */\n isBusy: boolean;\n}\n\ninterface SelectionActionsProps {\n /**\n * Real location of every loaded row, so the move dialog validates each\n * selected item against its own parent — same input the bulk bar takes.\n */\n locations: ItemLocations;\n children: (actions: SelectionActionsRender) => ReactNode;\n}\n\n/**\n * The actions that apply to the current selection — move and delete — with no\n * opinion about which menu shows them.\n *\n * Two menus offer them: the one a right-click on a selected item opens, and the\n * background menu, which prepends them to its own creation actions whenever\n * something is selected. Sharing the items keeps the two from drifting, and\n * keeps one copy of the dialogs they open.\n *\n * Only move and delete: the rest of an item's menu has no meaning for a set —\n * there is no one link to copy and no one file to replace.\n */\nexport const SelectionActions = ({ locations, children }: SelectionActionsProps) => {\n const { formatMessage } = useIntl();\n // Move and delete are both `assets.update` server-side — one flag gates both.\n const { canUpdate, isLoading: isLoadingPermissions } = useMediaLibraryPermissions();\n const { selectedIds, selectedFolderIds, clear } = useAssetSelection();\n const { currentFolderId } = useFolderNavigation();\n const [isMoveOpen, setIsMoveOpen] = useState(false);\n const [isDeleteOpen, setIsDeleteOpen] = useState(false);\n\n const count = selectedIds.size + selectedFolderIds.size;\n const isBusy = isMoveOpen || isDeleteOpen;\n // `canUpdate` is `false` until the RBAC check settles. Reading it before then\n // would hide the actions on first render, or close a menu before it paints.\n const hasActions = isLoadingPermissions || canUpdate;\n\n const moveItems = useMemo(\n () => buildDragSetFromSelection(selectedIds, selectedFolderIds, locations, currentFolderId),\n [selectedIds, selectedFolderIds, locations, currentFolderId]\n );\n\n const items = (\n <>\n {/* A label rather than a `Menu.Item`: it states what the actions below\n will act on and is not itself selectable. */}\n <Menu.Label>\n <Typography variant=\"sigma\" textColor=\"neutral600\">\n {formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.selected-count'),\n defaultMessage: '{count, plural, =1 {# item selected} other {# items selected}}',\n },\n { count }\n )}\n </Typography>\n </Menu.Label>\n <Menu.Separator />\n <Menu.Item startIcon={<ArrowRight />} onSelect={() => setIsMoveOpen(true)}>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move'),\n defaultMessage: 'Move',\n })}\n </Menu.Item>\n <Menu.Item startIcon={<Trash />} variant=\"danger\" onSelect={() => setIsDeleteOpen(true)}>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.delete'),\n defaultMessage: 'Delete',\n })}\n </Menu.Item>\n </>\n );\n\n const dialogs = (\n <>\n {isMoveOpen && (\n <BulkMoveDialog\n open\n onClose={() => setIsMoveOpen(false)}\n items={moveItems}\n onSuccess={clear}\n />\n )}\n {isDeleteOpen && (\n <DeleteItemsDialog\n open\n onClose={() => setIsDeleteOpen(false)}\n target={{\n fileIds: Array.from(selectedIds),\n folderIds: Array.from(selectedFolderIds),\n }}\n onSuccess={clear}\n />\n )}\n </>\n );\n\n return <>{children({ count, hasActions, items, dialogs, isBusy })}</>;\n};\n"],"names":["SelectionActions","locations","children","formatMessage","useIntl","canUpdate","isLoading","isLoadingPermissions","useMediaLibraryPermissions","selectedIds","selectedFolderIds","clear","useAssetSelection","currentFolderId","useFolderNavigation","isMoveOpen","setIsMoveOpen","useState","isDeleteOpen","setIsDeleteOpen","count","size","isBusy","hasActions","moveItems","useMemo","buildDragSetFromSelection","items","_jsxs","_Fragment","_jsx","Menu","Label","Typography","variant","textColor","id","getTranslationKey","defaultMessage","Separator","Item","startIcon","ArrowRight","onSelect","Trash","dialogs","BulkMoveDialog","open","onClose","onSuccess","DeleteItemsDialog","target","fileIds","Array","from","folderIds"],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useMemo } from 'react';
|
|
3
|
+
import { Menu, Typography } from '@strapi/design-system';
|
|
4
|
+
import { ArrowRight, Trash } from '@strapi/icons';
|
|
5
|
+
import { useIntl } from 'react-intl';
|
|
6
|
+
import { useMediaLibraryPermissions } from '../../../hooks/useMediaLibraryPermissions.mjs';
|
|
7
|
+
import { buildDragSetFromSelection } from '../../../utils/buildDragSetFromSelection.mjs';
|
|
8
|
+
import { getTranslationKey } from '../../../utils/translations.mjs';
|
|
9
|
+
import { useAssetSelection } from '../hooks/useAssetSelection.mjs';
|
|
10
|
+
import { useFolderNavigation } from '../hooks/useFolderNavigation.mjs';
|
|
11
|
+
import { BulkMoveDialog } from './BulkMoveDialog.mjs';
|
|
12
|
+
import { DeleteItemsDialog } from './DeleteItemsDialog.mjs';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The actions that apply to the current selection — move and delete — with no
|
|
16
|
+
* opinion about which menu shows them.
|
|
17
|
+
*
|
|
18
|
+
* Two menus offer them: the one a right-click on a selected item opens, and the
|
|
19
|
+
* background menu, which prepends them to its own creation actions whenever
|
|
20
|
+
* something is selected. Sharing the items keeps the two from drifting, and
|
|
21
|
+
* keeps one copy of the dialogs they open.
|
|
22
|
+
*
|
|
23
|
+
* Only move and delete: the rest of an item's menu has no meaning for a set —
|
|
24
|
+
* there is no one link to copy and no one file to replace.
|
|
25
|
+
*/ const SelectionActions = ({ locations, children })=>{
|
|
26
|
+
const { formatMessage } = useIntl();
|
|
27
|
+
// Move and delete are both `assets.update` server-side — one flag gates both.
|
|
28
|
+
const { canUpdate, isLoading: isLoadingPermissions } = useMediaLibraryPermissions();
|
|
29
|
+
const { selectedIds, selectedFolderIds, clear } = useAssetSelection();
|
|
30
|
+
const { currentFolderId } = useFolderNavigation();
|
|
31
|
+
const [isMoveOpen, setIsMoveOpen] = useState(false);
|
|
32
|
+
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
|
33
|
+
const count = selectedIds.size + selectedFolderIds.size;
|
|
34
|
+
const isBusy = isMoveOpen || isDeleteOpen;
|
|
35
|
+
// `canUpdate` is `false` until the RBAC check settles. Reading it before then
|
|
36
|
+
// would hide the actions on first render, or close a menu before it paints.
|
|
37
|
+
const hasActions = isLoadingPermissions || canUpdate;
|
|
38
|
+
const moveItems = useMemo(()=>buildDragSetFromSelection(selectedIds, selectedFolderIds, locations, currentFolderId), [
|
|
39
|
+
selectedIds,
|
|
40
|
+
selectedFolderIds,
|
|
41
|
+
locations,
|
|
42
|
+
currentFolderId
|
|
43
|
+
]);
|
|
44
|
+
const items = /*#__PURE__*/ jsxs(Fragment, {
|
|
45
|
+
children: [
|
|
46
|
+
/*#__PURE__*/ jsx(Menu.Label, {
|
|
47
|
+
children: /*#__PURE__*/ jsx(Typography, {
|
|
48
|
+
variant: "sigma",
|
|
49
|
+
textColor: "neutral600",
|
|
50
|
+
children: formatMessage({
|
|
51
|
+
id: getTranslationKey('list.bulk-actions.selected-count'),
|
|
52
|
+
defaultMessage: '{count, plural, =1 {# item selected} other {# items selected}}'
|
|
53
|
+
}, {
|
|
54
|
+
count
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
}),
|
|
58
|
+
/*#__PURE__*/ jsx(Menu.Separator, {}),
|
|
59
|
+
/*#__PURE__*/ jsx(Menu.Item, {
|
|
60
|
+
startIcon: /*#__PURE__*/ jsx(ArrowRight, {}),
|
|
61
|
+
onSelect: ()=>setIsMoveOpen(true),
|
|
62
|
+
children: formatMessage({
|
|
63
|
+
id: getTranslationKey('list.bulk-actions.move'),
|
|
64
|
+
defaultMessage: 'Move'
|
|
65
|
+
})
|
|
66
|
+
}),
|
|
67
|
+
/*#__PURE__*/ jsx(Menu.Item, {
|
|
68
|
+
startIcon: /*#__PURE__*/ jsx(Trash, {}),
|
|
69
|
+
variant: "danger",
|
|
70
|
+
onSelect: ()=>setIsDeleteOpen(true),
|
|
71
|
+
children: formatMessage({
|
|
72
|
+
id: getTranslationKey('list.bulk-actions.delete'),
|
|
73
|
+
defaultMessage: 'Delete'
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
]
|
|
77
|
+
});
|
|
78
|
+
const dialogs = /*#__PURE__*/ jsxs(Fragment, {
|
|
79
|
+
children: [
|
|
80
|
+
isMoveOpen && /*#__PURE__*/ jsx(BulkMoveDialog, {
|
|
81
|
+
open: true,
|
|
82
|
+
onClose: ()=>setIsMoveOpen(false),
|
|
83
|
+
items: moveItems,
|
|
84
|
+
onSuccess: clear
|
|
85
|
+
}),
|
|
86
|
+
isDeleteOpen && /*#__PURE__*/ jsx(DeleteItemsDialog, {
|
|
87
|
+
open: true,
|
|
88
|
+
onClose: ()=>setIsDeleteOpen(false),
|
|
89
|
+
target: {
|
|
90
|
+
fileIds: Array.from(selectedIds),
|
|
91
|
+
folderIds: Array.from(selectedFolderIds)
|
|
92
|
+
},
|
|
93
|
+
onSuccess: clear
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
return /*#__PURE__*/ jsx(Fragment, {
|
|
98
|
+
children: children({
|
|
99
|
+
count,
|
|
100
|
+
hasActions,
|
|
101
|
+
items,
|
|
102
|
+
dialogs,
|
|
103
|
+
isBusy
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export { SelectionActions };
|
|
109
|
+
//# sourceMappingURL=SelectionActions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionActions.mjs","sources":["../../../../../admin/src/pages/Assets/components/SelectionActions.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\n\nimport { Menu, Typography } from '@strapi/design-system';\nimport { ArrowRight, Trash } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\n\nimport { useMediaLibraryPermissions } from '../../../hooks/useMediaLibraryPermissions';\nimport { buildDragSetFromSelection } from '../../../utils/buildDragSetFromSelection';\nimport { getTranslationKey } from '../../../utils/translations';\nimport { useAssetSelection } from '../hooks/useAssetSelection';\nimport { useFolderNavigation } from '../hooks/useFolderNavigation';\n\nimport { BulkMoveDialog } from './BulkMoveDialog';\nimport { DeleteItemsDialog } from './DeleteItemsDialog';\n\nimport type { ItemLocations } from '../../../utils/itemLocations';\n\ninterface SelectionActionsRender {\n /** How many items the actions would act on. Zero means nothing is selected. */\n count: number;\n /**\n * Whether there is anything to offer. False for a role that can read but not\n * update; true while the RBAC check is still settling, so a menu built on it\n * does not flash empty.\n */\n hasActions: boolean;\n /** The count label, a separator, then Move and Delete. */\n items: ReactNode;\n /** Rendered outside `Menu.Root` — a dialog has to outlive the menu that opened it. */\n dialogs: ReactNode;\n /** True while a dialog is open, for callers that unmount once nothing is on screen. */\n isBusy: boolean;\n}\n\ninterface SelectionActionsProps {\n /**\n * Real location of every loaded row, so the move dialog validates each\n * selected item against its own parent — same input the bulk bar takes.\n */\n locations: ItemLocations;\n children: (actions: SelectionActionsRender) => ReactNode;\n}\n\n/**\n * The actions that apply to the current selection — move and delete — with no\n * opinion about which menu shows them.\n *\n * Two menus offer them: the one a right-click on a selected item opens, and the\n * background menu, which prepends them to its own creation actions whenever\n * something is selected. Sharing the items keeps the two from drifting, and\n * keeps one copy of the dialogs they open.\n *\n * Only move and delete: the rest of an item's menu has no meaning for a set —\n * there is no one link to copy and no one file to replace.\n */\nexport const SelectionActions = ({ locations, children }: SelectionActionsProps) => {\n const { formatMessage } = useIntl();\n // Move and delete are both `assets.update` server-side — one flag gates both.\n const { canUpdate, isLoading: isLoadingPermissions } = useMediaLibraryPermissions();\n const { selectedIds, selectedFolderIds, clear } = useAssetSelection();\n const { currentFolderId } = useFolderNavigation();\n const [isMoveOpen, setIsMoveOpen] = useState(false);\n const [isDeleteOpen, setIsDeleteOpen] = useState(false);\n\n const count = selectedIds.size + selectedFolderIds.size;\n const isBusy = isMoveOpen || isDeleteOpen;\n // `canUpdate` is `false` until the RBAC check settles. Reading it before then\n // would hide the actions on first render, or close a menu before it paints.\n const hasActions = isLoadingPermissions || canUpdate;\n\n const moveItems = useMemo(\n () => buildDragSetFromSelection(selectedIds, selectedFolderIds, locations, currentFolderId),\n [selectedIds, selectedFolderIds, locations, currentFolderId]\n );\n\n const items = (\n <>\n {/* A label rather than a `Menu.Item`: it states what the actions below\n will act on and is not itself selectable. */}\n <Menu.Label>\n <Typography variant=\"sigma\" textColor=\"neutral600\">\n {formatMessage(\n {\n id: getTranslationKey('list.bulk-actions.selected-count'),\n defaultMessage: '{count, plural, =1 {# item selected} other {# items selected}}',\n },\n { count }\n )}\n </Typography>\n </Menu.Label>\n <Menu.Separator />\n <Menu.Item startIcon={<ArrowRight />} onSelect={() => setIsMoveOpen(true)}>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.move'),\n defaultMessage: 'Move',\n })}\n </Menu.Item>\n <Menu.Item startIcon={<Trash />} variant=\"danger\" onSelect={() => setIsDeleteOpen(true)}>\n {formatMessage({\n id: getTranslationKey('list.bulk-actions.delete'),\n defaultMessage: 'Delete',\n })}\n </Menu.Item>\n </>\n );\n\n const dialogs = (\n <>\n {isMoveOpen && (\n <BulkMoveDialog\n open\n onClose={() => setIsMoveOpen(false)}\n items={moveItems}\n onSuccess={clear}\n />\n )}\n {isDeleteOpen && (\n <DeleteItemsDialog\n open\n onClose={() => setIsDeleteOpen(false)}\n target={{\n fileIds: Array.from(selectedIds),\n folderIds: Array.from(selectedFolderIds),\n }}\n onSuccess={clear}\n />\n )}\n </>\n );\n\n return <>{children({ count, hasActions, items, dialogs, isBusy })}</>;\n};\n"],"names":["SelectionActions","locations","children","formatMessage","useIntl","canUpdate","isLoading","isLoadingPermissions","useMediaLibraryPermissions","selectedIds","selectedFolderIds","clear","useAssetSelection","currentFolderId","useFolderNavigation","isMoveOpen","setIsMoveOpen","useState","isDeleteOpen","setIsDeleteOpen","count","size","isBusy","hasActions","moveItems","useMemo","buildDragSetFromSelection","items","_jsxs","_Fragment","_jsx","Menu","Label","Typography","variant","textColor","id","getTranslationKey","defaultMessage","Separator","Item","startIcon","ArrowRight","onSelect","Trash","dialogs","BulkMoveDialog","open","onClose","onSuccess","DeleteItemsDialog","target","fileIds","Array","from","folderIds"],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var reactIntl = require('react-intl');
|
|
6
|
+
var translations = require('../../../utils/translations.js');
|
|
7
|
+
var CursorAnchoredMenu = require('./CursorAnchoredMenu.js');
|
|
8
|
+
var SelectionActions = require('./SelectionActions.js');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Right-clicking one of several selected items acts on the selection, the way a
|
|
12
|
+
* file manager does.
|
|
13
|
+
*
|
|
14
|
+
* The count leads, so what the menu is about to act on is stated before the
|
|
15
|
+
* actions are. Right-clicking an item that is *not* in the selection replaces
|
|
16
|
+
* the selection with it first, and `AssetContextMenu` handles it instead.
|
|
17
|
+
*
|
|
18
|
+
* Right-clicking the background with a selection live is a different menu:
|
|
19
|
+
* `MainAreaContextMenu` shows these same actions above its creation ones.
|
|
20
|
+
*/ const SelectionContextMenu = ({ position, returnFocusTo, locations, onClose })=>{
|
|
21
|
+
const { formatMessage } = reactIntl.useIntl();
|
|
22
|
+
return /*#__PURE__*/ jsxRuntime.jsx(SelectionActions.SelectionActions, {
|
|
23
|
+
locations: locations,
|
|
24
|
+
children: ({ hasActions, items, dialogs, isBusy })=>/*#__PURE__*/ jsxRuntime.jsx(SelectionContextMenuBody, {
|
|
25
|
+
hasActions: hasActions,
|
|
26
|
+
isBusy: isBusy,
|
|
27
|
+
position: position,
|
|
28
|
+
returnFocusTo: returnFocusTo,
|
|
29
|
+
label: formatMessage({
|
|
30
|
+
id: translations.getTranslationKey('list.selection.context-menu.label'),
|
|
31
|
+
defaultMessage: 'Selection actions'
|
|
32
|
+
}),
|
|
33
|
+
items: items,
|
|
34
|
+
dialogs: dialogs,
|
|
35
|
+
onClose: onClose
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Split out because the "nothing left on screen" effects can't run inside the
|
|
41
|
+
* render prop above — hooks don't belong in a callback.
|
|
42
|
+
*/ const SelectionContextMenuBody = ({ hasActions, isBusy, position, returnFocusTo, label, items, dialogs, onClose })=>{
|
|
43
|
+
const [isMenuOpen, setIsMenuOpen] = React.useState(true);
|
|
44
|
+
// Nothing left on screen — the menu is shut and no dialog took its place.
|
|
45
|
+
React.useEffect(()=>{
|
|
46
|
+
if (!isMenuOpen && !isBusy) {
|
|
47
|
+
onClose();
|
|
48
|
+
}
|
|
49
|
+
}, [
|
|
50
|
+
isMenuOpen,
|
|
51
|
+
isBusy,
|
|
52
|
+
onClose
|
|
53
|
+
]);
|
|
54
|
+
// A role that can read but not update has nothing to offer for a selection.
|
|
55
|
+
React.useEffect(()=>{
|
|
56
|
+
if (!hasActions) {
|
|
57
|
+
onClose();
|
|
58
|
+
}
|
|
59
|
+
}, [
|
|
60
|
+
hasActions,
|
|
61
|
+
onClose
|
|
62
|
+
]);
|
|
63
|
+
if (!hasActions) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
67
|
+
children: [
|
|
68
|
+
/*#__PURE__*/ jsxRuntime.jsx(CursorAnchoredMenu.CursorAnchoredMenu, {
|
|
69
|
+
position: position,
|
|
70
|
+
open: isMenuOpen,
|
|
71
|
+
label: label,
|
|
72
|
+
returnFocusTo: returnFocusTo,
|
|
73
|
+
onClose: ()=>setIsMenuOpen(false),
|
|
74
|
+
children: items
|
|
75
|
+
}),
|
|
76
|
+
dialogs
|
|
77
|
+
]
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
exports.SelectionContextMenu = SelectionContextMenu;
|
|
82
|
+
//# sourceMappingURL=SelectionContextMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionContextMenu.js","sources":["../../../../../admin/src/pages/Assets/components/SelectionContextMenu.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from 'react';\n\nimport { useIntl } from 'react-intl';\n\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport { CursorAnchoredMenu, type CursorPosition } from './CursorAnchoredMenu';\nimport { SelectionActions } from './SelectionActions';\n\nimport type { ItemLocations } from '../../../utils/itemLocations';\n\ninterface SelectionContextMenuProps {\n position: CursorPosition;\n returnFocusTo: HTMLElement | null;\n /**\n * Real location of every loaded row, so the move dialog validates each\n * selected item against its own parent — same input the bulk bar takes.\n */\n locations: ItemLocations;\n onClose: () => void;\n}\n\n/**\n * Right-clicking one of several selected items acts on the selection, the way a\n * file manager does.\n *\n * The count leads, so what the menu is about to act on is stated before the\n * actions are. Right-clicking an item that is *not* in the selection replaces\n * the selection with it first, and `AssetContextMenu` handles it instead.\n *\n * Right-clicking the background with a selection live is a different menu:\n * `MainAreaContextMenu` shows these same actions above its creation ones.\n */\nexport const SelectionContextMenu = ({\n position,\n returnFocusTo,\n locations,\n onClose,\n}: SelectionContextMenuProps) => {\n const { formatMessage } = useIntl();\n\n return (\n <SelectionActions locations={locations}>\n {({ hasActions, items, dialogs, isBusy }) => (\n <SelectionContextMenuBody\n hasActions={hasActions}\n isBusy={isBusy}\n position={position}\n returnFocusTo={returnFocusTo}\n label={formatMessage({\n id: getTranslationKey('list.selection.context-menu.label'),\n defaultMessage: 'Selection actions',\n })}\n items={items}\n dialogs={dialogs}\n onClose={onClose}\n />\n )}\n </SelectionActions>\n );\n};\n\ninterface SelectionContextMenuBodyProps {\n hasActions: boolean;\n isBusy: boolean;\n position: CursorPosition;\n returnFocusTo: HTMLElement | null;\n label: string;\n items: ReactNode;\n dialogs: ReactNode;\n onClose: () => void;\n}\n\n/**\n * Split out because the \"nothing left on screen\" effects can't run inside the\n * render prop above — hooks don't belong in a callback.\n */\nconst SelectionContextMenuBody = ({\n hasActions,\n isBusy,\n position,\n returnFocusTo,\n label,\n items,\n dialogs,\n onClose,\n}: SelectionContextMenuBodyProps) => {\n const [isMenuOpen, setIsMenuOpen] = useState(true);\n\n // Nothing left on screen — the menu is shut and no dialog took its place.\n useEffect(() => {\n if (!isMenuOpen && !isBusy) {\n onClose();\n }\n }, [isMenuOpen, isBusy, onClose]);\n\n // A role that can read but not update has nothing to offer for a selection.\n useEffect(() => {\n if (!hasActions) {\n onClose();\n }\n }, [hasActions, onClose]);\n\n if (!hasActions) {\n return null;\n }\n\n return (\n <>\n <CursorAnchoredMenu\n position={position}\n open={isMenuOpen}\n label={label}\n returnFocusTo={returnFocusTo}\n onClose={() => setIsMenuOpen(false)}\n >\n {items}\n </CursorAnchoredMenu>\n {dialogs}\n </>\n );\n};\n"],"names":["SelectionContextMenu","position","returnFocusTo","locations","onClose","formatMessage","useIntl","_jsx","SelectionActions","hasActions","items","dialogs","isBusy","SelectionContextMenuBody","label","id","getTranslationKey","defaultMessage","isMenuOpen","setIsMenuOpen","useState","useEffect","_jsxs","_Fragment","CursorAnchoredMenu","open"],"mappings":";;;;;;;;;AAsBA;;;;;;;;;;AAUC,IACM,MAAMA,oBAAAA,GAAuB,CAAC,EACnCC,QAAQ,EACRC,aAAa,EACbC,SAAS,EACTC,OAAO,EACmB,GAAA;IAC1B,MAAM,EAAEC,aAAa,EAAE,GAAGC,iBAAAA,EAAAA;AAE1B,IAAA,qBACEC,cAAA,CAACC,iCAAAA,EAAAA;QAAiBL,SAAAA,EAAWA,SAAAA;kBAC1B,CAAC,EAAEM,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,MAAM,EAAE,iBACtCL,cAAA,CAACM,wBAAAA,EAAAA;gBACCJ,UAAAA,EAAYA,UAAAA;gBACZG,MAAAA,EAAQA,MAAAA;gBACRX,QAAAA,EAAUA,QAAAA;gBACVC,aAAAA,EAAeA,aAAAA;AACfY,gBAAAA,KAAAA,EAAOT,aAAAA,CAAc;AACnBU,oBAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,mCAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA,CAAA;gBACAP,KAAAA,EAAOA,KAAAA;gBACPC,OAAAA,EAASA,OAAAA;gBACTP,OAAAA,EAASA;;;AAKnB;AAaA;;;AAGC,IACD,MAAMS,wBAAAA,GAA2B,CAAC,EAChCJ,UAAU,EACVG,MAAM,EACNX,QAAQ,EACRC,aAAa,EACbY,KAAK,EACLJ,KAAK,EACLC,OAAO,EACPP,OAAO,EACuB,GAAA;AAC9B,IAAA,MAAM,CAACc,UAAAA,EAAYC,aAAAA,CAAc,GAAGC,cAAAA,CAAS,IAAA,CAAA;;IAG7CC,eAAAA,CAAU,IAAA;QACR,IAAI,CAACH,UAAAA,IAAc,CAACN,MAAAA,EAAQ;AAC1BR,YAAAA,OAAAA,EAAAA;AACF,QAAA;IACF,CAAA,EAAG;AAACc,QAAAA,UAAAA;AAAYN,QAAAA,MAAAA;AAAQR,QAAAA;AAAQ,KAAA,CAAA;;IAGhCiB,eAAAA,CAAU,IAAA;AACR,QAAA,IAAI,CAACZ,UAAAA,EAAY;AACfL,YAAAA,OAAAA,EAAAA;AACF,QAAA;IACF,CAAA,EAAG;AAACK,QAAAA,UAAAA;AAAYL,QAAAA;AAAQ,KAAA,CAAA;AAExB,IAAA,IAAI,CAACK,UAAAA,EAAY;QACf,OAAO,IAAA;AACT,IAAA;IAEA,qBACEa,eAAA,CAAAC,mBAAA,EAAA;;0BACEhB,cAAA,CAACiB,qCAAAA,EAAAA;gBACCvB,QAAAA,EAAUA,QAAAA;gBACVwB,IAAAA,EAAMP,UAAAA;gBACNJ,KAAAA,EAAOA,KAAAA;gBACPZ,aAAAA,EAAeA,aAAAA;AACfE,gBAAAA,OAAAA,EAAS,IAAMe,aAAAA,CAAc,KAAA,CAAA;AAE5BT,gBAAAA,QAAAA,EAAAA;;AAEFC,YAAAA;;;AAGP,CAAA;;;;"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
import { getTranslationKey } from '../../../utils/translations.mjs';
|
|
5
|
+
import { CursorAnchoredMenu } from './CursorAnchoredMenu.mjs';
|
|
6
|
+
import { SelectionActions } from './SelectionActions.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Right-clicking one of several selected items acts on the selection, the way a
|
|
10
|
+
* file manager does.
|
|
11
|
+
*
|
|
12
|
+
* The count leads, so what the menu is about to act on is stated before the
|
|
13
|
+
* actions are. Right-clicking an item that is *not* in the selection replaces
|
|
14
|
+
* the selection with it first, and `AssetContextMenu` handles it instead.
|
|
15
|
+
*
|
|
16
|
+
* Right-clicking the background with a selection live is a different menu:
|
|
17
|
+
* `MainAreaContextMenu` shows these same actions above its creation ones.
|
|
18
|
+
*/ const SelectionContextMenu = ({ position, returnFocusTo, locations, onClose })=>{
|
|
19
|
+
const { formatMessage } = useIntl();
|
|
20
|
+
return /*#__PURE__*/ jsx(SelectionActions, {
|
|
21
|
+
locations: locations,
|
|
22
|
+
children: ({ hasActions, items, dialogs, isBusy })=>/*#__PURE__*/ jsx(SelectionContextMenuBody, {
|
|
23
|
+
hasActions: hasActions,
|
|
24
|
+
isBusy: isBusy,
|
|
25
|
+
position: position,
|
|
26
|
+
returnFocusTo: returnFocusTo,
|
|
27
|
+
label: formatMessage({
|
|
28
|
+
id: getTranslationKey('list.selection.context-menu.label'),
|
|
29
|
+
defaultMessage: 'Selection actions'
|
|
30
|
+
}),
|
|
31
|
+
items: items,
|
|
32
|
+
dialogs: dialogs,
|
|
33
|
+
onClose: onClose
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Split out because the "nothing left on screen" effects can't run inside the
|
|
39
|
+
* render prop above — hooks don't belong in a callback.
|
|
40
|
+
*/ const SelectionContextMenuBody = ({ hasActions, isBusy, position, returnFocusTo, label, items, dialogs, onClose })=>{
|
|
41
|
+
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
|
42
|
+
// Nothing left on screen — the menu is shut and no dialog took its place.
|
|
43
|
+
useEffect(()=>{
|
|
44
|
+
if (!isMenuOpen && !isBusy) {
|
|
45
|
+
onClose();
|
|
46
|
+
}
|
|
47
|
+
}, [
|
|
48
|
+
isMenuOpen,
|
|
49
|
+
isBusy,
|
|
50
|
+
onClose
|
|
51
|
+
]);
|
|
52
|
+
// A role that can read but not update has nothing to offer for a selection.
|
|
53
|
+
useEffect(()=>{
|
|
54
|
+
if (!hasActions) {
|
|
55
|
+
onClose();
|
|
56
|
+
}
|
|
57
|
+
}, [
|
|
58
|
+
hasActions,
|
|
59
|
+
onClose
|
|
60
|
+
]);
|
|
61
|
+
if (!hasActions) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
65
|
+
children: [
|
|
66
|
+
/*#__PURE__*/ jsx(CursorAnchoredMenu, {
|
|
67
|
+
position: position,
|
|
68
|
+
open: isMenuOpen,
|
|
69
|
+
label: label,
|
|
70
|
+
returnFocusTo: returnFocusTo,
|
|
71
|
+
onClose: ()=>setIsMenuOpen(false),
|
|
72
|
+
children: items
|
|
73
|
+
}),
|
|
74
|
+
dialogs
|
|
75
|
+
]
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export { SelectionContextMenu };
|
|
80
|
+
//# sourceMappingURL=SelectionContextMenu.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionContextMenu.mjs","sources":["../../../../../admin/src/pages/Assets/components/SelectionContextMenu.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from 'react';\n\nimport { useIntl } from 'react-intl';\n\nimport { getTranslationKey } from '../../../utils/translations';\n\nimport { CursorAnchoredMenu, type CursorPosition } from './CursorAnchoredMenu';\nimport { SelectionActions } from './SelectionActions';\n\nimport type { ItemLocations } from '../../../utils/itemLocations';\n\ninterface SelectionContextMenuProps {\n position: CursorPosition;\n returnFocusTo: HTMLElement | null;\n /**\n * Real location of every loaded row, so the move dialog validates each\n * selected item against its own parent — same input the bulk bar takes.\n */\n locations: ItemLocations;\n onClose: () => void;\n}\n\n/**\n * Right-clicking one of several selected items acts on the selection, the way a\n * file manager does.\n *\n * The count leads, so what the menu is about to act on is stated before the\n * actions are. Right-clicking an item that is *not* in the selection replaces\n * the selection with it first, and `AssetContextMenu` handles it instead.\n *\n * Right-clicking the background with a selection live is a different menu:\n * `MainAreaContextMenu` shows these same actions above its creation ones.\n */\nexport const SelectionContextMenu = ({\n position,\n returnFocusTo,\n locations,\n onClose,\n}: SelectionContextMenuProps) => {\n const { formatMessage } = useIntl();\n\n return (\n <SelectionActions locations={locations}>\n {({ hasActions, items, dialogs, isBusy }) => (\n <SelectionContextMenuBody\n hasActions={hasActions}\n isBusy={isBusy}\n position={position}\n returnFocusTo={returnFocusTo}\n label={formatMessage({\n id: getTranslationKey('list.selection.context-menu.label'),\n defaultMessage: 'Selection actions',\n })}\n items={items}\n dialogs={dialogs}\n onClose={onClose}\n />\n )}\n </SelectionActions>\n );\n};\n\ninterface SelectionContextMenuBodyProps {\n hasActions: boolean;\n isBusy: boolean;\n position: CursorPosition;\n returnFocusTo: HTMLElement | null;\n label: string;\n items: ReactNode;\n dialogs: ReactNode;\n onClose: () => void;\n}\n\n/**\n * Split out because the \"nothing left on screen\" effects can't run inside the\n * render prop above — hooks don't belong in a callback.\n */\nconst SelectionContextMenuBody = ({\n hasActions,\n isBusy,\n position,\n returnFocusTo,\n label,\n items,\n dialogs,\n onClose,\n}: SelectionContextMenuBodyProps) => {\n const [isMenuOpen, setIsMenuOpen] = useState(true);\n\n // Nothing left on screen — the menu is shut and no dialog took its place.\n useEffect(() => {\n if (!isMenuOpen && !isBusy) {\n onClose();\n }\n }, [isMenuOpen, isBusy, onClose]);\n\n // A role that can read but not update has nothing to offer for a selection.\n useEffect(() => {\n if (!hasActions) {\n onClose();\n }\n }, [hasActions, onClose]);\n\n if (!hasActions) {\n return null;\n }\n\n return (\n <>\n <CursorAnchoredMenu\n position={position}\n open={isMenuOpen}\n label={label}\n returnFocusTo={returnFocusTo}\n onClose={() => setIsMenuOpen(false)}\n >\n {items}\n </CursorAnchoredMenu>\n {dialogs}\n </>\n );\n};\n"],"names":["SelectionContextMenu","position","returnFocusTo","locations","onClose","formatMessage","useIntl","_jsx","SelectionActions","hasActions","items","dialogs","isBusy","SelectionContextMenuBody","label","id","getTranslationKey","defaultMessage","isMenuOpen","setIsMenuOpen","useState","useEffect","_jsxs","_Fragment","CursorAnchoredMenu","open"],"mappings":";;;;;;;AAsBA;;;;;;;;;;AAUC,IACM,MAAMA,oBAAAA,GAAuB,CAAC,EACnCC,QAAQ,EACRC,aAAa,EACbC,SAAS,EACTC,OAAO,EACmB,GAAA;IAC1B,MAAM,EAAEC,aAAa,EAAE,GAAGC,OAAAA,EAAAA;AAE1B,IAAA,qBACEC,GAAA,CAACC,gBAAAA,EAAAA;QAAiBL,SAAAA,EAAWA,SAAAA;kBAC1B,CAAC,EAAEM,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,MAAM,EAAE,iBACtCL,GAAA,CAACM,wBAAAA,EAAAA;gBACCJ,UAAAA,EAAYA,UAAAA;gBACZG,MAAAA,EAAQA,MAAAA;gBACRX,QAAAA,EAAUA,QAAAA;gBACVC,aAAAA,EAAeA,aAAAA;AACfY,gBAAAA,KAAAA,EAAOT,aAAAA,CAAc;AACnBU,oBAAAA,EAAAA,EAAIC,iBAAAA,CAAkB,mCAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA,CAAA;gBACAP,KAAAA,EAAOA,KAAAA;gBACPC,OAAAA,EAASA,OAAAA;gBACTP,OAAAA,EAASA;;;AAKnB;AAaA;;;AAGC,IACD,MAAMS,wBAAAA,GAA2B,CAAC,EAChCJ,UAAU,EACVG,MAAM,EACNX,QAAQ,EACRC,aAAa,EACbY,KAAK,EACLJ,KAAK,EACLC,OAAO,EACPP,OAAO,EACuB,GAAA;AAC9B,IAAA,MAAM,CAACc,UAAAA,EAAYC,aAAAA,CAAc,GAAGC,QAAAA,CAAS,IAAA,CAAA;;IAG7CC,SAAAA,CAAU,IAAA;QACR,IAAI,CAACH,UAAAA,IAAc,CAACN,MAAAA,EAAQ;AAC1BR,YAAAA,OAAAA,EAAAA;AACF,QAAA;IACF,CAAA,EAAG;AAACc,QAAAA,UAAAA;AAAYN,QAAAA,MAAAA;AAAQR,QAAAA;AAAQ,KAAA,CAAA;;IAGhCiB,SAAAA,CAAU,IAAA;AACR,QAAA,IAAI,CAACZ,UAAAA,EAAY;AACfL,YAAAA,OAAAA,EAAAA;AACF,QAAA;IACF,CAAA,EAAG;AAACK,QAAAA,UAAAA;AAAYL,QAAAA;AAAQ,KAAA,CAAA;AAExB,IAAA,IAAI,CAACK,UAAAA,EAAY;QACf,OAAO,IAAA;AACT,IAAA;IAEA,qBACEa,IAAA,CAAAC,QAAA,EAAA;;0BACEhB,GAAA,CAACiB,kBAAAA,EAAAA;gBACCvB,QAAAA,EAAUA,QAAAA;gBACVwB,IAAAA,EAAMP,UAAAA;gBACNJ,KAAAA,EAAOA,KAAAA;gBACPZ,aAAAA,EAAeA,aAAAA;AACfE,gBAAAA,OAAAA,EAAS,IAAMe,aAAAA,CAAc,KAAA,CAAA;AAE5BT,gBAAAA,QAAAA,EAAAA;;AAEFC,YAAAA;;;AAGP,CAAA;;;;"}
|
|
@@ -28,6 +28,14 @@ const AssetSelectionProvider = ({ children, disabled = false })=>{
|
|
|
28
28
|
}, [
|
|
29
29
|
disabled
|
|
30
30
|
]);
|
|
31
|
+
const selectOnly = React.useCallback((key)=>{
|
|
32
|
+
if (disabled) return;
|
|
33
|
+
setState(selection.selectAll([
|
|
34
|
+
key
|
|
35
|
+
]));
|
|
36
|
+
}, [
|
|
37
|
+
disabled
|
|
38
|
+
]);
|
|
31
39
|
// No `disabled` guard, like `clear`: this only ever shrinks the selection, and a
|
|
32
40
|
// disabled provider has nothing in it (every additive path is guarded).
|
|
33
41
|
const deselect = React.useCallback((key)=>setState((prev)=>selection.deselect(prev, key)), []);
|
|
@@ -47,6 +55,7 @@ const AssetSelectionProvider = ({ children, disabled = false })=>{
|
|
|
47
55
|
toggle,
|
|
48
56
|
selectRange,
|
|
49
57
|
selectAll,
|
|
58
|
+
selectOnly,
|
|
50
59
|
deselect,
|
|
51
60
|
clear
|
|
52
61
|
}), [
|
|
@@ -58,6 +67,7 @@ const AssetSelectionProvider = ({ children, disabled = false })=>{
|
|
|
58
67
|
toggle,
|
|
59
68
|
selectRange,
|
|
60
69
|
selectAll,
|
|
70
|
+
selectOnly,
|
|
61
71
|
deselect,
|
|
62
72
|
clear
|
|
63
73
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAssetSelection.js","sources":["../../../../../admin/src/pages/Assets/hooks/useAssetSelection.ts"],"sourcesContent":["import {\n createContext,\n createElement,\n useCallback,\n useContext,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\n\nimport {\n clearSelection,\n createEmptySelection,\n deselect as deselectState,\n getIdsOfKind,\n selectAll as selectAllState,\n selectRange as selectRangeState,\n toggleSelection,\n type ItemKey,\n type SelectionState,\n} from '../utils/selection';\n\n/**\n * Ephemeral, view-state-only multi-select for the future Media Library. Selection\n * is page-scoped and reset on folder navigation or sort/filter changes (the\n * table/grid toggle keeps it — same list, different presentation).\n *\n * The actual computation lives in `../utils/selection.ts` (pure + unit-tested);\n * this hook is a thin React wrapper. Assets and folders share one mechanism —\n * items are tracked as namespaced keys (`asset:1` / `folder:1`) so toggle, range\n * and select-all behave the same for both. Range / select-all receive the\n * ordered key array from the caller (the view owns the rendered order), keeping\n * the context data-source-agnostic.\n */\nexport interface AssetSelection {\n /** Every selected item key (assets and folders), render-order agnostic. */\n selectedKeys: Set<ItemKey>;\n /** Selected asset ids, derived from {@link selectedKeys}. */\n selectedIds: Set<number>;\n /** Selected folder ids, derived from {@link selectedKeys}. */\n selectedFolderIds: Set<number>;\n anchorKey: ItemKey | null;\n isSelected: (key: ItemKey) => boolean;\n /** Additive toggle (Cmd/Ctrl+click, item checkbox). */\n toggle: (key: ItemKey) => void;\n /** Shift+click — selects the contiguous range from the anchor to the target. */\n selectRange: (orderedKeys: ItemKey[], targetKey: ItemKey) => void;\n /** Header checkbox — selects every rendered item (folders and assets). */\n selectAll: (orderedKeys: ItemKey[]) => void;\n /** Row-level \"...\" menu — drops one key when its item leaves the list (move, delete). */\n deselect: (key: ItemKey) => void;\n /** Close button / folder navigation / list-identity changes. */\n clear: () => void;\n}\n\nconst AssetSelectionContext = createContext<AssetSelection | null>(null);\n\ninterface AssetSelectionProviderProps {\n children: ReactNode;\n /**\n * When true, selection is inert: every mutator is a no-op and nothing ever\n * reads as selected. Every bulk action is gated on `assets.update`, so a user\n * without it has nothing to select for — the views also hide the checkboxes,\n * and this makes the remaining paths (click-to-select, Space key) do nothing.\n */\n disabled?: boolean;\n}\n\nexport const AssetSelectionProvider = ({\n children,\n disabled = false,\n}: AssetSelectionProviderProps) => {\n const [state, setState] = useState<SelectionState>(createEmptySelection);\n\n const isSelected = useCallback(\n (key: ItemKey) => !disabled && state.selectedKeys.has(key),\n [disabled, state.selectedKeys]\n );\n\n const toggle = useCallback(\n (key: ItemKey) => {\n if (disabled) return;\n setState((prev) => toggleSelection(prev, key));\n },\n [disabled]\n );\n\n const selectRange = useCallback(\n (orderedKeys: ItemKey[], targetKey: ItemKey) => {\n if (disabled) return;\n setState((prev) => selectRangeState(prev, orderedKeys, targetKey));\n },\n [disabled]\n );\n\n const selectAll = useCallback(\n (orderedKeys: ItemKey[]) => {\n if (disabled) return;\n setState(selectAllState(orderedKeys));\n },\n [disabled]\n );\n\n // No `disabled` guard, like `clear`: this only ever shrinks the selection, and a\n // disabled provider has nothing in it (every additive path is guarded).\n const deselect = useCallback((key: ItemKey) => setState((prev) => deselectState(prev, key)), []);\n\n const clear = useCallback(() => setState(clearSelection()), []);\n\n const selectedIds = useMemo(\n () => getIdsOfKind(state.selectedKeys, 'asset'),\n [state.selectedKeys]\n );\n const selectedFolderIds = useMemo(\n () => getIdsOfKind(state.selectedKeys, 'folder'),\n [state.selectedKeys]\n );\n\n const value = useMemo<AssetSelection>(\n () => ({\n selectedKeys: state.selectedKeys,\n selectedIds,\n selectedFolderIds,\n anchorKey: state.anchorKey,\n isSelected,\n toggle,\n selectRange,\n selectAll,\n deselect,\n clear,\n }),\n [\n state.selectedKeys,\n selectedIds,\n selectedFolderIds,\n state.anchorKey,\n isSelected,\n toggle,\n selectRange,\n selectAll,\n deselect,\n clear,\n ]\n );\n\n return createElement(AssetSelectionContext.Provider, { value }, children);\n};\n\nexport const useAssetSelection = () => {\n const context = useContext(AssetSelectionContext);\n\n if (!context) {\n throw new Error('useAssetSelection must be used within an AssetSelectionProvider');\n }\n\n return context;\n};\n\nexport const useAssetSelectionOptional = () => useContext(AssetSelectionContext);\n"],"names":["AssetSelectionContext","createContext","AssetSelectionProvider","children","disabled","state","setState","useState","createEmptySelection","isSelected","useCallback","key","selectedKeys","has","toggle","prev","toggleSelection","selectRange","orderedKeys","targetKey","selectRangeState","selectAll","selectAllState","deselect","deselectState","clear","clearSelection","selectedIds","useMemo","getIdsOfKind","selectedFolderIds","value","anchorKey","createElement","Provider","useAssetSelection","context","useContext","Error","useAssetSelectionOptional"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"useAssetSelection.js","sources":["../../../../../admin/src/pages/Assets/hooks/useAssetSelection.ts"],"sourcesContent":["import {\n createContext,\n createElement,\n useCallback,\n useContext,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\n\nimport {\n clearSelection,\n createEmptySelection,\n deselect as deselectState,\n getIdsOfKind,\n selectAll as selectAllState,\n selectRange as selectRangeState,\n toggleSelection,\n type ItemKey,\n type SelectionState,\n} from '../utils/selection';\n\n/**\n * Ephemeral, view-state-only multi-select for the future Media Library. Selection\n * is page-scoped and reset on folder navigation or sort/filter changes (the\n * table/grid toggle keeps it — same list, different presentation).\n *\n * The actual computation lives in `../utils/selection.ts` (pure + unit-tested);\n * this hook is a thin React wrapper. Assets and folders share one mechanism —\n * items are tracked as namespaced keys (`asset:1` / `folder:1`) so toggle, range\n * and select-all behave the same for both. Range / select-all receive the\n * ordered key array from the caller (the view owns the rendered order), keeping\n * the context data-source-agnostic.\n */\nexport interface AssetSelection {\n /** Every selected item key (assets and folders), render-order agnostic. */\n selectedKeys: Set<ItemKey>;\n /** Selected asset ids, derived from {@link selectedKeys}. */\n selectedIds: Set<number>;\n /** Selected folder ids, derived from {@link selectedKeys}. */\n selectedFolderIds: Set<number>;\n anchorKey: ItemKey | null;\n isSelected: (key: ItemKey) => boolean;\n /** Additive toggle (Cmd/Ctrl+click, item checkbox). */\n toggle: (key: ItemKey) => void;\n /** Shift+click — selects the contiguous range from the anchor to the target. */\n selectRange: (orderedKeys: ItemKey[], targetKey: ItemKey) => void;\n /** Header checkbox — selects every rendered item (folders and assets). */\n selectAll: (orderedKeys: ItemKey[]) => void;\n /**\n * Replaces the selection with this one key, as a right-click on an unselected\n * item does: what the menu then acts on is what the user can see is selected.\n */\n selectOnly: (key: ItemKey) => void;\n /** Row-level \"...\" menu — drops one key when its item leaves the list (move, delete). */\n deselect: (key: ItemKey) => void;\n /** Close button / folder navigation / list-identity changes. */\n clear: () => void;\n}\n\nconst AssetSelectionContext = createContext<AssetSelection | null>(null);\n\ninterface AssetSelectionProviderProps {\n children: ReactNode;\n /**\n * When true, selection is inert: every mutator is a no-op and nothing ever\n * reads as selected. Every bulk action is gated on `assets.update`, so a user\n * without it has nothing to select for — the views also hide the checkboxes,\n * and this makes the remaining paths (click-to-select, Space key) do nothing.\n */\n disabled?: boolean;\n}\n\nexport const AssetSelectionProvider = ({\n children,\n disabled = false,\n}: AssetSelectionProviderProps) => {\n const [state, setState] = useState<SelectionState>(createEmptySelection);\n\n const isSelected = useCallback(\n (key: ItemKey) => !disabled && state.selectedKeys.has(key),\n [disabled, state.selectedKeys]\n );\n\n const toggle = useCallback(\n (key: ItemKey) => {\n if (disabled) return;\n setState((prev) => toggleSelection(prev, key));\n },\n [disabled]\n );\n\n const selectRange = useCallback(\n (orderedKeys: ItemKey[], targetKey: ItemKey) => {\n if (disabled) return;\n setState((prev) => selectRangeState(prev, orderedKeys, targetKey));\n },\n [disabled]\n );\n\n const selectAll = useCallback(\n (orderedKeys: ItemKey[]) => {\n if (disabled) return;\n setState(selectAllState(orderedKeys));\n },\n [disabled]\n );\n\n const selectOnly = useCallback(\n (key: ItemKey) => {\n if (disabled) return;\n setState(selectAllState([key]));\n },\n [disabled]\n );\n\n // No `disabled` guard, like `clear`: this only ever shrinks the selection, and a\n // disabled provider has nothing in it (every additive path is guarded).\n const deselect = useCallback((key: ItemKey) => setState((prev) => deselectState(prev, key)), []);\n\n const clear = useCallback(() => setState(clearSelection()), []);\n\n const selectedIds = useMemo(\n () => getIdsOfKind(state.selectedKeys, 'asset'),\n [state.selectedKeys]\n );\n const selectedFolderIds = useMemo(\n () => getIdsOfKind(state.selectedKeys, 'folder'),\n [state.selectedKeys]\n );\n\n const value = useMemo<AssetSelection>(\n () => ({\n selectedKeys: state.selectedKeys,\n selectedIds,\n selectedFolderIds,\n anchorKey: state.anchorKey,\n isSelected,\n toggle,\n selectRange,\n selectAll,\n selectOnly,\n deselect,\n clear,\n }),\n [\n state.selectedKeys,\n selectedIds,\n selectedFolderIds,\n state.anchorKey,\n isSelected,\n toggle,\n selectRange,\n selectAll,\n selectOnly,\n deselect,\n clear,\n ]\n );\n\n return createElement(AssetSelectionContext.Provider, { value }, children);\n};\n\nexport const useAssetSelection = () => {\n const context = useContext(AssetSelectionContext);\n\n if (!context) {\n throw new Error('useAssetSelection must be used within an AssetSelectionProvider');\n }\n\n return context;\n};\n\nexport const useAssetSelectionOptional = () => useContext(AssetSelectionContext);\n"],"names":["AssetSelectionContext","createContext","AssetSelectionProvider","children","disabled","state","setState","useState","createEmptySelection","isSelected","useCallback","key","selectedKeys","has","toggle","prev","toggleSelection","selectRange","orderedKeys","targetKey","selectRangeState","selectAll","selectAllState","selectOnly","deselect","deselectState","clear","clearSelection","selectedIds","useMemo","getIdsOfKind","selectedFolderIds","value","anchorKey","createElement","Provider","useAssetSelection","context","useContext","Error","useAssetSelectionOptional"],"mappings":"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"}
|
|
@@ -26,6 +26,14 @@ const AssetSelectionProvider = ({ children, disabled = false })=>{
|
|
|
26
26
|
}, [
|
|
27
27
|
disabled
|
|
28
28
|
]);
|
|
29
|
+
const selectOnly = useCallback((key)=>{
|
|
30
|
+
if (disabled) return;
|
|
31
|
+
setState(selectAll([
|
|
32
|
+
key
|
|
33
|
+
]));
|
|
34
|
+
}, [
|
|
35
|
+
disabled
|
|
36
|
+
]);
|
|
29
37
|
// No `disabled` guard, like `clear`: this only ever shrinks the selection, and a
|
|
30
38
|
// disabled provider has nothing in it (every additive path is guarded).
|
|
31
39
|
const deselect$1 = useCallback((key)=>setState((prev)=>deselect(prev, key)), []);
|
|
@@ -45,6 +53,7 @@ const AssetSelectionProvider = ({ children, disabled = false })=>{
|
|
|
45
53
|
toggle,
|
|
46
54
|
selectRange: selectRange$1,
|
|
47
55
|
selectAll: selectAll$1,
|
|
56
|
+
selectOnly,
|
|
48
57
|
deselect: deselect$1,
|
|
49
58
|
clear
|
|
50
59
|
}), [
|
|
@@ -56,6 +65,7 @@ const AssetSelectionProvider = ({ children, disabled = false })=>{
|
|
|
56
65
|
toggle,
|
|
57
66
|
selectRange$1,
|
|
58
67
|
selectAll$1,
|
|
68
|
+
selectOnly,
|
|
59
69
|
deselect$1,
|
|
60
70
|
clear
|
|
61
71
|
]);
|