Issue 36702 asset picker - #36944
Conversation
- Extracted DotFolderListView component and related models/constants/mocks into the new `@dotcms/ui` library. - Updated imports in the Content Drive shell to reference the new location. - Introduced `selectionMode` input to support both single and multiple selection. - Re-exported necessary symbols from `@dotcms/ui` in the Content Drive UI for compatibility. - Added tests for the new component and selection mode functionality. This migration aims to share the folder/asset list component between Content Drive and future AssetPicker implementations, enhancing code reuse and maintainability.
|
Claude finished @nicobytes's task in 3m 30s —— View job PR Review — AssetPicker / Content Drive refactorReviewed the current head ( New Issues
Resolved
Blocking: the two 🔴 (SiteSearchAPI passthrough revert, missing picker providers). The rest are non-blocking but the |
There was a problem hiding this comment.
Pull request overview
This PR refactors the existing Folder List View used by Content Drive into a reusable presentational component in @dotcms/ui, adding a single-selection mode intended for the upcoming AssetPicker (while preserving current Content Drive behavior via the default multiple selection mode).
Changes:
- Moved Folder List View domain-agnostic types/constants into
@dotcms/uiand re-exported them from the Content Drive UI package for compatibility. - Added
selectionMode: 'single' | 'multiple'support to the table (checkboxes in multiple mode, radios in single mode) and normalized emitted selections to an array. - Updated Content Drive shell imports and updated/extended unit tests accordingly.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| core-web/libs/ui/src/lib/components/dot-folder-list-view/models.ts | Introduces shared column typing and selectionMode model for the Folder List View. |
| core-web/libs/ui/src/lib/components/dot-folder-list-view/constants.ts | Defines header column config and drag MIME type local to the component folder. |
| core-web/libs/ui/src/lib/components/dot-folder-list-view/mocks.ts | Moves test mocks alongside the component. |
| core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.ts | Adds selectionMode input, normalizes selection output, and updates internal imports to local UI sources. |
| core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.html | Switches checkbox vs radio rendering based on selectionMode and updates selection binding. |
| core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.scss | Fixes relative SCSS imports to match the libs layout. |
| core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.spec.ts | Updates tests for the new selection model and adds coverage for single-selection behavior. |
| core-web/libs/ui/src/index.ts | Exposes Folder List View component + related models/constants from @dotcms/ui. |
| core-web/libs/portlets/dot-content-drive/ui/src/lib/shared/models.ts | Removes Folder List View column typing now owned by @dotcms/ui. |
| core-web/libs/portlets/dot-content-drive/ui/src/lib/shared/constants.ts | Removes list-view constants now owned by @dotcms/ui. |
| core-web/libs/portlets/dot-content-drive/ui/src/index.ts | Re-exports the Folder List View API surface from @dotcms/ui for Content Drive consumers. |
| core-web/libs/portlets/dot-content-drive/portlet/src/lib/dot-content-drive-shell/dot-content-drive-shell.component.ts | Updates imports to use @dotcms/ui for the presentational list component/types. |
| core-web/libs/portlets/dot-content-drive/portlet/src/lib/dot-content-drive-shell/dot-content-drive-shell.component.spec.ts | Aligns test imports with the updated component export location. |
- Introduced new utility functions for managing folder hierarchies, including `getFolderHierarchyByPath` and `getFolderNodesByPath`, to improve folder navigation and loading in the content drive. - Added `folder-tree-load.utils.ts` and `folder-tree.utils.ts` files to encapsulate the new logic. - Implemented comprehensive unit tests for the new utilities to ensure functionality and reliability. - Updated existing services to utilize the new utilities, enhancing code organization and maintainability. These changes aim to streamline folder management and improve the user experience in the content drive interface.
Extracts the content-type, language, and search filter components (plus the chip-filter/list-item primitives and upload button) out of the content-drive portlet into @dotcms/ui so they can be shared with the AssetPicker. Store- specific logic stays behind thin adapter components in the portlet.
- serve target lacked a dependsOn, so dotcms-webcomponents could be stale or missing when dotcms-ui starts serving - webcomponents build target was missing outputs, preventing Nx from caching/detecting its build artifacts correctly
- Introduces DotAssetPickerStore in @dotcms/ui to power the upcoming AssetPicker dialog with a search request builder mirroring Content Drive's, but with no router/URL coupling so it can run inside a dialog over Edit Contentlet without corrupting host navigation. - Relocates ALL_FOLDER/SYSTEM_HOST_ID out of the Content Drive UI library into shared dot-folder-tree constants so both Content Drive and the new picker consume a single source.
…o issue-36702-asset-picker
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 76 out of 78 changed files in this pull request and generated no new comments.
Suppressed comments (1)
core-web/libs/ui/src/lib/components/dot-asset-picker/store/features/with-asset-folder-tree.feature.ts:97
loadFolderssetsfoldersStatustoERRORincatchError, but then thesubscribeblock unconditionally patches it back toLOADED(becausecatchErrorreturns an empty array). This masks folder-tree failures and makes the UI indistinguishable from a successful empty tree.
The AssetPicker (browse/pick a single asset) now composes the dropzone, upload-type selector, and folder sidebar/toolbar that Content Drive already had, so both features share one implementation instead of duplicating upload flow logic. - Move `dot-content-drive-dropzone` and the upload-type-selector dialog out of the Content Drive portlet into `@dotcms/ui` as `DotUploadDropzoneComponent` and `DotUploadTypeSelectorComponent`, decoupled from `DotContentDriveStore` (folder/drag-state now passed via inputs/outputs) - Add `DotAssetPickerComponent` with sidebar/toolbar subcomponents, wiring the shared dropzone, upload selector, and folder tree to a new `DotAssetPickerStore` - Update Content Drive shell to consume the relocated shared components and derive drag/target-folder state locally
Adds a global last-used-path store, a config builder that translates Edit Content field type (File/Image) into picker filters, and server-side base-type narrowing for the content type filter so restricted hosts don't page through mostly-discarded results.
- Add `--disable-dev-shm-usage` to Chromium launch options: containers default to 64MB /dev/shm, and exhausting it crashes the browser with a SIGSEGV and no test output - Raise Node's old-space heap ceiling via NODE_OPTIONS on the Playwright runner process to prevent "Reached heap limit" aborts (exit 134) - Keep CI at 2 workers rather than dropping to 1, since these two fixes target the actual crash causes directly
…o issue-36702-asset-picker
The preview renders text assets as an editable code block and other types as thumbnail + metadata, so a .txt seed never produced the file name assertion this test relied on. Also assert thumbnail visibility.
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> # Conflicts: # core-web/libs/portlets/dot-content-drive/portlet/src/lib/components/dot-content-drive-sidebar/dot-content-drive-sidebar.component.spec.ts # core-web/libs/portlets/dot-content-drive/portlet/src/lib/components/dot-content-drive-sidebar/dot-content-drive-sidebar.component.ts # core-web/libs/portlets/dot-content-drive/portlet/src/lib/shared/constants.ts # core-web/libs/portlets/dot-content-drive/portlet/src/lib/utils/functions.spec.ts # core-web/libs/portlets/dot-content-drive/portlet/src/lib/utils/functions.ts # core-web/libs/portlets/dot-content-drive/portlet/src/lib/utils/tree-folder.utils.ts # core-web/libs/portlets/dot-content-drive/ui/src/lib/dot-tree-folder/dot-tree-folder.component.spec.ts # core-web/libs/portlets/dot-content-drive/ui/src/lib/dot-tree-folder/dot-tree-folder.component.ts # core-web/libs/portlets/dot-content-drive/ui/src/lib/shared/constants.ts # core-web/libs/portlets/dot-content-drive/ui/src/lib/shared/models.ts # core-web/libs/ui/src/index.ts # core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.html # core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.spec.ts # core-web/libs/ui/src/lib/components/dot-folder-list-view/dot-folder-list-view.component.ts Co-authored-by: nicobytes <7611944+nicobytes@users.noreply.github.com>
Merge conflicts resolved in commit |
- Extract a reusable dot-dialog component (header/content/footer) and migrate the image editor and asset picker to use it, replacing their duplicated header markup with a shared shell and per-feature fullscreen-toggle components - Replace the asset picker's fixed sidebar width with a resizable p-splitter panel so users can adjust the folder tree vs. asset list balance - Consolidate content-drive's folder-tree hierarchy/tree-building utilities out of the shared data-access lib into the portlet, and align the hierarchy page size with the backend's permissions-enabled per-page cap - Fix the asset picker paginator to compute total records from the cursor-based Drive API's hasMoreContent flag instead of a nonexistent grand total - Unsize the folder-list-view title column so it absorbs leftover width instead of overflowing the table when only a subset of columns is shown
Introduces a reusable header/content/footer layout shell (DotDialogComponent and its slot components) so dialogs that render their own chrome can share consistent sizing, padding, and divider styling instead of each rolling its own layout.
The `draggable` attribute was bound as a DOM property, which browsers sometimes ignore mid-render, letting a drag start on read-only rows. Bind it via `[attr.draggable]` and short-circuit `onDragStart` when `$readOnly()` is true so no drag event is ever processed for read-only lists.
- Introduce `buildAssetPickerDialogConfig` in `@dotcms/ui` so every caller (file field, image field, block editor) shares the same PrimeNG dialog contract instead of hand-rolling it, and add `video`/`audio` picker modes. - Repoint the block editor's image/video/audio slash items at the shared `DotAssetPickerComponent` (via `EditorModalService`) instead of the legacy `DotBrowserSelectorComponent`, resolving the current site through `DotSiteService` since the picker requires one. - Make the content-type filter popover size itself to the taller of its two columns (clamped between 7 and 9 rows) instead of a fixed 9-row height, so restricted hosts like the asset picker don't show large dead space.
zJaaal
left a comment
There was a problem hiding this comment.
Review: AssetPicker / Content Drive refactor
Reviewed at 7e1b2ae (136+ files). Focus was bad practices and regressions in Content Drive, plus the shared components the refactor extracted into @dotcms/ui. 11 findings inline, most severe first.
Blocking, in my read:
DotAssetPickerComponentis missing two providers, so it cannot construct in the legacy Dojo binary-field host (and now the Story Block path too).- A merge resolution dropped main's
DotDataExceptionpassthrough inSiteSearchAPIImpl(unrelated to this PR, would land in main). - The folder-tree
publish()nullsselectedNodeafter a sidebar search, so dropped files silently land at the site root.
Content Drive specifically: the biggest issue is tree-folder.utils.spec.ts going from 38 tests to 5 while tree-folder.utils.ts is unchanged and still live. That is not coverage moving with the code: every component that actually moved kept or grew its tests.
Verified clean (flagging these because they look alarming in the diff but check out):
- Removing
#fillWidthis legitimate, not a lost fix:titleis now unsized and absorbs the remainder, so the rescale is genuinely dead code. - The dropzone extraction is faithful, and
resetContextMenu()survives as the newdragEnteroutput. - The upload-button label mapping is preserved exactly,
.toUpperCase()included. - The content-type filter keeps the monotonic cache, the
#cancelFetch$race guard, theensureparam and the chip label fallback; the adapter keeps the same doublepatchFilters/removeFiltercommit shape. - The search input improved: the old placeholder was a hardcoded untranslated
"Search"; it is now thesearchkey, same text, plus value re-sync, trim and a distinct-emission guard. DotContentDriveService->providedIn: 'root'is safe (stateless,HttpClientonly).ALL_FOLDER/SYSTEM_HOST_IDhave a single definition re-exported, so no split identity.- Content Drive's main grid stays uncontrolled and passes no
selectionMode/showActions/titleOpensItem, so it keeps the old defaults. - Spec coverage carried over where things moved: folder-list-view 2417 -> 2584 lines / 183 tests, content-type filter 41 -> 52, dropzone and upload-selector 1:1.
[draggable]->[attr.draggable]is behavior-neutral; shell/sidebar spec changes are symbol renames with assertions intact.
Reviewed by Claude (Claude Code), posted from @zJaaal's account.
| selector: 'dot-asset-picker', | ||
| templateUrl: './dot-asset-picker.component.html', | ||
| changeDetection: ChangeDetectionStrategy.OnPush, | ||
| providers: [DotAssetPickerStore, MessageService], |
There was a problem hiding this comment.
🔴 Missing providers — the picker cannot open in the legacy Dojo binary-field host.
providers covers DotAssetPickerStore and MessageService, but not:
DotHttpErrorManagerService(@Injectable(), noprovidedIn) — injected instore/features/with-asset-browse.feature.tsandwith-asset-folder-tree.feature.tsDotContentTypeService(@Injectable()) — injected bydot-content-type-filter.component.tsviaDotAssetPickerToolbarComponent
Both are only provided in apps/dotcms-ui/src/app/providers.ts. apps/dotcms-binary-field-builder/src/app/app.module.ts — the host that bootstraps dotcms-binary-field — provides neither, and DotFileFieldComponent provides only DialogService.
Repro: legacy content editor, File/Image field with allowExistingFile → click "Select Existing File" → DialogService.open(DotAssetPickerComponent) → DotAssetPickerStore construction throws NullInjectorError: No provider for DotHttpErrorManagerService (then DotContentTypeService), dialog renders blank.
This is a regression: the replaced DotBrowserSelectorComponent used only root-provided services (DotBrowsingService, DotUploadFileService, DotContentletService). dot-file-field.component.legacy-availability.spec.ts only asserts construction, never dialog open, so nothing catches it — and store/dot-asset-picker.store.spec.ts mock-provides DotHttpErrorManagerService, which confirms the dependency is real.
Adding both to this providers array is the smallest fix and makes the picker self-sufficient in any host.
| public Optional<String> defaultIndexName() throws DotDataException { | ||
| try { | ||
| return router.readChecked(SiteSearchAPI::defaultIndexName); | ||
| } catch (DotDataException e) { |
There was a problem hiding this comment.
🔴 Merge resolution dropped main's DotDataException passthrough (unrelated to this PR).
main has } catch (DotDataException e) { throw e; } here, added by ad09f0361c (the #36983 sitesearch fix). It was removed by a merge commit on this branch, and it is still missing after the 7e1b2ae re-merge of main — so merging this PR reverts that fix.
Consequence: any DotDataException from the read provider now falls through to catch (Exception e) and is re-wrapped as a new DotDataException(e.getMessage(), e), losing the original subclass and nesting the cause one level deeper.
isDefaultIndex() three lines below still has the passthrough, so the two are now inconsistent — which is the clearest sign this was accidental. Please restore it.
| patchState(store, { | ||
| folders, | ||
| ...(selectedKey | ||
| ? { selectedNode: findNodeByKey(folders, selectedKey) ?? null } |
There was a problem hiding this comment.
🟠 publish() nulls the tree highlight after a sidebar search, silently retargeting uploads to the site root.
publish() re-points selectedNode by key and sets it to null when findNodeByKey misses. But searchFoldersInBrowsingSite deliberately leaves selectedNode pointing at a node that is no longer in the published tree (documented intent), and path is left untouched. So the next mutateNode → publish wipes the highlight.
Repro: select /images/banners/ in the tree (list scopes to it) → type "logo" in "Search sites & folders" → expand any node or click "Load more". selectedNode becomes null, so $targetFolder is undefined and #uploadByBaseType falls back to hostFolder: browsingSite()?.identifier. A dropped file is created at the site root while the asset list still shows /images/banners/. The folder's pinned defaultBaseType is lost too, so the user is re-prompted for Asset/File.
Either keep the previous node when the lookup misses, or clear path alongside it so the two cannot disagree.
| }, | ||
| // Nothing to browse and nothing to say beyond that — the picker simply doesn't open. | ||
| error: () => { | ||
| /* noop */ |
There was a problem hiding this comment.
🟠 "Select Existing File" is now a completely silent no-op on failure.
Both paths swallow it: next does nothing when site is falsy, and error is an empty /* noop */. No toast, no Logger, no UI message — compare line ~486 in this same file, which surfaces SERVER_ERROR for a comparable failure.
Repro: DotSiteService.getCurrentSite() 500s (or the session has no current site) → the user clicks "Select Existing File" and nothing happens, with no way to tell the click registered. Previously the dialog opened unconditionally, so this is a new dead end.
|
|
||
| const mode = isImage ? 'image' : 'file'; | ||
|
|
||
| this.#dialogRef = this.#dialogService.open( |
There was a problem hiding this comment.
🟠 Async dialog open can stack two pickers.
#openAssetPicker assigns this.#dialogRef without checking whether a dialog is already open or in flight, and the open is now gated behind an HTTP round-trip that widens the race window.
Repro: double-click "Select Existing File" (or click again while getCurrentSite() is pending) → two getCurrentSite() requests → two DotAssetPickerComponent dialogs stacked, each with a live onClose subscription, while only the second is reachable through #dialogRef. The first leaks and can still write a selection. The previous synchronous open made this practically unreachable.
Guard on #dialogRef/an in-flight flag, or exhaustMap the site lookup.
|
|
||
| it('should use a native PrimeNG folder icon', () => { | ||
| expect(ALL_FOLDER.icon).toBe('pi pi-folder'); | ||
| import { ALL_FOLDER } from '@dotcms/ui'; |
There was a problem hiding this comment.
🟠 Content Drive: the sidebar tree util suite was deleted (38 tests → 5), on production code that did not change.
This file no longer imports tree-folder.utils.ts at all — the surviving 5 tests only assert the shape of the ALL_FOLDER constant, which moved to @dotcms/ui.
Deleted: generateAllParentPaths (8 tests), createTreeNode (12), buildTreeFolderNodes (13, including the "rootNode as selectedNode" fallback paths — empty hierarchy, no path match, empty target path).
tree-folder.utils.ts is byte-identical to main and still consumed by store/features/sidebar/withSidebar.ts and utils/functions.ts, so it still drives sidebar tree construction, parent-path expansion and selected-node resolution. That logic is now completely uncovered.
This isn't coverage moving with the code — everything that genuinely moved kept or grew its tests (folder-list-view 2417 → 2584 lines / 183 tests, content-type filter 41 → 52, dropzone 1:1). This file looks like collateral damage from the ALL_FOLDER relocation. Worth restoring the three suites, importing the utils from their current path.
| files: FileList; | ||
| targetFolder: DotFolderTreeNodeData; | ||
| } | ||
| export type DotContentDriveUploadFiles = DotUploadFiles; |
There was a problem hiding this comment.
🟡 Content Drive: move/upload targetFolder lost its type guarantee.
DotContentDriveUploadFiles was { files: FileList; targetFolder: DotFolderTreeNodeData } — targetFolder required and folder-specific. It is now an alias of DotUploadFiles, whose targetFolder is optional and typed TreeNodeData. DotContentDriveMoveItems = Omit<..., 'files'> inherits that.
Meanwhile getMoveMetadata in dot-content-drive-shell.component.ts is unchanged and still reads event.targetFolder.path / .hostname with no guard. core-web/tsconfig.base.json sets "strict": false, so strictNullChecks cannot flag the now-optional property.
TreeNodeLoadMoreData is also assignable to TreeNodeData and carries no inode with optional path/hostname, so a move event built from a load-more node type-checks and yields a pathToMove of //undefined/ that then goes to bulkFire.
To be fair on blame: the null-targetFolder crash predates this PR (dot-tree-folder onDrop already emitted $activeDropNode() unguarded). What this PR removes is the type that documented and enforced the contract. onRequestUpload was updated to narrow on LOAD_MORE_NODE_TYPE; getMoveMetadata was not — worth doing the same there, or keeping a Content-Drive-specific alias that requires folder data.
| * No width bookkeeping is needed when a column drops out: `title` is unsized (see | ||
| * `HEADER_COLUMNS`) and soaks up whatever the remaining sized columns leave over. | ||
| */ | ||
| protected readonly $fixedColumns = computed<DotFolderListViewFixedColumn[]>(() => { |
There was a problem hiding this comment.
🔵 Content Drive: the move stripped the comments that recorded previously-fixed bugs.
The logic came across intact, but the "why" did not. Lost in this file:
- the
$columnsnote explaining that extras anchor to wheretypesits in the canonical order rather than its rendered index — keying off the rendered index appended the extras when Type was hidden, putting every extra cell one heading early - the item-title
titleattribute rationale (fixed-width column,truncateclips the rest) - the
contentTypetruncate rationale - the locked-by-others color rationale
A later cleanup that "simplifies" $columns back to a rendered-index lookup reintroduces the misaligned-extras bug with nothing in the file warning it off. Cheap to carry these across.
| @@ -1,6 +1,18 @@ | |||
| export * from './lib/dot-folder-list-view/dot-folder-list-view.component'; | |||
| // Presentational list lives in @dotcms/ui; re-export for Content Drive consumers. | |||
There was a problem hiding this comment.
🔵 Content Drive: two import paths now exist for the same symbols.
This barrel re-exports DotFolderListViewComponent, HEADER_COLUMNS, DOT_DRAG_ITEM and the column types straight from @dotcms/ui, and Content Drive uses both entry points for the same classes:
dot-content-drive-shell.component.tsimportsDotFolderListViewComponentfrom@dotcms/uidot-content-drive-action-preview.component.tsimports it (plusDotFolderListViewColumnField) from@dotcms/portlets/content-drive/ui
Same runtime class either way, but a future deprecation or signature change has to be chased through two barrels and neither path reads as canonical. Worth picking one and either dropping the re-export or marking it explicitly transitional.
| {{ $localTimezoneLabel() }} | ||
| <a href="#" data-testid="push-publish-timezone-toggle" (click)="toggleTimezone($event)"> | ||
| {{ ($showTimezone() ? 'hide' : 'Change') | dm }} | ||
| {{ ($showTimezone() ? 'hide' : 'change') | dm }} |
There was a problem hiding this comment.
🔵 i18n key switched to one whose value is lowercase (and out of scope for this PR).
'Change' → 'change'. Language.properties has Change=Change (line 590) but change=change (line 589), so this timezone toggle now renders a lowercase "change" while its counterpart still renders "Hide" (hide=Hide). Inconsistent capitalization in the push-publish dialog.
Unrelated to Content Drive / AssetPicker — looks like an incidental edit worth reverting.
Reworks the File field's AssetPicker dialog helper into a shared component usable across Story Block and WYSIWYG fields, adds E2E coverage for both, and updates the WYSIWYG plugin service to open the shared picker (with guarded async site lookup) instead of the legacy dialog.
- Point deleteContentlets at /api/v1/workflow (the old /api/v1/content/actions endpoint doesn't exist and was silently 404ing, leaking seeded content every run) - Drop searchFor calls in favor of relying on modDate:desc ordering now that seeded assets are guaranteed the newest rows - Select the outer textbox in BlockEditorField since ProseMirror nests a second contenteditable textbox inside it - Assert the TinyMCE dotAddImage button's tooltip, since TinyMCE promotes it to aria-label/title and it's the button's only accessible name
This pull request introduces a set of improvements and refactorings to the folder tree utilities and related data-access APIs, aimed at enhancing code reuse, maintainability, and consistency across the Content Drive and Host Folder Field features. The changes include moving folder tree logic into shared utilities, updating service providers, and aligning data models.
Core refactoring and utility extraction:
folder-tree.utils.tsandfolder-tree-load.utils.tsin@dotcms/data-access, centralizing the logic for building, loading, and paginating folder trees. This includes new functions such asgenerateAllParentPaths,createTreeNode, andbuildTreeFolderNodes, as well as utilities for paginated loading and "Load more" node handling. [1] [2]API and provider updates:
DotContentDriveServiceto use Angular'sprovidedIn: 'root'for global availability, and removed it from route-level providers. This ensures the service can be used from dialogs and other contexts without explicit injection in every route. [1] [2]Imports and dependency cleanup:
@dotcms/data-accessinstead of local utility definitions, and cleaned up duplicate or outdated imports for components such asDotFolderListViewComponent. [1] [2] [3] [4]Data model alignment:
TreeNodeContentDatamodel to include additional metadata fields (inode,defaultBaseType,fromTable) for improved folder selection and upload behavior in the Content Drive and AssetPicker.Constants and type re-exports:
@dotcms/data-access. [1] [2] [3] [4]These changes improve code maintainability, reduce duplication, and ensure that folder tree behaviors remain consistent across all consuming features.
This PR fixes: #36702
AssetPicker series — all seven sub-issues are resolved in this PR:
Fixes #36831 — AssetPicker 1/7: Migrate DotFolderListView to @dotcms/ui
Fixes #36832 — AssetPicker 2/7: Lift folder-tree helpers and harden drive data-access
Fixes #36833 — AssetPicker 3/7: Decouple Content Drive toolbar chrome for AssetPicker reuse
Fixes #36834 — AssetPicker 4/7: Headless browse store (no URL) for AssetPicker
Fixes #36835 — AssetPicker 5/7: Build DotAssetPicker shell in @dotcms/ui
Fixes #36836 — AssetPicker 6/7: File/Image entry configs, silent mime, CT limits, global last path
Fixes #36837 — AssetPicker 7/7: Wire AssetPicker into Edit Content File/Image fields