diff --git a/src/components/sidebar-left.tsx b/src/components/sidebar-left.tsx index c1fc956..7a2e4d6 100644 --- a/src/components/sidebar-left.tsx +++ b/src/components/sidebar-left.tsx @@ -7,6 +7,8 @@ import DefibrillatorDetails from "./sidebar/defibrillatorDetails"; import SidebarAction from "../model/sidebarAction"; import DefibrillatorEditor from "./sidebar/defibrillatorEditor"; import { DefibrillatorData } from "../model/defibrillatorData"; +import PhotoReport from "./sidebar/photoReporter"; +import PhotoUpload from "./sidebar/photoUploader"; const SidebarLeft: FC = (props) => { const { @@ -42,6 +44,14 @@ const SidebarLeft: FC = (props) => { data={data} /> ); + case SidebarAction.reportPhoto: + return ( + + ); + case SidebarAction.uploadPhoto: + return ( + + ); default: return null; } diff --git a/src/components/sidebar.css b/src/components/sidebar.css index 265a8aa..5fcbed9 100644 --- a/src/components/sidebar.css +++ b/src/components/sidebar.css @@ -132,3 +132,23 @@ .legend-text { font-size: calc(100% - 1px); } + +.image-gallery-slide { + max-height: 300px; +} + +.image-gallery-custom-icon { + color:#fff; + /* transition:all .3s ease-out; */ + /* appearance:none; */ + background-color:transparent; + /* border:0; */ + cursor:pointer; + /* outline:none; */ + position: absolute; + left: 15px; + bottom: 15px; + /* padding: 4; */ + z-index:4; + filter:drop-shadow(0 2px 2px #1a1a1a) +} diff --git a/src/components/sidebar/access.tsx b/src/components/sidebar/access.tsx index 8bd4aa1..8cbe6c6 100644 --- a/src/components/sidebar/access.tsx +++ b/src/components/sidebar/access.tsx @@ -1,6 +1,22 @@ import { useTranslation } from "react-i18next"; import React from "react"; +const accessToColourMapping = { + yes: "has-background-green has-text-white-ter", + no: "has-background-red has-text-white-ter", + private: "has-background-blue has-text-white-ter", + permissive: "has-background-blue has-text-white-ter", + customers: "has-background-yellow has-text-black-ter", + default: "has-background-gray has-text-white-ter", +}; + +export function accessColourClass(access: string): string { + if (access in accessToColourMapping) { + return accessToColourMapping[access as keyof typeof accessToColourMapping]; + } + return accessToColourMapping.default; +} + export default function AccessFormField({ access, setAccess }: AccessFormFieldProps) { const { t } = useTranslation(); const groupName = "aedAccess"; diff --git a/src/components/sidebar/defibrillatorDetails.tsx b/src/components/sidebar/defibrillatorDetails.tsx index dc35ddf..70d0059 100644 --- a/src/components/sidebar/defibrillatorDetails.tsx +++ b/src/components/sidebar/defibrillatorDetails.tsx @@ -1,6 +1,7 @@ -import React, { FC } from "react"; +import React, { FC, useRef } from "react"; import { useTranslation } from "react-i18next"; import { + Button, Card, Columns, Image, } from "react-bulma-components"; import { @@ -10,6 +11,7 @@ import { import Icon from "@mdi/react"; import ImageGallery, { ReactImageGalleryItem } from "react-image-gallery"; import "react-image-gallery/styles/css/image-gallery.css"; +import SidebarAction from "src/model/sidebarAction"; import { CloseSidebarButton, CopyUrlButton, EditButton, @@ -22,48 +24,71 @@ import { OpeningHoursField } from "./openingHours"; import { CheckDateField } from "./verificationDate"; import { DefibrillatorData } from "../../model/defibrillatorData"; import DetailTextRow from "./detailTextRow"; +import { useAppContext } from "../../appContext"; +import { accessColourClass } from "./access"; +import { backendBaseUrl } from "../../backend"; +import { initialModalState, ModalType } from "../../model/modal"; -const testImages: Array = [ - { - original: "https://picsum.photos/id/1018/1000/600/", - thumbnail: "https://picsum.photos/id/1018/250/150/", - }, - { - original: "https://picsum.photos/id/1015/1000/600/", - thumbnail: "https://picsum.photos/id/1015/250/150/", - }, - { - original: "https://picsum.photos/id/1019/1000/600/", - thumbnail: "https://picsum.photos/id/1019/250/150/", - }, -]; - -const accessToColourMapping = { - yes: "has-background-green has-text-white-ter", - no: "has-background-red has-text-white-ter", - private: "has-background-blue has-text-white-ter", - permissive: "has-background-blue has-text-white-ter", - customers: "has-background-yellow has-text-black-ter", - default: "has-background-gray has-text-white-ter", -}; - -function accessColourClass(access: string): string { - if (access in accessToColourMapping) { - return accessToColourMapping[access as keyof typeof accessToColourMapping]; +function photoGallery(data: DefibrillatorData, closeSidebar: () => void) { + const { t } = useTranslation(); + const { authState: { auth }, setSidebarAction, setModalState } = useAppContext(); + let images: ReactImageGalleryItem[] = []; + // Currently only one photo allowed + if (data.photoRelativeUrl !== undefined && data.photoRelativeUrl !== null) { + images = [ + { + original: backendBaseUrl + data.photoRelativeUrl, + }, + ]; } - return accessToColourMapping.default; -} - -function photoGallery(images: Array) { if (images.length > 0) { + const refImg = useRef(null); + const renderCustomControls = () => ( + + ); + return (
- 1} /> -
+ 1} + renderCustomControls={renderCustomControls} + /> +
); } - return null; + return ( +
+ +
+
+ ); } const DefibrillatorDetails: FC = (props) => { @@ -97,7 +122,7 @@ const DefibrillatorDetails: FC = (props) => { - {photoGallery(testImages)} + {photoGallery(data, closeSidebar)} diff --git a/src/components/sidebar/photoReporter.tsx b/src/components/sidebar/photoReporter.tsx new file mode 100644 index 0000000..8646dd9 --- /dev/null +++ b/src/components/sidebar/photoReporter.tsx @@ -0,0 +1,69 @@ +import React, { FC } from "react"; +import { Button, Card, Image } from "react-bulma-components"; +import { useTranslation } from "react-i18next"; +import { DefibrillatorData } from "src/model/defibrillatorData"; +import SidebarAction from "src/model/sidebarAction"; +import { CloseSidebarButton } from "./buttons"; +import { accessColourClass } from "./access"; +import { useAppContext } from "../../appContext"; +import { backendBaseUrl } from "../../backend"; + +interface DefibrillatorDetailsProps { + data: DefibrillatorData | null, + closeSidebar: () => void, +} + +const PhotoReport: FC = (props) => { + const { t } = useTranslation(); + const { + data, closeSidebar, + } = props; + const { setSidebarAction } = useAppContext(); + if (data === null) return null; + const accessText = data.tags.access ? ` - ${t(`access.${data.tags.access}`)}` : ""; + const sendReport = (photoId: string | undefined) => { + if (photoId === undefined) { + console.error("Photo id is undefined. Report issue to the maintainers."); + return; + } + console.log("Reported photo:", photoId); + fetch(`${backendBaseUrl}/api/v1/photos/report`, { + method: "POST", + body: `id=${encodeURIComponent(photoId)}`, + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", + }, + }) + .then(() => console.log("uploaded")) // todo: add error handling + .catch((error) => console.log(error)); + closeSidebar(); + }; + + return ( + + ); +}; + +export default PhotoReport; diff --git a/src/components/sidebar/photoUploader.tsx b/src/components/sidebar/photoUploader.tsx new file mode 100644 index 0000000..f57b674 --- /dev/null +++ b/src/components/sidebar/photoUploader.tsx @@ -0,0 +1,94 @@ +import React, { FC, useState } from "react"; +import { Button, Card, Image } from "react-bulma-components"; +import { useTranslation } from "react-i18next"; +import { DefibrillatorData } from "src/model/defibrillatorData"; +import SidebarAction from "src/model/sidebarAction"; +import { CloseSidebarButton } from "./buttons"; +import { accessColourClass } from "./access"; +import { useAppContext } from "../../appContext"; +import { backendBaseUrl } from "../../backend"; + +interface DefibrillatorDetailsProps { + data: DefibrillatorData | null, + closeSidebar: () => void, +} + +const PhotoUpload: FC = (props) => { + const { t } = useTranslation(); + const { + data, closeSidebar, + } = props; + const { authState: { auth }, setSidebarAction } = useAppContext(); + const [selectedImage, setSelectedImage] = useState(null); + if (data === null) return null; + const accessText = data.tags.access ? ` - ${t(`access.${data.tags.access}`)}` : ""; + + return ( + + ); +}; + +export default PhotoUpload; diff --git a/src/model/defibrillatorData.ts b/src/model/defibrillatorData.ts index 01a6c5a..b5a5c2b 100644 --- a/src/model/defibrillatorData.ts +++ b/src/model/defibrillatorData.ts @@ -7,5 +7,7 @@ export interface NewDefibrillatorData { export interface DefibrillatorData extends NewDefibrillatorData { osmId: string, osmType: string, + photoId: string | undefined, + photoRelativeUrl: string | undefined, version: string, } \ No newline at end of file diff --git a/src/model/sidebarAction.ts b/src/model/sidebarAction.ts index 2f33e4c..f680820 100644 --- a/src/model/sidebarAction.ts +++ b/src/model/sidebarAction.ts @@ -3,5 +3,7 @@ enum SidebarAction { showDetails, addNode, editNode, + reportPhoto, + uploadPhoto, } export default SidebarAction; \ No newline at end of file diff --git a/src/osm.ts b/src/osm.ts index 95e05b3..02a8f0c 100644 --- a/src/osm.ts +++ b/src/osm.ts @@ -10,6 +10,8 @@ export async function fetchNodeData(url: string): Promise