Skip to content

Commit

Permalink
Merge pull request #31 from ramarao9/allow-note-attachment
Browse files Browse the repository at this point in the history
add changes for the UseNoteAttachment property
  • Loading branch information
ramarao9 committed Aug 28, 2022
2 parents dd752ba + ade5bd9 commit eb273cb
Show file tree
Hide file tree
Showing 6 changed files with 3,432 additions and 4,317 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@

# msbuild output directories
/bin
/obj
/obj
.DS_Store
~/Library/Microsoft/PowerAppsCli/usersettings.json
2 changes: 1 addition & 1 deletion AttachmentDragandDrop/Other/Solution.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<!-- Description of Cds Publisher in language code -->
<Description description="https://github.com/ramarao9/AttachmentUploader" languagecode="1033" />
</Descriptions>
<Version>1.0.0.5</Version>
<Version>1.0.1.0</Version>
<!-- Solution Package Type: Unmanaged(0)/Managed(1)/Both(2)-->
<Managed>2</Managed>
<Publisher>
Expand Down
281 changes: 141 additions & 140 deletions AttachmentUpload/AttachmentUploader.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import * as React from "react";
import { IInputs } from "./generated/ManifestTypes"
import {useDropzone} from 'react-dropzone'
import { useDropzone } from 'react-dropzone'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { faSpinner } from '@fortawesome/free-solid-svg-icons'

export interface UploadProps {
id: string;
entityName:string;
entitySetName:string;
controlToRefresh:string|null;
uploadIcon:string;
context: ComponentFramework.Context<IInputs>|undefined;
id: string;
entityName: string;
entitySetName: string;
controlToRefresh: string | null;
uploadIcon: string;
useNoteAttachment: boolean;
context: ComponentFramework.Context<IInputs> | undefined;
}

export interface FileInfo {
Expand All @@ -20,152 +22,151 @@ export interface FileInfo {
}

export const AttachmentUploader: React.FC<UploadProps> = (uploadProps: UploadProps) => {

const [uploadIcn,setuploadIcn]=React.useState(uploadProps.uploadIcon);
const [totalFileCount, setTotalFileCount] = React.useState(0);
const [currentUploadCount, setCurrentUploadCount] = React.useState(0);
const translate = (name:string) => uploadProps.context?.resources.getString(name);

const onDrop = React.useCallback((acceptedFiles:any) => {

if(acceptedFiles && acceptedFiles.length){
setTotalFileCount(acceptedFiles.length);
}


const toBase64 = async (file:any) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onabort = () => reject();
reader.onerror = error => reject(error);
});

const uploadFileToRecord = async (id: string, entity: string,entitySetName:string,
fileInfo: FileInfo,context: ComponentFramework.Context<IInputs>)=>{

let isActivityMimeAttachment = (entity.toLowerCase() === "email" || entity.toLowerCase() === "appointment");
let attachmentRecord: ComponentFramework.WebApi.Entity = {};
if (isActivityMimeAttachment) {
attachmentRecord["objectid_activitypointer@odata.bind"] = `/activitypointers(${id})`;
attachmentRecord["body"] = fileInfo.body;
}
else {
attachmentRecord[`objectid_${entity}@odata.bind`] = `/${entitySetName}(${id})`;
attachmentRecord["documentbody"] = fileInfo.body;
}

if(fileInfo.type && fileInfo.type!==""){
attachmentRecord["mimetype"] =fileInfo.type;
}

attachmentRecord["filename"] = fileInfo.name;
attachmentRecord["objecttypecode"] = entity;
let attachmentEntity = isActivityMimeAttachment ? "activitymimeattachment" : "annotation";
await context.webAPI.createRecord(attachmentEntity, attachmentRecord)
}

const [uploadIcn, setuploadIcn] = React.useState(uploadProps.uploadIcon);
const [totalFileCount, setTotalFileCount] = React.useState(0);
const [currentUploadCount, setCurrentUploadCount] = React.useState(0);
const translate = (name: string) => uploadProps.context?.resources.getString(name);

const uploadFilesToCRM = async (files: any) => {


try{
for(let i=0;i<acceptedFiles.length;i++)
{
setCurrentUploadCount(i);
let file=acceptedFiles[i] as any;
let base64Data=await toBase64(acceptedFiles[i]);
let base64DataStr=base64Data as string;
let base64IndexOfBase64 = base64DataStr.indexOf(';base64,') + ';base64,'.length;
var base64 = base64DataStr.substring(base64IndexOfBase64);
let fileInfo:FileInfo ={name:file.name,type:file.type,body:base64};
let entityId = uploadProps.id;
let entityName = uploadProps.entityName;

if (entityId == null || entityId === "") {//this happens when the record is created and the user tries to upload
let currentPageContext = uploadProps.context as any;
currentPageContext = currentPageContext ? currentPageContext["page"] : undefined;
entityId = currentPageContext.entityId;
entityName = currentPageContext.entityTypeName;
}

await uploadFileToRecord(entityId,entityName,uploadProps.entitySetName, fileInfo,uploadProps.context!!);
}
}
catch(e: any){
let errorMessagePrefix=(acceptedFiles.length===1) ? translate("error_while_uploading_attachment") : translate("error_while_uploading_attachments");
let errOptions={message:`${errorMessagePrefix} ${e.message}`};
uploadProps.context?.navigation.openErrorDialog(errOptions)
const onDrop = React.useCallback((acceptedFiles: any) => {

if (acceptedFiles && acceptedFiles.length) {
setTotalFileCount(acceptedFiles.length);
}


const toBase64 = async (file: any) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onabort = () => reject();
reader.onerror = error => reject(error);
});

const uploadFileToRecord = async (id: string, entity: string, entitySetName: string,
fileInfo: FileInfo, context: ComponentFramework.Context<IInputs>) => {

let isActivityMimeAttachment = !uploadProps.useNoteAttachment && (entity.toLowerCase() === "email" || entity.toLowerCase() === "appointment");
let attachmentRecord: ComponentFramework.WebApi.Entity = {};
if (isActivityMimeAttachment) {
attachmentRecord["objectid_activitypointer@odata.bind"] = `/activitypointers(${id})`;
attachmentRecord["body"] = fileInfo.body;
}
else {
attachmentRecord[`objectid_${entity}@odata.bind`] = `/${entitySetName}(${id})`;
attachmentRecord["documentbody"] = fileInfo.body;
}

if (fileInfo.type && fileInfo.type !== "") {
attachmentRecord["mimetype"] = fileInfo.type;
}

attachmentRecord["filename"] = fileInfo.name;
attachmentRecord["objecttypecode"] = entity;
let attachmentEntity = isActivityMimeAttachment ? "activitymimeattachment" : "annotation";
await context.webAPI.createRecord(attachmentEntity, attachmentRecord)
}


const uploadFilesToCRM = async (files: any) => {


try {
for (let i = 0; i < acceptedFiles.length; i++) {
setCurrentUploadCount(i);
let file = acceptedFiles[i] as any;
let base64Data = await toBase64(acceptedFiles[i]);
let base64DataStr = base64Data as string;
let base64IndexOfBase64 = base64DataStr.indexOf(';base64,') + ';base64,'.length;
var base64 = base64DataStr.substring(base64IndexOfBase64);
let fileInfo: FileInfo = { name: file.name, type: file.type, body: base64 };
let entityId = uploadProps.id;
let entityName = uploadProps.entityName;

if (entityId == null || entityId === "") {//this happens when the record is created and the user tries to upload
let currentPageContext = uploadProps.context as any;
currentPageContext = currentPageContext ? currentPageContext["page"] : undefined;
entityId = currentPageContext.entityId;
entityName = currentPageContext.entityTypeName;
}

setTotalFileCount(0);
let xrmObj: any = (window as any)["Xrm"];
if (xrmObj && xrmObj.Page && uploadProps.controlToRefresh) {
var controlToRefresh = xrmObj.Page.getControl(uploadProps.controlToRefresh);
if (controlToRefresh) {
controlToRefresh.refresh();
}
}
await uploadFileToRecord(entityId, entityName, uploadProps.entitySetName, fileInfo, uploadProps.context!!);
}
}
catch (e: any) {
let errorMessagePrefix = (acceptedFiles.length === 1) ? translate("error_while_uploading_attachment") : translate("error_while_uploading_attachments");
let errOptions = { message: `${errorMessagePrefix} ${e.message}` };
uploadProps.context?.navigation.openErrorDialog(errOptions)
}

setTotalFileCount(0);
let xrmObj: any = (window as any)["Xrm"];
if (xrmObj && xrmObj.Page && uploadProps.controlToRefresh) {
var controlToRefresh = xrmObj.Page.getControl(uploadProps.controlToRefresh);
if (controlToRefresh) {
controlToRefresh.refresh();
}
}
}


uploadFilesToCRM(acceptedFiles);
uploadFilesToCRM(acceptedFiles);




}, [totalFileCount,currentUploadCount])

const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
}, [totalFileCount, currentUploadCount])


if (uploadProps.id==null ||uploadProps.id === "") {
return (
<div className={"defaultContentCont"}>
{translate("save_record_to_enable_content")}
</div>
);
}
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })


if (uploadProps.id == null || uploadProps.id === "") {
return (
<div className={"defaultContentCont"}>
{translate("save_record_to_enable_content")}
</div>
);
}

let fileStats = null;
if (totalFileCount > 0) {
fileStats = (
<div className={"filesStatsCont uploadDivs"}>
<div>
<FontAwesomeIcon icon={faSpinner as IconProp} inverse size="2x" spin />
</div>
<div className={"uploadStatusText"}>
{translate("uploading")} ({currentUploadCount}/{totalFileCount})
</div>
</div>
);
}



return (
<div className={"dragDropCont"}>
<div className={"dropZoneCont uploadDivs"} {...getRootProps()} style={{ backgroundColor: isDragActive ? '#F8F8F8' : 'white' }}>
<input {...getInputProps()} />
<div>
<img className={"uploadImgDD"} src={uploadIcn} alt="Upload" />
</div>
<div>
{
isDragActive ?
<p>{translate("drop_files_here")}</p> :
<p>{translate("drop_files_here_or_click_to_upload")}</p>
}
</div>
</div>

{fileStats}



</div>
)

let fileStats = null;
if (totalFileCount > 0) {
fileStats = (
<div className={"filesStatsCont uploadDivs"}>
<div>
<FontAwesomeIcon icon={faSpinner} inverse size="2x" spin />
</div>
<div className={"uploadStatusText"}>
{translate("uploading")} ({currentUploadCount}/{totalFileCount})
</div>
</div>
);
}



return (
<div className={"dragDropCont"}>
<div className={"dropZoneCont uploadDivs"} {...getRootProps()} style={{ backgroundColor: isDragActive ? '#F8F8F8' : 'white' }}>
<input {...getInputProps()} />
<div>
<img className={"uploadImgDD"} src={uploadIcn} alt="Upload" />
</div>
<div>
{
isDragActive ?
<p>{translate("drop_files_here")}</p> :
<p>{translate("drop_files_here_or_click_to_upload")}</p>
}
</div>
</div>

{ fileStats }



</div>
)



}
10 changes: 9 additions & 1 deletion AttachmentUpload/ControlManifest.Input.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="vij" constructor="AttachmentUpload" version="1.0.12" display-name-key="AttachmentUpload"
<control namespace="vij" constructor="AttachmentUpload" version="1.1.1" display-name-key="AttachmentUpload"
description-key="Upload attachments easily using drag and drop" control-type="standard">
<type-group name="flds">
<type>SingleLine.Text</type>
Expand All @@ -19,6 +19,14 @@
<property name="ControlNameForRefresh" display-name-key="ControlNameForRefresh" description-key="ControlNameForRefresh"
usage="input" of-type="SingleLine.Text"/>

<property name="UseNoteAttachment" display-name-key="UseNoteAttachment" description-key="Upload as attachment on the Notes entity when Email or Appointment instead of Attachment(ActivityMimeAttachment)"
usage="input" of-type="Enum">
<value name="No" display-name-key="No" default="true">0</value>
<value name="Yes" display-name-key="Yes">1</value>
</property>



<resources>
<code path="index.ts" order="1"/>
<css path="AttachmentUploader.css" order="2" />
Expand Down
6 changes: 4 additions & 2 deletions AttachmentUpload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class AttachmentUpload implements ComponentFramework.StandardControl<IInp
entitySetName: "",
controlToRefresh: "",
uploadIcon: "",
useNoteAttachment: false,
context: undefined
};
constructor() {
Expand All @@ -41,7 +42,8 @@ export class AttachmentUpload implements ComponentFramework.StandardControl<IInp
this.uploadProps.context = context;
this.uploadProps.controlToRefresh = context.parameters.ControlNameForRefresh.raw;
this.uploadProps.uploadIcon = this.getImageBase64();
this.uploadProps.context=context;
this.uploadProps.useNoteAttachment = context.parameters.UseNoteAttachment.raw === "1";
this.uploadProps.context = context;
}
this.attachmentUploaderContainer = container;
}
Expand Down Expand Up @@ -77,7 +79,7 @@ export class AttachmentUpload implements ComponentFramework.StandardControl<IInp
this.uploadProps.context = context;

let entityRef = this.getEntityReference(context);
if (entityRef && entityRef.id!=="") {
if (entityRef && entityRef.id !== "") {
this.uploadProps.id = entityRef.id;
}
this.uploadProps.controlToRefresh = context.parameters.ControlNameForRefresh.raw;
Expand Down
Loading

0 comments on commit eb273cb

Please sign in to comment.