//
// SPDX-License-Identifier: AGPL-3.0
- import { CommonResourceService } from "../../common/api/common-resource-service";
- import { CollectionResource } from "../../models/collection";
++import * as _ from "lodash";
+ import { CommonResourceService } from "~/common/api/common-resource-service";
+ import { CollectionResource } from "~/models/collection";
import axios, { AxiosInstance } from "axios";
import { KeepService } from "../keep-service/keep-service";
- import { FilterBuilder } from "../../common/api/filter-builder";
- import { CollectionFile, createCollectionFile, createCollectionDirectory, createCollectionFilesTree, CollectionFileType, CollectionDirectory } from "../../models/collection-file";
- import { parseKeepManifestText, stringifyKeepManifest } from "../collection-files-service/collection-manifest-parser";
- import * as _ from "lodash";
- import { KeepManifestStream } from "../../models/keep-manifest";
- import { WebDAV } from "../../common/webdav";
++import { WebDAV } from "~/common/webdav";
+import { AuthService } from "../auth-service/auth-service";
+import { mapTree, getNodeChildren, getNode, TreeNode } from "../../models/tree";
- import { getTagValue } from "../../common/xml";
++import { getTagValue } from "~/common/xml";
+ import { FilterBuilder } from "~/common/api/filter-builder";
-import { CollectionFile, createCollectionFile } from "~/models/collection-file";
++import { CollectionFile, createCollectionFile, CollectionFileType, CollectionDirectory, createCollectionDirectory } from '~/models/collection-file';
+ import { parseKeepManifestText, stringifyKeepManifest } from "../collection-files-service/collection-manifest-parser";
-import * as _ from "lodash";
+ import { KeepManifestStream } from "~/models/keep-manifest";
++import { createCollectionFilesTree } from '~/models/collection-file';
export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
import { unionize, ofType, UnionOf } from "unionize";
import { Dispatch } from "redux";
- import { ResourceKind } from "../../models/resource";
- import { CollectionResource } from "../../models/collection";
- import { collectionPanelFilesAction, loadCollectionFiles } from "./collection-panel-files/collection-panel-files-actions";
- import { createTree } from "../../models/tree";
-import { ResourceKind } from "~/models/resource";
++import { loadCollectionFiles } from "./collection-panel-files/collection-panel-files-actions";
+ import { CollectionResource } from "~/models/collection";
+ import { collectionPanelFilesAction } from "./collection-panel-files/collection-panel-files-actions";
+ import { createTree } from "~/models/tree";
import { RootState } from "../store";
- import { ServiceRepository } from "../../services/services";
- import { TagResource, TagProperty } from "../../models/tag";
+ import { ServiceRepository } from "~/services/services";
+ import { TagResource, TagProperty } from "~/models/tag";
import { snackbarActions } from "../snackbar/snackbar-actions";
export const collectionPanelActions = unionize({
// SPDX-License-Identifier: AGPL-3.0
import { default as unionize, ofType, UnionOf } from "unionize";
-import { CollectionFilesTree } from "~/models/collection-file";
+import { Dispatch } from "redux";
- import { CollectionFilesTree, CollectionFileType } from "../../../models/collection-file";
- import { ServiceRepository } from "../../../services/services";
++import { CollectionFilesTree, CollectionFileType } from "~/models/collection-file";
++import { ServiceRepository } from "~/services/services";
+import { RootState } from "../../store";
+import { snackbarActions } from "../../snackbar/snackbar-actions";
+import { dialogActions } from "../../dialog/dialog-actions";
- import { getNodeValue, getNodeDescendants } from "../../../models/tree";
++import { getNodeValue, getNodeDescendants } from "~/models/tree";
+import { CollectionPanelDirectory, CollectionPanelFile } from "./collection-panel-files-state";
export const collectionPanelFilesAction = unionize({
SET_COLLECTION_FILES: ofType<CollectionFilesTree>(),
}, { tag: 'type', value: 'payload' });
export type CollectionPanelFilesAction = UnionOf<typeof collectionPanelFilesAction>;
- });
+
+export const loadCollectionFiles = (uuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const files = await services.collectionService.files(uuid);
+ dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(files));
+ };
+
+export const removeCollectionFiles = (filePaths: string[]) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const { item } = getState().collectionPanel;
+ if (item) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+ const promises = filePaths.map(filePath => services.collectionService.deleteFile(item.uuid, filePath));
+ await Promise.all(promises);
+ dispatch<any>(loadCollectionFiles(item.uuid));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000 }));
+ }
+ };
+
+export const removeCollectionsSelectedFiles = () =>
+ (dispatch: Dispatch, getState: () => RootState) => {
+ const tree = getState().collectionPanelFiles;
+ const allFiles = getNodeDescendants('')(tree)
+ .map(id => getNodeValue(id)(tree))
+ .filter(file => file !== undefined) as Array<CollectionPanelDirectory | CollectionPanelFile>;
+
+ const selectedDirectories = allFiles.filter(file => file.selected && file.type === CollectionFileType.DIRECTORY);
+ const selectedFiles = allFiles.filter(file => file.selected && !selectedDirectories.some(dir => dir.id === file.path));
+ const paths = [...selectedDirectories, ...selectedFiles].map(file => file.id);
+ dispatch<any>(removeCollectionFiles(paths));
+ };
+
+export const FILE_REMOVE_DIALOG = 'fileRemoveDialog';
+export const openFileRemoveDialog = (filePath: string) =>
+ (dispatch: Dispatch, getState: () => RootState) => {
+ const file = getNodeValue(filePath)(getState().collectionPanelFiles);
+ if (file) {
+ const title = file.type === CollectionFileType.DIRECTORY
+ ? 'Removing directory'
+ : 'Removing file';
+ const text = file.type === CollectionFileType.DIRECTORY
+ ? 'Are you sure you want to remove this directory?'
+ : 'Are you sure you want to remove this file?';
+
+ dispatch(dialogActions.OPEN_DIALOG({
+ id: FILE_REMOVE_DIALOG,
+ data: {
+ title,
+ text,
+ confirmButtonLabel: 'Remove',
+ filePath
+ }
+ }));
+ }
+ };
+
+export const MULTIPLE_FILES_REMOVE_DIALOG = 'multipleFilesRemoveDialog';
+export const openMultipleFilesRemoveDialog = () =>
+ dialogActions.OPEN_DIALOG({
+ id: MULTIPLE_FILES_REMOVE_DIALOG,
+ data: {
+ title: 'Removing files',
+ text: 'Are you sure you want to remove selected files?',
+ confirmButtonLabel: 'Remove'
+ }
++ });
//
// SPDX-License-Identifier: AGPL-3.0
-import { CollectionPanelFilesState, CollectionPanelFile, CollectionPanelDirectory, mapCollectionFileToCollectionPanelFile } from "./collection-panel-files-state";
+import { CollectionPanelFilesState, CollectionPanelFile, CollectionPanelDirectory, mapCollectionFileToCollectionPanelFile, mergeCollectionPanelFilesStates } from "./collection-panel-files-state";
import { CollectionPanelFilesAction, collectionPanelFilesAction } from "./collection-panel-files-actions";
- import { createTree, mapTreeValues, getNode, setNode, getNodeAncestors, getNodeDescendants, setNodeValueWith, mapTree } from "../../../models/tree";
- import { CollectionFileType } from "../../../models/collection-file";
+ import { createTree, mapTreeValues, getNode, setNode, getNodeAncestors, getNodeDescendants, setNodeValueWith, mapTree } from "~/models/tree";
+ import { CollectionFileType } from "~/models/collection-file";
export const collectionPanelFilesReducer = (state: CollectionPanelFilesState = createTree(), action: CollectionPanelFilesAction) => {
return collectionPanelFilesAction.match(action, {
//
// SPDX-License-Identifier: AGPL-3.0
- import { CollectionFile, CollectionDirectory, CollectionFileType } from '../../../models/collection-file';
- import { Tree, TreeNode, getNodeDescendants, TREE_ROOT_ID, mapTreeValues, getNode, getNodeValue } from '../../../models/tree';
++import { Tree, TreeNode, mapTreeValues, getNodeValue } from '~/models/tree';
+ import { CollectionFile, CollectionDirectory, CollectionFileType } from '~/models/collection-file';
-import { Tree, TreeNode } from '~/models/tree';
export type CollectionPanelFilesState = Tree<CollectionPanelDirectory | CollectionPanelFile>;
: { ...node.value, selected: false }
};
};
- };
+
+export const mergeCollectionPanelFilesStates = (oldState: CollectionPanelFilesState, newState: CollectionPanelFilesState) => {
+ return mapTreeValues((value: CollectionPanelDirectory | CollectionPanelFile) => {
+ const oldValue = getNodeValue(value.id)(oldState);
+ return oldValue
+ ? oldValue.type === CollectionFileType.DIRECTORY
+ ? { ...value, collapsed: oldValue.collapsed, selected: oldValue.selected }
+ : { ...value, selected: oldValue.selected }
+ : value;
+ })(newState);
++};
// SPDX-License-Identifier: AGPL-3.0
import { ContextMenuActionSet } from "../context-menu-action-set";
- import { collectionPanelFilesAction, openMultipleFilesRemoveDialog } from "../../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
- import { createCollectionWithSelected } from "../../create-collection-dialog-with-selected/create-collection-dialog-with-selected";
-import { collectionPanelFilesAction } from "~/store/collection-panel/collection-panel-files/collection-panel-files-actions";
-import { openMultipleFilesRemoveDialog } from "~/views-components/file-remove-dialog/multiple-files-remove-dialog";
++import { collectionPanelFilesAction, openMultipleFilesRemoveDialog } from "~/store/collection-panel/collection-panel-files/collection-panel-files-actions";
+ import { createCollectionWithSelected } from "~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected";
export const collectionFilesActionSet: ContextMenuActionSet = [[{
// SPDX-License-Identifier: AGPL-3.0
import { ContextMenuActionSet } from "../context-menu-action-set";
- import { RenameIcon, DownloadIcon, RemoveIcon } from "../../../components/icon/icon";
+ import { RenameIcon, DownloadIcon, RemoveIcon } from "~/components/icon/icon";
import { openRenameFileDialog } from "../../rename-file-dialog/rename-file-dialog";
-import { openFileRemoveDialog } from "../../file-remove-dialog/file-remove-dialog";
+import { DownloadCollectionFileAction } from "../actions/download-collection-file-action";
+import { openFileRemoveDialog } from "../../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
export const collectionFilesItemActionSet: ContextMenuActionSet = [[{
// SPDX-License-Identifier: AGPL-3.0
import * as React from "react";
-import { ListItemIcon, ListItemText } from "@material-ui/core";
+import { ListItemIcon, ListItemText, ListItem } from "@material-ui/core";
- import { AddFavoriteIcon, RemoveFavoriteIcon } from "../../../components/icon/icon";
+ import { AddFavoriteIcon, RemoveFavoriteIcon } from "~/components/icon/icon";
import { connect } from "react-redux";
- import { RootState } from "../../../store/store";
+ import { RootState } from "~/store/store";
-const mapStateToProps = (state: RootState) => ({
- isFavorite: state.contextMenu.resource !== undefined && state.favorites[state.contextMenu.resource.uuid] === true
+const mapStateToProps = (state: RootState, props: { onClick: () => {} }) => ({
+ isFavorite: state.contextMenu.resource !== undefined && state.favorites[state.contextMenu.resource.uuid] === true,
+ onClick: props.onClick
});
-export const ToggleFavoriteAction = connect(mapStateToProps)((props: { isFavorite: boolean }) =>
- <>
+export const ToggleFavoriteAction = connect(mapStateToProps)((props: { isFavorite: boolean, onClick: () => void }) =>
+ <ListItem
+ button
+ onClick={props.onClick}>
<ListItemIcon>
{props.isFavorite
? <RemoveFavoriteIcon />
import { Dispatch } from "redux";
import { connect } from "react-redux";
- import { ConfirmationDialog } from "../../components/confirmation-dialog/confirmation-dialog";
- import { withDialog, WithDialogProps } from "../../store/dialog/with-dialog";
- import { RootState } from "../../store/store";
- import { removeCollectionFiles, FILE_REMOVE_DIALOG } from "../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
+ import { ConfirmationDialog } from "~/components/confirmation-dialog/confirmation-dialog";
-import { withDialog } from "~/store/dialog/with-dialog";
-import { dialogActions } from "~/store/dialog/dialog-actions";
-import { snackbarActions } from "~/store/snackbar/snackbar-actions";
++import { withDialog, WithDialogProps } from '~/store/dialog/with-dialog';
++import { RootState } from '~/store/store';
++import { removeCollectionFiles, FILE_REMOVE_DIALOG } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
-const FILE_REMOVE_DIALOG = 'fileRemoveDialog';
+const mapStateToProps = (state: RootState, props: WithDialogProps<{ filePath: string }>) => ({
+ filePath: props.data.filePath
+});
-const mapDispatchToProps = (dispatch: Dispatch) => ({
- onConfirm: () => {
- // TODO: dispatch action that removes single file
- dispatch(dialogActions.CLOSE_DIALOG({ id: FILE_REMOVE_DIALOG }));
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing file...', hideDuration: 2000 }));
- setTimeout(() => {
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'File removed.', hideDuration: 2000 }));
- }, 1000);
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<{ filePath: string }>) => ({
+ onConfirm: (filePath: string) => {
+ props.closeDialog();
+ dispatch<any>(removeCollectionFiles([filePath]));
}
});
});
export const [FileRemoveDialog] = [ConfirmationDialog]
- .map(withDialog(FILE_REMOVE_DIALOG))
- .map(connect(undefined, mapDispatchToProps));
+ .map(connect(mapStateToProps, mapDispatchToProps, mergeProps))
- .map(withDialog(FILE_REMOVE_DIALOG));
++ .map(withDialog(FILE_REMOVE_DIALOG));
import { Dispatch } from "redux";
import { connect } from "react-redux";
- import { ConfirmationDialog } from "../../components/confirmation-dialog/confirmation-dialog";
- import { withDialog, WithDialogProps } from "../../store/dialog/with-dialog";
+import { MULTIPLE_FILES_REMOVE_DIALOG, removeCollectionsSelectedFiles } from "../../store/collection-panel/collection-panel-files/collection-panel-files-actions";
+ import { ConfirmationDialog } from "~/components/confirmation-dialog/confirmation-dialog";
-import { withDialog } from "~/store/dialog/with-dialog";
-import { dialogActions } from "~/store/dialog/dialog-actions";
-import { snackbarActions } from "~/store/snackbar/snackbar-actions";
++import { withDialog, WithDialogProps } from "~/store/dialog/with-dialog";
-const MULTIPLE_FILES_REMOVE_DIALOG = 'multipleFilesRemoveDialog';
-
-const mapDispatchToProps = (dispatch: Dispatch) => ({
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
onConfirm: () => {
- // TODO: dispatch action that removes multiple files
- dispatch(dialogActions.CLOSE_DIALOG({ id: MULTIPLE_FILES_REMOVE_DIALOG }));
- dispatch(snackbarActions.OPEN_SNACKBAR({message: 'Removing files...', hideDuration: 2000}));
- setTimeout(() => {
- dispatch(snackbarActions.OPEN_SNACKBAR({message: 'Files removed.', hideDuration: 2000}));
- }, 1000);
+ props.closeDialog();
+ dispatch<any>(removeCollectionsSelectedFiles());
}
});
-export const openMultipleFilesRemoveDialog = () =>
- dialogActions.OPEN_DIALOG({
- id: MULTIPLE_FILES_REMOVE_DIALOG,
- data: {
- title: 'Removing files',
- text: 'Are you sure you want to remove selected files?',
- confirmButtonLabel: 'Remove'
- }
- });
-
export const [MultipleFilesRemoveDialog] = [ConfirmationDialog]
- .map(withDialog(MULTIPLE_FILES_REMOVE_DIALOG))
- .map(connect(undefined, mapDispatchToProps));
+ .map(connect(undefined, mapDispatchToProps))
- .map(withDialog(MULTIPLE_FILES_REMOVE_DIALOG));
++ .map(withDialog(MULTIPLE_FILES_REMOVE_DIALOG));