From 09a1dcf8baefd03cdc94bfa319a6ac44a518f077 Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Thu, 15 Oct 2020 11:18:32 +0300 Subject: [PATCH] deleting and uploading photos --- frontend/package-lock.json | 11 ++ frontend/package.json | 6 +- frontend/src/AppToaster.tsx | 40 ++++++ frontend/src/Photos/Overview.tsx | 5 + frontend/src/Photos/PhotoCard.tsx | 81 ++++++++++-- frontend/src/Photos/Photos.scss | 7 ++ frontend/src/Photos/UploadButton.tsx | 54 ++++++++ frontend/src/redux/api/auth/index.ts | 8 +- frontend/src/redux/api/photos.ts | 32 ++++- frontend/src/redux/api/user/index.ts | 4 +- frontend/src/redux/api/utils.ts | 28 +++-- frontend/src/redux/photos/actions.ts | 137 ++++++++++++++++++++- frontend/src/redux/photos/reducer.ts | 75 ++++++++++++ frontend/src/redux/photos/sagas.ts | 177 ++++++++++++++++++++++++++- insomnia.json | 2 +- src/routes/photos.ts | 14 +++ 16 files changed, 648 insertions(+), 33 deletions(-) create mode 100644 frontend/src/Photos/UploadButton.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5ed5dcc..925770e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2144,6 +2144,12 @@ "@types/node": "*" } }, + "@types/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-82E/lVRaqelV9qmRzzJ1PKTpyrpnT7mwdneKNJB9hUtypZDMggloDfFUCIqRRx3lYRxteCwXSq9c+W71Vf0QnQ==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", @@ -12074,6 +12080,11 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "spark-md5": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.1.tgz", + "integrity": "sha512-0tF3AGSD1ppQeuffsLDIOWlKUd3lS92tFxcsrh5Pe3ZphhnoK+oXIBTzOAThZCiuINZLvpiLH/1VS1/ANEJVig==" + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 203a83b..7a5ae70 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,6 +45,7 @@ "redux-persist": "^6.0.0", "redux-saga": "^1.1.3", "sass": "^1.27.0", + "spark-md5": "^3.0.1", "ts-jest": "^26.4.1", "typescript": "^4.0.3" }, @@ -60,6 +61,7 @@ "@types/react-redux": "^7.1.9", "@types/react-router": "^5.1.8", "@types/react-router-dom": "^5.1.6", - "@types/sass": "^1.16.0" + "@types/sass": "^1.16.0", + "@types/spark-md5": "^3.0.2" } -} \ No newline at end of file +} diff --git a/frontend/src/AppToaster.tsx b/frontend/src/AppToaster.tsx index a040ec0..0a4df19 100644 --- a/frontend/src/AppToaster.tsx +++ b/frontend/src/AppToaster.tsx @@ -1,10 +1,23 @@ import { Position, Toaster } from "@blueprintjs/core"; +import { IPhotoReqJSON } from "~../../src/entity/Photo"; export const AppToaster = Toaster.create({ className: "recipe-toaster", position: Position.TOP, }); +export function showDeletionToast(cancelFn: () => void) { + AppToaster.show({ + message: "Photo deleted!", + intent: "danger", + timeout: 2900, + action: { + text: "Undo", + onClick: cancelFn, + }, + }); +} + export function showPasswordSavedToast(): void { AppToaster.show({ message: "Password saved!", @@ -20,3 +33,30 @@ export function showPasswordNotSavedToast(error: string): void { timeout: 2000, }); } + +export function showPhotoCreateFailToast(f: File, e: string): void { + AppToaster.show({ + message: `Failed to create ${f.name}: ${e}`, + intent: "danger", + timeout: 1000, + }); +} + +export function showPhotoUploadJSONFailToast( + p: IPhotoReqJSON, + e: string, +): void { + AppToaster.show({ + message: `Failed to upload ${p.hash}: ${e}`, + intent: "danger", + timeout: 1000, + }); +} + +export function showPhotoUploadFileFailToast(f: File, e: string): void { + AppToaster.show({ + message: `Failed to upload ${f.name}: ${e}`, + intent: "danger", + timeout: 1000, + }); +} diff --git a/frontend/src/Photos/Overview.tsx b/frontend/src/Photos/Overview.tsx index 322aa17..78b649b 100644 --- a/frontend/src/Photos/Overview.tsx +++ b/frontend/src/Photos/Overview.tsx @@ -7,6 +7,8 @@ import { photosLoadStart } from "~redux/photos/actions"; import { IPhotoReqJSON } from "~../../src/entity/Photo"; import { LoadingStub } from "~LoadingStub"; import { PhotoCard } from "./PhotoCard"; +import { Button } from "@blueprintjs/core"; +import { UploadButton } from "./UploadButton"; export interface IOverviewComponentProps { photos: IPhotoReqJSON[] | null; @@ -33,6 +35,9 @@ export const OverviewComponent: React.FunctionComponent return (
+
+ +
{photos}
); diff --git a/frontend/src/Photos/PhotoCard.tsx b/frontend/src/Photos/PhotoCard.tsx index 8f0823d..caa725e 100644 --- a/frontend/src/Photos/PhotoCard.tsx +++ b/frontend/src/Photos/PhotoCard.tsx @@ -1,16 +1,79 @@ -import { Card } from "@blueprintjs/core"; +import { Card, ContextMenuTarget, Menu, MenuItem } from "@blueprintjs/core"; import * as React from "react"; import { IPhotoReqJSON } from "~../../src/entity/Photo"; import { getPhotoImgPath } from "~redux/api/photos"; +import { showDeletionToast } from "~AppToaster"; +import { Dispatch } from "redux"; +import { photoDeleteCancel, photoDeleteStart } from "~redux/photos/actions"; +import { connect } from "react-redux"; -export interface IPhotoCardProps { +export interface IPhotoCardComponentProps { photo: IPhotoReqJSON; + + deletePhoto: (photo: IPhotoReqJSON) => void; + cancelDelete: (photo: IPhotoReqJSON) => void; } -export const PhotoCard: React.FunctionComponent = (props) => { - return ( - - - - ); -}; +@ContextMenuTarget +export class PhotoCardComponent extends React.PureComponent< + IPhotoCardComponentProps +> { + constructor(props: IPhotoCardComponentProps) { + super(props); + + this.handleDelete = this.handleDelete.bind(this); + //this.handleEdit = this.handleEdit.bind(this); + } + + public handleDelete(): void { + showDeletionToast(() => this.props.cancelDelete(this.props.photo)); + this.props.deletePhoto(this.props.photo); + } + /* + public handleEdit() { + this.props.history.push(`/docs/${this.props.doc.id}/edit`); + } + */ + public render(): JSX.Element { + return ( + + this.props.history.push(`/docs/${this.props.doc.id}`) + } + */ + > + + + ); + } + + public renderContextMenu(): JSX.Element { + return ( + + {/* + + */} + + + ); + } +} + +function mapDispatchToProps(dispatch: Dispatch) { + return { + deletePhoto: (photo: IPhotoReqJSON) => + dispatch(photoDeleteStart(photo)), + cancelDelete: (photo: IPhotoReqJSON) => + dispatch(photoDeleteCancel(photo)), + }; +} + +export const PhotoCard = connect(null, mapDispatchToProps)(PhotoCardComponent); diff --git a/frontend/src/Photos/Photos.scss b/frontend/src/Photos/Photos.scss index c4ae75a..7aa407f 100644 --- a/frontend/src/Photos/Photos.scss +++ b/frontend/src/Photos/Photos.scss @@ -4,6 +4,13 @@ display: flex; flex-direction: column; + #actionbar { + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: center; + } + .list { display: flex; flex-shrink: 0; diff --git a/frontend/src/Photos/UploadButton.tsx b/frontend/src/Photos/UploadButton.tsx new file mode 100644 index 0000000..68f9a20 --- /dev/null +++ b/frontend/src/Photos/UploadButton.tsx @@ -0,0 +1,54 @@ +import { Button } from "@blueprintjs/core"; +import * as React from "react"; +import { connect } from "react-redux"; +import { Dispatch } from "redux"; +import { photosUploadStart } from "~redux/photos/actions"; + +export interface IUploadButtonComponentProps { + startUpload: (files: FileList) => void; +} + +export const UploadButtonComponent: React.FunctionComponent = ( + props, +) => { + const fileInputRef = React.useRef(null); + + const onInputChange = (e: React.ChangeEvent) => { + if (e.target.files) { + props.startUpload(e.target.files); + } + }; + + return ( + <> + +