const getParentId = (item: CollectionDirectory | CollectionFile) =>
item.path
- ? join('', [getCollectionId(item.id), item.path])
+ ? join('', [getCollectionResourceCollectionUuid(item.id), item.path])
: item.path;
-const getCollectionId = pipe(
+export const getCollectionResourceCollectionUuid = pipe(
split('/'),
head,
-);
\ No newline at end of file
+);
}
export interface CollectionPartialCopyToExistingCollectionFormData {
- collectionUuid: string;
+ destination: {uuid: string, path?: string};
}
export const openCollectionPartialCopyToNewCollectionDialog = () =>
const currentCollection = getState().collectionPanel.item;
if (currentCollection) {
const initialData = {
- collectionUuid: ''
+ destination: {uuid: '', destinationPath: ''}
};
dispatch(initialize(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION, initialData));
dispatch<any>(resetPickerProjectTree());
}
};
-export const copyCollectionPartialToExistingCollection = ({ collectionUuid }: CollectionPartialCopyToExistingCollectionFormData) =>
+export const copyCollectionPartialToExistingCollection = ({ destination }: CollectionPartialCopyToExistingCollectionFormData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const state = getState();
// Get current collection
const sourceCollection = state.collectionPanel.item;
- if (sourceCollection) {
+ if (sourceCollection && destination.uuid) {
try {
dispatch(startSubmit(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION));
.map(file => file.id.replace(new RegExp(`(^${sourceCollection.uuid})`), ''));
// Copy files
- const updatedCollection = await services.collectionService.copyFiles(sourceCollection.portableDataHash, paths, {uuid: collectionUuid}, '/', false);
+ const updatedCollection = await services.collectionService.copyFiles(sourceCollection.portableDataHash, paths, {uuid: destination.uuid}, destination.path || '/', false);
dispatch(updateResources([updatedCollection]));
dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION }));
// SPDX-License-Identifier: AGPL-3.0
import { Dispatch } from "redux";
-import { initialize, startSubmit } from "redux-form";
+import { FormErrors, initialize, startSubmit, stopSubmit } from "redux-form";
import { CommonResourceServiceError, getCommonResourceServiceError } from "services/common-service/common-resource-service";
import { ServiceRepository } from "services/services";
import { filterCollectionFilesBySelection } from "store/collection-panel/collection-panel-files/collection-panel-files-state";
import { RootState } from "store/store";
import { initProjectsTreePicker } from "store/tree-picker/tree-picker-actions";
-export const COLLECTION_PARTIAL_MOVE_TO_NEW_COLLECTION = 'COLLECTION_PARTIAL_MOVE_TO_NEW_COLLECTION_DIALOG';
-export const COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION = 'COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION_DIALOG';
+export const COLLECTION_PARTIAL_MOVE_TO_NEW_COLLECTION = 'COLLECTION_PARTIAL_MOVE_TO_NEW_DIALOG';
+export const COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION = 'COLLECTION_PARTIAL_MOVE_TO_SELECTED_DIALOG';
export interface CollectionPartialMoveToNewCollectionFormData {
name: string;
}
export interface CollectionPartialMoveToExistingCollectionFormData {
- collectionUuid: string;
+ destination: {uuid: string, path?: string};
}
export const openCollectionPartialMoveToNewCollectionDialog = () =>
const currentCollection = getState().collectionPanel.item;
if (currentCollection) {
const initialData = {
- collectionUuid: ''
+ destination: {uuid: '', path: ''}
};
dispatch(initialize(COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION, initialData));
dispatch<any>(resetPickerProjectTree());
}
};
-export const moveCollectionPartialToExistingCollection = ({ collectionUuid }: CollectionPartialMoveToExistingCollectionFormData) =>
+export const moveCollectionPartialToExistingCollection = ({ destination }: CollectionPartialMoveToExistingCollectionFormData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const state = getState();
// Get current collection
const sourceCollection = state.collectionPanel.item;
- if (sourceCollection) {
+ if (sourceCollection && destination.uuid) {
try {
dispatch(startSubmit(COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION));
dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION));
.map(file => file.id.replace(new RegExp(`(^${sourceCollection.uuid})`), ''));
// Move files
- const updatedCollection = await services.collectionService.moveFiles(sourceCollection.uuid, sourceCollection.portableDataHash, paths, {uuid: collectionUuid}, '/', false);
+ const updatedCollection = await services.collectionService.moveFiles(sourceCollection.uuid, sourceCollection.portableDataHash, paths, {uuid: destination.uuid}, destination.path || '/', false);
dispatch(updateResources([updatedCollection]));
dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_MOVE_TO_SELECTED_COLLECTION }));
import { unionize, ofType, UnionOf } from "common/unionize";
import { TreeNode, initTreeNode, getNodeDescendants, TreeNodeStatus, getNode, TreePickerId, Tree } from 'models/tree';
-import { createCollectionFilesTree } from "models/collection-file";
+import { CollectionFileType, createCollectionFilesTree } from "models/collection-file";
import { Dispatch } from 'redux';
import { RootState } from 'store/store';
import { getUserUuid } from "common/getuser";
export interface LoadProjectParams {
includeCollections?: boolean;
+ includeDirectories?: boolean;
includeFiles?: boolean;
includeFilterGroups?: boolean;
options?: { showOnlyOwned: boolean; showOnlyWritable: boolean; };
export const loadProject = (params: LoadProjectParamsWithId) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const { id, pickerId, includeCollections = false, includeFiles = false, includeFilterGroups = false, loadShared = false, options, searchProjects = false } = params;
+ const {
+ id,
+ pickerId,
+ includeCollections = false,
+ includeDirectories = false,
+ includeFiles = false,
+ includeFilterGroups = false,
+ loadShared = false,
+ options,
+ searchProjects = false
+ } = params;
dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId }));
value: item,
status: item.kind === ResourceKind.PROJECT
? TreeNodeStatus.INITIAL
- : includeFiles
+ : includeDirectories || includeFiles
? TreeNodeStatus.INITIAL
: TreeNodeStatus.LOADED
}),
}));
};
-export const loadCollection = (id: string, pickerId: string) =>
+export const loadCollection = (id: string, pickerId: string, includeDirectories?: boolean, includeFiles?: boolean) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(treePickerActions.LOAD_TREE_PICKER_NODE({ id, pickerId }));
const node = getNode(id)(picker);
if (node && 'kind' in node.value && node.value.kind === ResourceKind.COLLECTION) {
- const files = await services.collectionService.files(node.value.uuid);
+ const files = (await services.collectionService.files(node.value.uuid))
+ .filter((file) => (
+ (includeFiles) ||
+ (includeDirectories && file.type === CollectionFileType.DIRECTORY)
+ ));
const tree = createCollectionFilesTree(files);
const sorted = sortFilesTree(tree);
const filesTree = mapTreeValues(services.collectionService.extendFileURL)(sorted);
}));
}
};
-export const loadUserProject = (pickerId: string, includeCollections = false, includeFiles = false, options?: { showOnlyOwned: boolean, showOnlyWritable: boolean }) =>
+export const loadUserProject = (pickerId: string, includeCollections = false, includeDirectories = false, includeFiles = false, options?: { showOnlyOwned: boolean, showOnlyWritable: boolean }) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const uuid = getUserUuid(getState());
if (uuid) {
- dispatch(loadProject({ id: uuid, pickerId, includeCollections, includeFiles, options }));
+ dispatch(loadProject({ id: uuid, pickerId, includeCollections, includeDirectories, includeFiles, options }));
}
};
interface LoadFavoritesProjectParams {
pickerId: string;
includeCollections?: boolean;
+ includeDirectories?: boolean;
includeFiles?: boolean;
options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
}
export const loadFavoritesProject = (params: LoadFavoritesProjectParams,
options: { showOnlyOwned: boolean, showOnlyWritable: boolean } = { showOnlyOwned: true, showOnlyWritable: false }) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const { pickerId, includeCollections = false, includeFiles = false } = params;
+ const { pickerId, includeCollections = false, includeDirectories = false, includeFiles = false } = params;
const uuid = getUserUuid(getState());
if (uuid) {
const filters = pipe(
value: item,
status: item.kind === ResourceKind.PROJECT
? TreeNodeStatus.INITIAL
- : includeFiles
+ : includeDirectories || includeFiles
? TreeNodeStatus.INITIAL
: TreeNodeStatus.LOADED
}),
export const loadPublicFavoritesProject = (params: LoadFavoritesProjectParams) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const { pickerId, includeCollections = false, includeFiles = false } = params;
+ const { pickerId, includeCollections = false, includeDirectories = false, includeFiles = false } = params;
const uuidPrefix = getState().auth.config.uuidPrefix;
const publicProjectUuid = `${uuidPrefix}-j7d0g-publicfavorites`;
value: item,
status: item.headKind === ResourceKind.PROJECT
? TreeNodeStatus.INITIAL
- : includeFiles
+ : includeDirectories || includeFiles
? TreeNodeStatus.INITIAL
: TreeNodeStatus.LOADED
}),
import { InjectedFormProps } from 'redux-form';
import { CollectionPartialCopyToExistingCollectionFormData } from 'store/collections/collection-partial-copy-actions';
import { PickerIdProp } from "store/tree-picker/picker-id";
-import { CollectionPickerField } from 'views-components/form-fields/collection-form-fields';
+import { DirectoryPickerField } from 'views-components/form-fields/collection-form-fields';
type DialogCollectionPartialCopyProps = WithDialogProps<string> & InjectedFormProps<CollectionPartialCopyToExistingCollectionFormData>;
(pickerId: string) =>
() =>
<>
- <CollectionPickerField {...{ pickerId }}/>
+ <DirectoryPickerField {...{ pickerId }}/>
</>);
import { InjectedFormProps } from 'redux-form';
import { CollectionPartialMoveToExistingCollectionFormData } from "store/collections/collection-partial-move-actions";
import { PickerIdProp } from "store/tree-picker/picker-id";
-import { CollectionPickerField } from 'views-components/form-fields/collection-form-fields';
+import { DirectoryPickerField } from 'views-components/form-fields/collection-form-fields';
type DialogCollectionPartialMoveProps = WithDialogProps<string> & InjectedFormProps<CollectionPartialMoveToExistingCollectionFormData>;
(pickerId: string) =>
() =>
<>
- <CollectionPickerField {...{ pickerId }}/>
+ <DirectoryPickerField {...{ pickerId }}/>
</>);
COLLECTION_NAME_VALIDATION, COLLECTION_NAME_VALIDATION_ALLOW_SLASH,
COLLECTION_DESCRIPTION_VALIDATION, COLLECTION_PROJECT_VALIDATION
} from "validators/validators";
-import { ProjectTreePickerField, CollectionTreePickerField } from "views-components/projects-tree-picker/tree-picker-field";
+import { ProjectTreePickerField, CollectionTreePickerField, DirectoryTreePickerField } from "views-components/projects-tree-picker/tree-picker-field";
import { PickerIdProp } from 'store/tree-picker/picker-id';
import { connect } from "react-redux";
import { RootState } from "store/store";
import { MultiCheckboxField } from "components/checkbox-field/checkbox-field";
import { getStorageClasses } from "common/config";
+import { ERROR_MESSAGE } from "validators/require";
interface CollectionNameFieldProps {
validate: Validator[];
component={CollectionTreePickerField}
validate={COLLECTION_PROJECT_VALIDATION} />;
+const validateDirectory = (val) => (val ? undefined : ERROR_MESSAGE);
+
+export const DirectoryPickerField = (props: PickerIdProp) =>
+ <Field
+ name="destination"
+ pickerId={props.pickerId}
+ component={DirectoryTreePickerField}
+ validate={validateDirectory} />;
+
interface StorageClassesProps {
items: string[];
defaultClasses?: string[];
defaultValues={props.defaultClasses}
helperText='At least one class should be selected'
component={MultiCheckboxField}
- items={props.items} />);
\ No newline at end of file
+ items={props.items} />);
export const FavoritesTreePicker = connect(() => ({
rootItemIcon: FavoriteIcon,
}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
- loadRootItem: (_, pickerId, includeCollections, includeFiles, options) => {
- dispatch<any>(loadFavoritesProject({ pickerId, includeCollections, includeFiles }, options));
+ loadRootItem: (_, pickerId, includeCollections, includeDirectories, includeFiles, options) => {
+ dispatch<any>(loadFavoritesProject({ pickerId, includeCollections, includeDirectories, includeFiles }, options));
},
-}))(ProjectsTreePicker);
\ No newline at end of file
+}))(ProjectsTreePicker);
export interface ProjectsTreePickerDataProps {
includeCollections?: boolean;
+ includeDirectories?: boolean;
includeFiles?: boolean;
rootItemIcon: IconType;
showSelection?: boolean;
disableActivation?: string[];
options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
loadRootItem: (item: TreeItem<ProjectsTreePickerRootItem>, pickerId: string,
- includeCollections?: boolean, includeFiles?: boolean, options?: { showOnlyOwned: boolean, showOnlyWritable: boolean }) => void;
+ includeCollections?: boolean, includeDirectories?: boolean, includeFiles?: boolean, options?: { showOnlyOwned: boolean, showOnlyWritable: boolean }) => void;
}
export type ProjectsTreePickerProps = ProjectsTreePickerDataProps & Partial<PickedTreePickerProps>;
showSelection: isSelectionVisible(showSelection),
});
-const mapDispatchToProps = (dispatch: Dispatch, { loadRootItem, includeCollections, includeFiles, relatedTreePickers, options, ...props }: ProjectsTreePickerProps): PickedTreePickerProps => ({
+const mapDispatchToProps = (dispatch: Dispatch, { loadRootItem, includeCollections, includeDirectories, includeFiles, relatedTreePickers, options, ...props }: ProjectsTreePickerProps): PickedTreePickerProps => ({
onContextMenu: () => { return; },
toggleItemActive: (event, item, pickerId) => {
if ('kind' in data) {
dispatch<any>(
data.kind === ResourceKind.COLLECTION
- ? loadCollection(id, pickerId)
- : loadProject({ id, pickerId, includeCollections, includeFiles, options })
+ ? loadCollection(id, pickerId, includeDirectories, includeFiles)
+ : loadProject({ id, pickerId, includeCollections, includeDirectories, includeFiles, options })
);
} else if (!('type' in data) && loadRootItem) {
- loadRootItem(item as TreeItem<ProjectsTreePickerRootItem>, pickerId, includeCollections, includeFiles, options);
+ loadRootItem(item as TreeItem<ProjectsTreePickerRootItem>, pickerId, includeCollections, includeDirectories, includeFiles, options);
}
} else if (status === TreeItemStatus.LOADED) {
dispatch(treePickerActions.TOGGLE_TREE_PICKER_NODE_COLLAPSE({ id, pickerId }));
export const HomeTreePicker = connect(() => ({
rootItemIcon: ProjectsIcon,
}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
- loadRootItem: (_, pickerId, includeCollections, includeFiles, options) => {
- dispatch<any>(loadUserProject(pickerId, includeCollections, includeFiles, options));
+ loadRootItem: (_, pickerId, includeCollections, includeDirectories, includeFiles, options) => {
+ dispatch<any>(loadUserProject(pickerId, includeCollections, includeDirectories, includeFiles, options));
},
}))(ProjectsTreePicker);
export interface ToplevelPickerProps {
pickerId: string;
includeCollections?: boolean;
+ includeDirectories?: boolean;
includeFiles?: boolean;
showSelection?: boolean;
options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
const { home, shared, favorites, publicFavorites, search } = getProjectsTreePickerIds(props.pickerId);
const params = {
includeCollections: props.includeCollections,
+ includeDirectories: props.includeDirectories,
includeFiles: props.includeFiles,
options: props.options
};
const relatedTreePickers = getRelatedTreePickers(pickerId);
const p = {
includeCollections: this.props.includeCollections,
+ includeDirectories: this.props.includeDirectories,
includeFiles: this.props.includeFiles,
showSelection: this.props.showSelection,
options: this.props.options,
export const PublicFavoritesTreePicker = connect(() => ({
rootItemIcon: PublicFavoriteIcon,
}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
- loadRootItem: (_, pickerId, includeCollections, includeFiles, options) => {
- dispatch<any>(loadPublicFavoritesProject({ pickerId, includeCollections, includeFiles, options }));
+ loadRootItem: (_, pickerId, includeCollections, includeDirectories, includeFiles, options) => {
+ dispatch<any>(loadPublicFavoritesProject({ pickerId, includeCollections, includeDirectories, includeFiles, options }));
},
-}))(ProjectsTreePicker);
\ No newline at end of file
+}))(ProjectsTreePicker);
export const SearchProjectsPicker = connect(() => ({
rootItemIcon: SearchIcon,
}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
- loadRootItem: (_, pickerId, includeCollections, includeFiles, options) => {
- dispatch<any>(loadProject({ id: SEARCH_PROJECT_ID, pickerId, includeCollections, includeFiles, searchProjects: true, options }));
+ loadRootItem: (_, pickerId, includeCollections, includeDirectories, includeFiles, options) => {
+ dispatch<any>(loadProject({ id: SEARCH_PROJECT_ID, pickerId, includeCollections, includeDirectories, includeFiles, searchProjects: true, options }));
},
}))(ProjectsTreePicker);
export const SharedTreePicker = connect(() => ({
rootItemIcon: ShareMeIcon,
}), (dispatch: Dispatch): Pick<ProjectsTreePickerProps, 'loadRootItem'> => ({
- loadRootItem: (_, pickerId, includeCollections, includeFiles, options) => {
- dispatch<any>(loadProject({ id: SHARED_PROJECT_ID, pickerId, includeCollections, includeFiles, loadShared: true, options }));
+ loadRootItem: (_, pickerId, includeCollections, includeDirectories, includeFiles, options) => {
+ dispatch<any>(loadProject({ id: SHARED_PROJECT_ID, pickerId, includeCollections, includeDirectories, includeFiles, loadShared: true, options }));
},
}))(ProjectsTreePicker);
import { ProjectsTreePicker } from 'views-components/projects-tree-picker/projects-tree-picker';
import { ProjectsTreePickerItem } from 'store/tree-picker/tree-picker-middleware';
import { PickerIdProp } from 'store/tree-picker/picker-id';
+import { CollectionFileType, getCollectionResourceCollectionUuid } from "models/collection-file";
+import { ResourceKind } from "models/resource";
export const ProjectTreePickerField = (props: WrappedFieldProps & PickerIdProp) =>
<div style={{ display: 'flex', minHeight: 0, flexDirection: 'column' }}>
</Typography>}
</div>
</div>;
+
+const handleDirectoryChange = (props: WrappedFieldProps) =>
+ (_: any, data: TreeItem<ProjectsTreePickerItem>) => {
+ if ('kind' in data.data && data.data.kind === ResourceKind.COLLECTION) {
+ props.input.onChange({
+ uuid: data.data.uuid,
+ path: '/'
+ });
+ } else if ('type' in data.data && data.data.type === CollectionFileType.DIRECTORY) {
+ props.input.onChange({
+ uuid: getCollectionResourceCollectionUuid(data.data.id),
+ path: [data.data.path, data.data.name].join('/')
+ });
+ } else {
+ props.input.onChange('');
+ }
+ }
+
+export const DirectoryTreePickerField = (props: WrappedFieldProps & PickerIdProp) =>
+ <div style={{ display: 'flex', minHeight: 0, flexDirection: 'column' }}>
+ <div style={{ flexBasis: '275px', flexShrink: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
+ <ProjectsTreePicker
+ pickerId={props.pickerId}
+ toggleItemActive={handleDirectoryChange(props)}
+ options={{ showOnlyOwned: false, showOnlyWritable: true }}
+ includeCollections
+ includeDirectories />
+ {props.meta.dirty && props.meta.error &&
+ <Typography variant='caption' color='error'>
+ {props.meta.error}
+ </Typography>}
+ </div>
+ </div>;
<ProjectsTreePicker
pickerId={this.props.commandInput.id}
includeCollections
+ includeDirectories
includeFiles
showSelection
options={this.props.options}
<ProjectsTreePicker
pickerId={this.props.commandInput.id}
includeCollections
+ includeDirectories
includeFiles
options={this.props.options}
toggleItemActive={this.setFile} />