WIP Photo Editor
This commit is contained in:
@@ -12377,6 +12377,32 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>errors</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>notfound</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>itemtypes</name>
|
||||
<children>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.9.17",
|
||||
"logrocket": "^1.2.0",
|
||||
"markerjs2": "^2.8.1",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"phone": "^2.4.21",
|
||||
"preval.macro": "^5.0.0",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||
//Component Imports
|
||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||
@@ -118,6 +119,13 @@ export function App({ checkUserSession, currentUser, online, setOnline }) {
|
||||
component={TechPageContainer}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute
|
||||
isAuthorized={currentUser.authorized}
|
||||
path="/edit"
|
||||
component={DocumentEditorContainer}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</Suspense>
|
||||
</Switch>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//import "tui-image-editor/dist/tui-image-editor.css";
|
||||
import { Spin } from "antd";
|
||||
import * as markerjs2 from "markerjs2";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
import { GenerateSrcUrl } from "../jobs-documents-gallery/job-documents.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function DocumentEditorComponent({ currentUser, bodyshop, document }) {
|
||||
const imgRef = useRef(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const markerArea = useRef(null);
|
||||
const triggerUpload = async (dataUrl) => {
|
||||
setLoading(true);
|
||||
handleUpload(
|
||||
{
|
||||
filename: `${document.key.split("/").pop()}-${Date.now()}.jpg`,
|
||||
file: await b64toBlob(dataUrl),
|
||||
onSuccess: () => setLoading(false),
|
||||
onError: () => setLoading(false),
|
||||
},
|
||||
{
|
||||
bodyshop: bodyshop,
|
||||
uploaded_by: currentUser.email,
|
||||
jobId: document.jobid,
|
||||
//billId: billId,
|
||||
tagsArray: ["edited"],
|
||||
//callback: callbackAfterUpload,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (imgRef.current !== null) {
|
||||
// create a marker.js MarkerArea
|
||||
markerArea.current = new markerjs2.MarkerArea(imgRef.current);
|
||||
// attach an event handler to assign annotated image back to our image element
|
||||
markerArea.current.addCloseEventListener((closeEvent) => {
|
||||
console.log("Close Event", closeEvent);
|
||||
});
|
||||
|
||||
markerArea.current.addRenderEventListener((dataUrl) => {
|
||||
triggerUpload(dataUrl);
|
||||
});
|
||||
// launch marker.js
|
||||
|
||||
markerArea.current.renderAtNaturalSize = true;
|
||||
markerArea.current.renderImageType = "image/jpeg";
|
||||
markerArea.current.renderImageQuality = 1;
|
||||
//markerArea.current.settings.displayMode = "inline";
|
||||
markerArea.current.show();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [imgRef.current, triggerUpload]);
|
||||
|
||||
async function b64toBlob(url) {
|
||||
const res = await fetch(url);
|
||||
return await res.blob();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Spin spinning={loading}>
|
||||
<img
|
||||
ref={imgRef}
|
||||
src={GenerateSrcUrl(document)}
|
||||
alt="sample"
|
||||
crossOrigin="anonymous"
|
||||
style={{ maxWidth: "90vw", maxHeight: "90vh" }}
|
||||
/>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(DocumentEditorComponent);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Modal, Result } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useLocation } from "react-router";
|
||||
import { GET_DOCUMENT_BY_PK } from "../../graphql/documents.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import DocumentEditor from "./document-editor.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function DocumentEditorContainer() {
|
||||
//Get the image details for the image to be saved.
|
||||
//Get the document id from the search string.
|
||||
const { documentId } = queryString.parse(useLocation().search);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { loading, error, data } = useQuery(GET_DOCUMENT_BY_PK, {
|
||||
variables: { documentId },
|
||||
skip: !documentId,
|
||||
});
|
||||
|
||||
if (loading) return <LoadingSpinner />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
if (!data.documents_by_pk)
|
||||
return <Result status="404" title={t("general.errors.notfound")} />;
|
||||
return (
|
||||
<div>
|
||||
<DocumentEditor document={data ? data.documents_by_pk : null} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -21,8 +21,10 @@ export const handleUpload = (ev, context) => {
|
||||
const { onError, onSuccess, onProgress } = ev;
|
||||
const { bodyshop, jobId } = context;
|
||||
|
||||
let key = `${bodyshop.id}/${jobId}/${ev.file.name.replace(/\.[^/.]+$/, "")}`;
|
||||
let extension = ev.file.name.split(".").pop();
|
||||
const fileName = ev.file.name || ev.filename;
|
||||
|
||||
let key = `${bodyshop.id}/${jobId}/${fileName.replace(/\.[^/.]+$/, "")}`;
|
||||
let extension = fileName.split(".").pop();
|
||||
uploadToCloudinary(
|
||||
key,
|
||||
extension,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
||||
|
||||
export const GenerateSrcUrl = (value) => {
|
||||
return `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
|
||||
value.type
|
||||
)}/upload/${value.key}${value.extension ? `.${value.extension}` : ""}`;
|
||||
};
|
||||
|
||||
export const GenerateThumbUrl = (value) =>
|
||||
`${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
|
||||
value.type
|
||||
)}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${
|
||||
value.key
|
||||
}`;
|
||||
@@ -5,6 +5,7 @@ import Gallery from "react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DocumentsUploadComponent from "../documents-upload/documents-upload.component";
|
||||
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
||||
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
||||
import JobsDocumentsDownloadButton from "./jobs-document-gallery.download.component";
|
||||
import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.component";
|
||||
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
|
||||
@@ -29,16 +30,8 @@ function JobsDocumentsComponent({
|
||||
const fileType = DetermineFileType(value.type);
|
||||
if (value.type.startsWith("image")) {
|
||||
acc.images.push({
|
||||
src: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(value.type)}/upload/${value.key}${
|
||||
value.extension ? `.${value.extension}` : ""
|
||||
}`,
|
||||
thumbnail: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(value.type)}/upload/${
|
||||
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
}/${value.key}`,
|
||||
src: GenerateSrcUrl(value),
|
||||
thumbnail: GenerateThumbUrl(value),
|
||||
thumbnailHeight: 225,
|
||||
thumbnailWidth: 225,
|
||||
isSelected: false,
|
||||
@@ -52,28 +45,17 @@ function JobsDocumentsComponent({
|
||||
} else {
|
||||
let thumb;
|
||||
switch (fileType) {
|
||||
case "video":
|
||||
thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`;
|
||||
break;
|
||||
case "raw":
|
||||
thumb = `${window.location.origin}/file.png`;
|
||||
break;
|
||||
default:
|
||||
thumb = `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${fileType}/upload/${
|
||||
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
}/${value.key}${value.extension ? `.${value.extension}` : ""}`;
|
||||
thumb = GenerateThumbUrl(value);
|
||||
break;
|
||||
}
|
||||
|
||||
const fileName = value.key.split("/").pop();
|
||||
acc.other.push({
|
||||
src: `${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${fileType}/upload/${value.key}${
|
||||
value.extension ? `.${value.extension}` : ""
|
||||
}`,
|
||||
src: GenerateSrcUrl(value),
|
||||
thumbnail: thumb,
|
||||
tags: [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const GET_DOCUMENT_BY_PK = gql`
|
||||
query GET_DOCUMENT_BY_PK($documentId: uuid!) {
|
||||
documents_by_pk(id: $documentId) {
|
||||
id
|
||||
name
|
||||
key
|
||||
type
|
||||
size
|
||||
takenat
|
||||
extension
|
||||
jobid
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_DOCUMENTS_BY_JOB = gql`
|
||||
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
|
||||
jobs_by_pk(id: $jobId) {
|
||||
|
||||
@@ -792,6 +792,9 @@
|
||||
"submitticket": "Submit a Support Ticket",
|
||||
"view": "View"
|
||||
},
|
||||
"errors": {
|
||||
"notfound": "No record was found."
|
||||
},
|
||||
"itemtypes": {
|
||||
"contract": "CC Contract",
|
||||
"courtesycar": "Courtesy Car",
|
||||
|
||||
@@ -792,6 +792,9 @@
|
||||
"submitticket": "",
|
||||
"view": ""
|
||||
},
|
||||
"errors": {
|
||||
"notfound": ""
|
||||
},
|
||||
"itemtypes": {
|
||||
"contract": "",
|
||||
"courtesycar": "",
|
||||
|
||||
@@ -792,6 +792,9 @@
|
||||
"submitticket": "",
|
||||
"view": ""
|
||||
},
|
||||
"errors": {
|
||||
"notfound": ""
|
||||
},
|
||||
"itemtypes": {
|
||||
"contract": "",
|
||||
"courtesycar": "",
|
||||
|
||||
@@ -8302,6 +8302,11 @@ map-visit@^1.0.0:
|
||||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
markerjs2@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/markerjs2/-/markerjs2-2.8.1.tgz#33c455cc1edd8fa9a5e5b39ed782dcd1b923c917"
|
||||
integrity sha512-M9AflvjOD5aIcBM0HZWW6u1h/NRdzfq73B9ILv1YehF88PeF0tYT5HIsi9PaSJ6EUOR/vWysZN08f3EyDCJixw==
|
||||
|
||||
material-colors@^1.2.1:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz"
|
||||
|
||||
Reference in New Issue
Block a user