import { GroupResource } from 'models/group';
import { UserResource } from 'models/user';
import { extractUuidObjectType, ResourceObjectType } from "models/resource";
+import { CollectionService } from "services/collection-service/collection-service";
+import { CollectionResource } from "models/collection";
export class AncestorService {
constructor(
private groupsService: GroupsService,
- private userService: UserService
+ private userService: UserService,
+ private collectionService: CollectionService,
) { }
- async ancestors(startUuid: string, endUuid: string): Promise<Array<UserResource | GroupResource>> {
+ async ancestors(startUuid: string, endUuid: string): Promise<Array<UserResource | GroupResource | CollectionResource>> {
return this._ancestors(startUuid, endUuid);
}
- private async _ancestors(startUuid: string, endUuid: string, previousUuid = ''): Promise<Array<UserResource | GroupResource>> {
+ private async _ancestors(startUuid: string, endUuid: string, previousUuid = ''): Promise<Array<UserResource | GroupResource | CollectionResource>> {
if (startUuid === previousUuid) {
return [];
return this.groupsService;
case ResourceObjectType.USER:
return this.userService;
+ case ResourceObjectType.COLLECTION:
+ return this.collectionService;
default:
return undefined;
}
const workflowService = new WorkflowService(apiClient, actions);
const linkAccountService = new LinkAccountService(apiClient, actions);
- const ancestorsService = new AncestorService(groupsService, userService);
-
const idleTimeout = (config && config.clusterConfig && config.clusterConfig.Workbench.IdleTimeout) || '0s';
const authService = new AuthService(apiClient, config.rootUrl, actions,
(parse(idleTimeout, 's') || 0) > 0);
const collectionService = new CollectionService(apiClient, webdavClient, authService, actions);
+ const ancestorsService = new AncestorService(groupsService, userService, collectionService);
const favoriteService = new FavoriteService(linkService, groupsService);
const tagService = new TagService(linkService);
const searchService = new SearchService();
const currentCollection = getState().collectionPanel.item;
if (currentCollection) {
const initialData = {
- destination: {uuid: '', destinationPath: ''}
+ destination: {uuid: currentCollection.uuid, destinationPath: ''}
};
dispatch(initialize(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION, initialData));
dispatch<any>(resetPickerProjectTree());
const currentCollection = getState().collectionPanel.item;
if (currentCollection) {
const initialData = {
- destination: {uuid: '', path: ''}
+ destination: {uuid: currentCollection.uuid, path: ''}
};
dispatch(initialize(COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION, initialData));
dispatch<any>(resetPickerProjectTree());
// Get current collection
const sourceCollection = state.collectionPanel.item;
- if (sourceCollection && destination.uuid) {
+ if (sourceCollection && destination && destination.uuid) {
try {
dispatch(startSubmit(COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION));
dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION));
// SPDX-License-Identifier: AGPL-3.0
import { unionize, ofType, UnionOf } from "common/unionize";
-import { TreeNode, initTreeNode, getNodeDescendants, TreeNodeStatus, getNode, TreePickerId, Tree } from 'models/tree';
+import { TreeNode, initTreeNode, getNodeDescendants, TreeNodeStatus, getNode, TreePickerId, Tree, setNode, createTree } from 'models/tree';
import { CollectionFileType, createCollectionFilesTree, getCollectionResourceCollectionUuid } from "models/collection-file";
import { Dispatch } from 'redux';
import { RootState } from 'store/store';
import { mapTreeValues } from "models/tree";
import { sortFilesTree } from "services/collection-service/collection-service-files-response";
import { GroupClass, GroupResource } from "models/group";
+import { CollectionResource } from "models/collection";
export const treePickerActions = unionize({
LOAD_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(),
SET_TREE_PICKER_PROJECT_SEARCH: ofType<{ pickerId: string, projectSearchValue: string }>(),
SET_TREE_PICKER_COLLECTION_FILTER: ofType<{ pickerId: string, collectionFilterValue: string }>(),
SET_TREE_PICKER_LOAD_PARAMS: ofType<{ pickerId: string, params: LoadProjectParams }>(),
+ REFRESH_TREE_PICKER: ofType<{ pickerId: string }>(),
});
export type TreePickerSearchAction = UnionOf<typeof treePickerSearchActions>;
export const getSelectedNodes = <Value>(pickerId: string) => (state: TreePicker) =>
getAllNodes<Value>(pickerId, node => node.selected)(state);
-export const initProjectsTreePicker = (pickerId: string) =>
- async (dispatch: Dispatch, _: () => RootState, services: ServiceRepository) => {
+export const initProjectsTreePicker = (pickerId: string, selectedItemUuid?: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const { home, shared, favorites, publicFavorites, search } = getProjectsTreePickerIds(pickerId);
dispatch<any>(initUserProject(home));
dispatch<any>(initSharedProject(shared));
dispatch<any>(initFavoritesProject(favorites));
dispatch<any>(initPublicFavoritesProject(publicFavorites));
dispatch<any>(initSearchProject(search));
+
+ if (selectedItemUuid) {
+ dispatch<any>(loadInitialValue(selectedItemUuid, pickerId));
+ }
};
interface ReceiveTreePickerDataParams<T> {
}
};
-
+export const HOME_PROJECT_ID = 'Home Projects';
export const initUserProject = (pickerId: string) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const uuid = getUserUuid(getState());
dispatch(receiveTreePickerData({
id: '',
pickerId,
- data: [{ uuid, name: 'Home Projects' }],
+ data: [{ uuid, name: HOME_PROJECT_ID }],
extractNodeData: value => ({
id: value.uuid,
status: TreeNodeStatus.INITIAL,
}));
};
+export const loadInitialValue = (initialValue: string, pickerId: string) =>
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ const { home, shared } = getProjectsTreePickerIds(pickerId);
+ const homeUuid = getUserUuid(getState());
+ const ancestors = (await services.ancestorsService.ancestors(initialValue, ''))
+ .filter(item =>
+ item.kind === ResourceKind.GROUP ||
+ item.kind === ResourceKind.COLLECTION
+ ) as (GroupResource | CollectionResource)[];
+
+ if (ancestors.length) {
+ const isUserHomeProject = !!(homeUuid && ancestors.some(item => item.ownerUuid === homeUuid));
+ const pickerTreeId = isUserHomeProject ? home : shared;
+ const pickerTreeRootUuid: string = (homeUuid && isUserHomeProject) ? homeUuid : SHARED_PROJECT_ID;
+
+ ancestors[0].ownerUuid = '';
+ const tree = createInitialLocationTree(ancestors, initialValue);
+ dispatch(
+ treePickerActions.APPEND_TREE_PICKER_NODE_SUBTREE({
+ id: pickerTreeRootUuid,
+ pickerId: pickerTreeId,
+ subtree: tree
+ }));
+ dispatch(treePickerActions.EXPAND_TREE_PICKER_NODES({ ids: [pickerTreeRootUuid], pickerId: pickerTreeId }));
+ dispatch(treePickerActions.ACTIVATE_TREE_PICKER_NODE({ id: initialValue, pickerId: pickerTreeId }));
+ dispatch(treePickerSearchActions.REFRESH_TREE_PICKER({ pickerId: pickerTreeId }));
+ }
+
+ }
+
export const FAVORITES_PROJECT_ID = 'Favorites';
export const initFavoritesProject = (pickerId: string) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
return undefined;
}
};
+
+/**
+ * Create an expanded tree picker subtree from array of nested projects/collection
+ * Assumes the root item of the subtree already has an empty string ownerUuid
+ */
+export const createInitialLocationTree = (data: Array<GroupResource | CollectionResource>, tailUuid: string) => {
+ return data
+ .reduce((tree, item) => setNode({
+ children: [],
+ id: item.uuid,
+ parent: item.ownerUuid,
+ value: item,
+ active: false,
+ selected: false,
+ expanded: item.uuid !== tailUuid,
+ status: item.uuid !== tailUuid ? TreeNodeStatus.LOADED : TreeNodeStatus.INITIAL,
+ })(tree), createTree<GroupResource | CollectionResource>());
+};
//
// SPDX-License-Identifier: AGPL-3.0
-import { Dispatch } from 'redux';
+import { Dispatch, MiddlewareAPI } from 'redux';
import { RootState } from 'store/store';
import { ServiceRepository } from 'services/services';
import { Middleware } from "redux";
isSearchAction = true;
searchChanged = store.getState().treePickerSearch.collectionFilterValues[pickerId] !== collectionFilterValue;
},
+
+ REFRESH_TREE_PICKER: refreshPickers(store),
default: () => { }
});
}
}),
- SET_TREE_PICKER_COLLECTION_FILTER: ({ pickerId }) =>
- store.dispatch<any>((dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const picker = getTreePicker<ProjectsTreePickerItem>(pickerId)(getState().treePicker);
- if (picker) {
- const loadParams = getState().treePickerSearch.loadProjectParams[pickerId];
- getNodeDescendantsIds('')(picker)
- .map(id => {
- const node = getNode(id)(picker);
- if (node && node.status !== TreeNodeStatus.INITIAL) {
- if (node.id.substring(6, 11) === 'tpzed' || node.id.substring(6, 11) === 'j7d0g') {
- dispatch<any>(loadProject({
- ...loadParams,
- id: node.id,
- pickerId: pickerId,
- }));
- }
- if (node.id === SHARED_PROJECT_ID) {
- dispatch<any>(loadProject({
- ...loadParams,
- id: node.id,
- pickerId: pickerId,
- loadShared: true
- }));
- }
- if (node.id === SEARCH_PROJECT_ID) {
- dispatch<any>(loadProject({
- ...loadParams,
- id: node.id,
- pickerId: pickerId,
- searchProjects: true
- }));
- }
- if (node.id === FAVORITES_PROJECT_ID) {
- dispatch<any>(loadFavoritesProject({
- ...loadParams,
- pickerId: pickerId,
- }));
- }
- if (node.id === PUBLIC_FAVORITES_PROJECT_ID) {
- dispatch<any>(loadPublicFavoritesProject({
- ...loadParams,
- pickerId: pickerId,
- }));
- }
- }
- return id;
- });
- }
- }),
+ SET_TREE_PICKER_COLLECTION_FILTER: refreshPickers(store),
default: () => { }
});
return r;
}
+
+const refreshPickers = (store: MiddlewareAPI) => ({ pickerId }) =>
+ store.dispatch<any>((dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const picker = getTreePicker<ProjectsTreePickerItem>(pickerId)(getState().treePicker);
+ if (picker) {
+ const loadParams = getState().treePickerSearch.loadProjectParams[pickerId];
+ getNodeDescendantsIds('')(picker)
+ .map(id => {
+ const node = getNode(id)(picker);
+ if (node && node.status !== TreeNodeStatus.INITIAL) {
+ if (node.id.substring(6, 11) === 'tpzed' || node.id.substring(6, 11) === 'j7d0g') {
+ dispatch<any>(loadProject({
+ ...loadParams,
+ id: node.id,
+ pickerId: pickerId,
+ }));
+ }
+ if (node.id === SHARED_PROJECT_ID) {
+ dispatch<any>(loadProject({
+ ...loadParams,
+ id: node.id,
+ pickerId: pickerId,
+ loadShared: true
+ }));
+ }
+ if (node.id === SEARCH_PROJECT_ID) {
+ dispatch<any>(loadProject({
+ ...loadParams,
+ id: node.id,
+ pickerId: pickerId,
+ searchProjects: true
+ }));
+ }
+ if (node.id === FAVORITES_PROJECT_ID) {
+ dispatch<any>(loadFavoritesProject({
+ ...loadParams,
+ pickerId: pickerId,
+ }));
+ }
+ if (node.id === PUBLIC_FAVORITES_PROJECT_ID) {
+ dispatch<any>(loadPublicFavoritesProject({
+ ...loadParams,
+ pickerId: pickerId,
+ }));
+ }
+ }
+ return id;
+ });
+ }
+ })
dialogTitle='Copy to existing collection'
formFields={CollectionPartialCopyFields(props.pickerId)}
submitLabel='Copy files'
+ enableWhenPristine
{...props}
/>;
dialogTitle='Move to existing collection'
formFields={CollectionPartialMoveFields(props.pickerId)}
submitLabel='Move files'
+ enableWhenPristine
{...props}
/>;
import { ArvadosTheme } from 'common/custom-theme';
export interface ToplevelPickerProps {
+ currentUuid?: string;
pickerId: string;
includeCollections?: boolean;
includeDirectories?: boolean;
componentDidMount() {
const { home, shared, favorites, publicFavorites, search } = getProjectsTreePickerIds(this.props.pickerId);
- this.props.dispatch<any>(initProjectsTreePicker(this.props.pickerId));
+ this.props.dispatch<any>(initProjectsTreePicker(this.props.pickerId, this.props.currentUuid));
this.props.dispatch(treePickerSearchActions.SET_TREE_PICKER_PROJECT_SEARCH({ pickerId: search, projectSearchValue: "" }));
this.props.dispatch(treePickerSearchActions.SET_TREE_PICKER_COLLECTION_FILTER({ pickerId: search, collectionFilterValue: "" }));
<div style={{ display: 'flex', minHeight: 0, flexDirection: 'column' }}>
<div style={{ flexBasis: '275px', flexShrink: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
<ProjectsTreePicker
+ currentUuid={props.input.value.uuid}
pickerId={props.pickerId}
toggleItemActive={handleDirectoryChange(props)}
options={{ showOnlyOwned: false, showOnlyWritable: true }}