├── solution
├── .gitignore
├── other
│ ├── Relationships.xml
│ ├── Customizations.xml
│ └── Solution.xml
└── solution.cdsproj
├── pcfconfig.json
├── docs
└── PCF-Gallery-Control.gif
├── tsconfig.json
├── obj
├── PCFGalleryControl.pcfproj.nuget.cache
├── PCFGalleryControl.pcfproj.nuget.g.targets
├── PCFGalleryControl.pcfproj.nuget.g.props
└── project.assets.json
├── .gitignore
├── README.md
├── package.json
├── PCFGalleryControl.pcfproj
├── GalleryFieldControl
├── css
│ └── GalleryFieldControl.css
├── ControlManifest.Input.xml
└── index.ts
└── LICENSE
/solution/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /obj
--------------------------------------------------------------------------------
/pcfconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "outDir": "./out/controls"
3 | }
--------------------------------------------------------------------------------
/docs/PCF-Gallery-Control.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DynamicsNinja/PCF-Gallery-Control/HEAD/docs/PCF-Gallery-Control.gif
--------------------------------------------------------------------------------
/solution/other/Relationships.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/pcf-scripts/tsconfig_base.json",
3 | "compilerOptions": {
4 | "typeRoots": ["node_modules/@types"],
5 | }
6 | }
--------------------------------------------------------------------------------
/obj/PCFGalleryControl.pcfproj.nuget.cache:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "dgSpecHash": "cPP26Jr/hyfKSvGYqhp6d+KXCKsdORTIVsOvz0JdCqcWAHajt4Yc/Xq4kgqfNGD5N7a+JL3TjCQLUR44zlVO4Q==",
4 | "success": true
5 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # generated directory
7 | **/generated
8 |
9 | # output directory
10 | /out
--------------------------------------------------------------------------------
/solution/other/Customizations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 1033
16 |
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PCF Gallery Control
2 |
3 | ## Overview
4 |
5 | PCF control that shows thumbnails of images from entity notes. Control is bound to Single Line of Text field.
6 |
7 | ## Download
8 |
9 | [](https://github.com/DynamicsNinja/PCF-Gallery-Control/releases/latest)
10 |
11 | ## Preview
12 |
13 | 
14 |
15 | ## Features
16 |
17 | - Thumbnails of images from notes & email attachments
18 | - Image preview on click on the thumbnail
19 | - Hide preview image on click
20 | - Download image on click on the filename
--------------------------------------------------------------------------------
/obj/PCFGalleryControl.pcfproj.nuget.g.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pcf-project",
3 | "version": "1.0.0",
4 | "description": "Project containing your PowerApps Component Framework (PCF) control.",
5 | "scripts": {
6 | "build": "pcf-scripts build",
7 | "clean": "pcf-scripts clean",
8 | "rebuild": "pcf-scripts rebuild",
9 | "start": "pcf-scripts start"
10 | },
11 | "dependencies": {
12 | "@types/file-saver": "^2.0.1",
13 | "@types/jquery": "^3.3.29",
14 | "@types/node": "^10.12.18",
15 | "@types/powerapps-component-framework": "1.1.0",
16 | "file-saver": "^2.0.2",
17 | "jquery": "^3.4.1"
18 | },
19 | "devDependencies": {
20 | "pcf-scripts": "~0",
21 | "pcf-start": "~0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/PCFGalleryControl.pcfproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | PCFGalleryControl
7 | 2b4792c9-00d9-487b-b13f-8a2b987ba212
8 | $(MSBuildThisFileDirectory)out\controls
9 |
10 |
11 |
12 | PackageReference
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Always
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/GalleryFieldControl/css/GalleryFieldControl.css:
--------------------------------------------------------------------------------
1 | .Fic\.GalleryFieldControl .thumbnail img {
2 | border: 1px solid #ddd;
3 | border-radius: 4px;
4 | padding: 5px;
5 | }
6 |
7 | .Fic\.GalleryFieldControl .thumbnail img:hover {
8 | box-shadow: 0 0 2px 1px rgba(0, 140, 186, 0.5);
9 | cursor: pointer;
10 | }
11 |
12 | .Fic\.GalleryFieldControl .thumbnail-container {
13 | display: inline-block;
14 | margin-left: 5px;
15 | }
16 |
17 | .Fic\.GalleryFieldControl .file-name {
18 | white-space: nowrap;
19 | overflow: hidden;
20 | text-overflow: ellipsis;
21 | text-align: center;
22 | }
23 |
24 | .Fic\.GalleryFieldControl .file-name:hover {
25 | cursor: pointer;
26 | }
27 |
28 | .Fic\.GalleryFieldControl .preview-img {
29 | max-width: 100%;
30 | margin-top: 10px;
31 | }
32 |
33 | .Fic\.GalleryFieldControl .section-header {
34 | margin: 5px 0px 5px 0px;
35 | }
36 |
37 | .Fic\.GalleryFieldControl .files-group {
38 | border-bottom: 1px solid rgb(216, 216, 216);
39 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Ivan Ficko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/GalleryFieldControl/ControlManifest.Input.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/obj/PCFGalleryControl.pcfproj.nuget.g.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | True
5 | NuGet
6 | C:\Users\ivanf\Source\Repos\PCFGalleryControl\obj\project.assets.json
7 | $(UserProfile)\.nuget\packages\
8 | C:\Users\ivanf\.nuget\packages\
9 | PackageReference
10 | 4.9.3
11 |
12 |
13 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/solution/solution.cdsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | solution
7 | c5a41bbf-7b82-419a-984e-abf5560aff77
8 | v4.6.2
9 | PackageReference
10 |
11 |
12 |
13 |
14 | Managed
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Always
30 |
31 |
32 |
33 |
34 |
35 | 2b4792c9-00d9-487b-b13f-8a2b987ba212
36 | PCFGalleryControl
37 | Build
38 | false
39 | Content
40 | Always
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/obj/project.assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "targets": {
4 | ".NETFramework,Version=v4.0": {
5 | "Microsoft.PowerApps.MSBuild.Pcf/0.2.59": {
6 | "type": "package",
7 | "build": {
8 | "build/Microsoft.PowerApps.MSBuild.Pcf.props": {},
9 | "build/Microsoft.PowerApps.MSBuild.Pcf.targets": {}
10 | }
11 | }
12 | },
13 | ".NETFramework,Version=v4.0/win": {
14 | "Microsoft.PowerApps.MSBuild.Pcf/0.2.59": {
15 | "type": "package",
16 | "build": {
17 | "build/Microsoft.PowerApps.MSBuild.Pcf.props": {},
18 | "build/Microsoft.PowerApps.MSBuild.Pcf.targets": {}
19 | }
20 | }
21 | },
22 | ".NETFramework,Version=v4.0/win-x64": {
23 | "Microsoft.PowerApps.MSBuild.Pcf/0.2.59": {
24 | "type": "package",
25 | "build": {
26 | "build/Microsoft.PowerApps.MSBuild.Pcf.props": {},
27 | "build/Microsoft.PowerApps.MSBuild.Pcf.targets": {}
28 | }
29 | }
30 | },
31 | ".NETFramework,Version=v4.0/win-x86": {
32 | "Microsoft.PowerApps.MSBuild.Pcf/0.2.59": {
33 | "type": "package",
34 | "build": {
35 | "build/Microsoft.PowerApps.MSBuild.Pcf.props": {},
36 | "build/Microsoft.PowerApps.MSBuild.Pcf.targets": {}
37 | }
38 | }
39 | }
40 | },
41 | "libraries": {
42 | "Microsoft.PowerApps.MSBuild.Pcf/0.2.59": {
43 | "sha512": "lR9n5cKLOHIW40uHCQSyQmXIplLHNis5IqJ6OE+r1EL/ZJNl3eUtleNvJmbVVDcejIp19vGDvpuxfBpj+oPXjg==",
44 | "type": "package",
45 | "path": "microsoft.powerapps.msbuild.pcf/0.2.59",
46 | "files": [
47 | ".nupkg.metadata",
48 | ".signature.p7s",
49 | "3rdPartyNotice.txt",
50 | "LICENSE.txt",
51 | "build/Microsoft.PowerApps.MSBuild.Pcf.props",
52 | "build/Microsoft.PowerApps.MSBuild.Pcf.targets",
53 | "microsoft.powerapps.msbuild.pcf.0.2.59.nupkg.sha512",
54 | "microsoft.powerapps.msbuild.pcf.nuspec"
55 | ]
56 | }
57 | },
58 | "projectFileDependencyGroups": {
59 | ".NETFramework,Version=v4.0": [
60 | "Microsoft.PowerApps.MSBuild.Pcf >= 0.*"
61 | ]
62 | },
63 | "packageFolders": {
64 | "C:\\Users\\ivanf\\.nuget\\packages\\": {}
65 | },
66 | "project": {
67 | "version": "1.0.0",
68 | "restore": {
69 | "projectUniqueName": "C:\\Users\\ivanf\\Source\\Repos\\PCFGalleryControl\\PCFGalleryControl.pcfproj",
70 | "projectName": "PCFGalleryControl",
71 | "projectPath": "C:\\Users\\ivanf\\Source\\Repos\\PCFGalleryControl\\PCFGalleryControl.pcfproj",
72 | "packagesPath": "C:\\Users\\ivanf\\.nuget\\packages\\",
73 | "outputPath": "C:\\Users\\ivanf\\Source\\Repos\\PCFGalleryControl\\obj\\",
74 | "projectStyle": "PackageReference",
75 | "skipContentFileWrite": true,
76 | "configFilePaths": [
77 | "C:\\Users\\ivanf\\AppData\\Roaming\\NuGet\\NuGet.Config",
78 | "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
79 | ],
80 | "originalTargetFrameworks": [
81 | "net40"
82 | ],
83 | "sources": {
84 | "https://www.nuget.org/api/v2/": {}
85 | },
86 | "frameworks": {
87 | "net40": {
88 | "projectReferences": {}
89 | }
90 | }
91 | },
92 | "frameworks": {
93 | "net40": {
94 | "dependencies": {
95 | "Microsoft.PowerApps.MSBuild.Pcf": {
96 | "target": "Package",
97 | "version": "[0.*, )"
98 | }
99 | }
100 | }
101 | },
102 | "runtimes": {
103 | "win": {
104 | "#import": []
105 | },
106 | "win-x64": {
107 | "#import": []
108 | },
109 | "win-x86": {
110 | "#import": []
111 | }
112 | }
113 | }
114 | }
--------------------------------------------------------------------------------
/solution/other/Solution.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | solution
6 |
7 |
8 |
9 |
10 |
11 | 1.0
12 |
13 | 2
14 |
15 |
16 | IvanFicko
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | fic
29 |
30 | 58972
31 |
32 |
33 |
34 | 1
35 | 1
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 1
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 2
63 | 1
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | 1
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/GalleryFieldControl/index.ts:
--------------------------------------------------------------------------------
1 | import { IInputs, IOutputs } from "./generated/ManifestTypes";
2 |
3 | import * as FileSaver from 'file-saver';
4 |
5 | class EntityReference {
6 | id: string;
7 | typeName: string;
8 | constructor(typeName: string, id: string) {
9 | this.id = id;
10 | this.typeName = typeName;
11 | }
12 | }
13 |
14 | class AttachedFile implements ComponentFramework.FileObject {
15 | fileContent: string;
16 | fileSize: number;
17 | fileName: string;
18 | mimeType: string;
19 | constructor(fileName: string, mimeType: string, fileContent: string, fileSize: number) {
20 | this.fileName = fileName;
21 | this.mimeType = mimeType;
22 | this.fileContent = fileContent;
23 | this.fileSize = fileSize;
24 | }
25 | }
26 |
27 | export class GalleryFieldControl implements ComponentFramework.StandardControl {
28 |
29 | private _context: ComponentFramework.Context;
30 | private _container: HTMLDivElement;
31 |
32 | private _thumbnailHeight: number | null;
33 | private _thumbnailWidth: number | null;
34 |
35 | private _minImageHeight: number | null;
36 | private _minImageWidth: number | null;
37 |
38 | private _notesContainer: HTMLDivElement;
39 | private _timelineEmailsContainer: HTMLDivElement;
40 |
41 | private _previewImage: HTMLImageElement;
42 |
43 | private _thumbnailClicked: EventListenerOrEventListenerObject;
44 | private _clearPreviewImage: EventListenerOrEventListenerObject;
45 |
46 | private _supportedMimeTypes: string[] = ["image/jpeg", "image/png", "image/svg+xml"];
47 | private _supportedExtensions : string[] = [".jpg", ".jpeg", ".png", ".svg", ".gif"];
48 |
49 | constructor() {
50 |
51 | }
52 |
53 | /**
54 | * Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
55 | * Data-set values are not initialized here, use updateView.
56 | * @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
57 | * @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
58 | * @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
59 | * @param container If a control is marked control-type='starndard', it will receive an empty div element within which it can render its content.
60 | */
61 | public init(context: ComponentFramework.Context, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement) {
62 | // Add control initialization code
63 | this._context = context;
64 | this._container = container;
65 |
66 | this._thumbnailHeight = context.parameters.thumbnailHeight == undefined ? null : context.parameters.thumbnailHeight.raw;
67 | this._thumbnailWidth = context.parameters.thumbnailWidth == undefined ? null : context.parameters.thumbnailWidth.raw;
68 |
69 | this._minImageHeight = context.parameters.minImageHeight == undefined ? null : context.parameters.minImageHeight.raw;
70 | this._minImageWidth = context.parameters.minImageWidth == undefined ? null : context.parameters.minImageWidth.raw;
71 |
72 | let reference: EntityReference = new EntityReference(
73 | (context).page.entityTypeName,
74 | (context).page.entityId
75 | )
76 |
77 | this._thumbnailClicked = this.ThumbnailClicked.bind(this);
78 | this._clearPreviewImage = this.ClearPreviewImage.bind(this);
79 |
80 | let notesContainer = document.createElement("div");
81 | notesContainer.classList.add("files-group");
82 | this._notesContainer = notesContainer;
83 |
84 | let notesHeader = document.createElement("div");
85 | notesHeader.textContent = reference.typeName == "email" ? "Attachments" : "Notes";
86 | notesHeader.classList.add("section-header");
87 | notesContainer.appendChild(notesHeader);
88 |
89 | let notes = document.createElement("div");
90 | notesContainer.appendChild(notes);
91 |
92 |
93 | let timelineEmailsContainer = document.createElement("div");
94 | timelineEmailsContainer.classList.add("files-group");
95 | this._timelineEmailsContainer = timelineEmailsContainer;
96 |
97 | let timelineEmailsHeader = document.createElement("div");
98 | timelineEmailsHeader.textContent = "Timeline Emails";
99 | timelineEmailsHeader.classList.add("section-header");
100 | timelineEmailsContainer.appendChild(timelineEmailsHeader);
101 |
102 | let timelineEmails = document.createElement("div");
103 | timelineEmailsContainer.appendChild(timelineEmails);
104 |
105 |
106 | this._container.appendChild(notesContainer);
107 | this._container.appendChild(timelineEmailsContainer);
108 |
109 | let previewImg = document.createElement("img");
110 | previewImg.classList.add("preview-img");
111 | previewImg.addEventListener("click", this._clearPreviewImage);
112 |
113 | this._previewImage = previewImg;
114 |
115 | this._container.appendChild(previewImg);
116 |
117 | this.GetFiles(reference).then(result => this.RenderThumbnails(result, notes));
118 | this.GetEmailAttachemntsFromTimeline(reference).then(result => this.RenderThumbnails(result, timelineEmails));
119 | }
120 |
121 |
122 | /**
123 | * Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
124 | * @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
125 | */
126 | public updateView(context: ComponentFramework.Context): void {
127 | // Add code to update control view
128 | }
129 |
130 | /**
131 | * It is called by the framework prior to a control receiving new data.
132 | * @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as “bound” or “output”
133 | */
134 | public getOutputs(): IOutputs {
135 | return {};
136 | }
137 |
138 | /**
139 | * Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
140 | * i.e. cancelling any pending remote calls, removing listeners, etc.
141 | */
142 | public destroy(): void {
143 | // Add code to cleanup control if necessary
144 | }
145 |
146 |
147 | private async GetFiles(ref: EntityReference): Promise {
148 | let attachmentType = ref.typeName == "email" ? "activitymimeattachment" : "annotation";
149 | let fetchXml =
150 | "" +
151 | " " +
152 | " " +
153 | " " +
154 | " " +
155 | " " +
156 | "";
157 |
158 | let query = '?fetchXml=' + encodeURIComponent(fetchXml);
159 |
160 | try {
161 | const result = await this._context.webAPI.retrieveMultipleRecords(attachmentType, query);
162 | if (result.entities.length == 0) { this._notesContainer.hidden = true; }
163 | let items = [];
164 | for (let i = 0; i < result.entities.length; i++) {
165 | let record = result.entities[i];
166 | let fileName = record["filename"];
167 | let mimeType = record["mimetype"];
168 | let content = record["body"] || record["documentbody"];
169 | let fileSize = record["filesize"];
170 |
171 | const ext = fileName.substr(fileName.lastIndexOf('.')).toLowerCase();
172 |
173 | if (!this._supportedMimeTypes.includes(mimeType) && !this._supportedExtensions.includes(ext)) { continue; }
174 |
175 | let file = new AttachedFile(fileName, mimeType, content, fileSize);
176 | items.push(file);
177 | }
178 | return items;
179 | }
180 | catch (error) {
181 | return [];
182 | }
183 | }
184 |
185 | private async GetEmailAttachemntsFromTimeline(ref: EntityReference) {
186 |
187 | let fetchXml =
188 | "" +
189 | " " +
190 | " " +
191 | " " +
192 | " " +
193 | " " +
194 | " " +
195 | " " +
196 | "";
197 |
198 |
199 | let query = '?fetchXml=' + encodeURIComponent(fetchXml);
200 |
201 | try {
202 | const result = await this._context.webAPI.retrieveMultipleRecords("activitymimeattachment", query);
203 | if (result.entities.length == 0) { this._timelineEmailsContainer.hidden = true; }
204 | let items = [];
205 | for (let i = 0; i < result.entities.length; i++) {
206 | let record = result.entities[i];
207 |
208 | let mimeType = record["mimetype"];
209 | let fileName = record["filename"];
210 | let content = record["body"] || record["documentbody"];
211 | let fileSize = record["filesize"];
212 |
213 | const ext = fileName.substr(fileName.lastIndexOf('.')).toLowerCase();
214 |
215 | if (!this._supportedMimeTypes.includes(mimeType) && !this._supportedExtensions.includes(ext)) { continue; }
216 |
217 | let file = new AttachedFile(fileName, mimeType, content, fileSize);
218 | items.push(file);
219 | }
220 | return items;
221 | }
222 | catch (error) {
223 | return [];
224 | }
225 | }
226 |
227 | private async RenderThumbnails(files: AttachedFile[], container: HTMLDivElement) {
228 | for (let index = 0; index < files.length; index++) {
229 | const file = files[index];
230 |
231 | let itemContainer = document.createElement("div");
232 | itemContainer.classList.add("thumbnail-container");
233 |
234 | let thumbnailDiv = document.createElement("div");
235 | thumbnailDiv.classList.add("thumbnail");
236 |
237 | // let imageDiv = document.createElement("img");
238 | // imageDiv.src = 'data:' + file.mimeType + ';base64, ' + file.fileContent;
239 | let base64String = 'data:' + file.mimeType + ';base64, ' + file.fileContent;
240 | let imageDiv = await this.GetLoadedImageElement(base64String);
241 |
242 | if ((this._minImageHeight != null && imageDiv.height < this._minImageHeight) && (this._minImageWidth != null && imageDiv.width < this._minImageWidth)) {
243 | imageDiv.remove();
244 | continue;
245 | }
246 |
247 | imageDiv.height = this._thumbnailHeight == null ? 150 : this._thumbnailHeight;
248 | imageDiv.width = this._thumbnailWidth == null ? 150 : this._thumbnailWidth;
249 | imageDiv.addEventListener("click", this._thumbnailClicked);
250 | thumbnailDiv.appendChild(imageDiv);
251 |
252 | let fileNameDiv = document.createElement("div");
253 | fileNameDiv.classList.add("file-name");
254 | fileNameDiv.style.width = (this._thumbnailWidth == null ? 162 : this._thumbnailWidth + 12).toString() + "px";
255 | fileNameDiv.textContent = file.fileName;
256 | fileNameDiv.onclick = (e => { this.DownloadFile(file); });
257 |
258 | itemContainer.appendChild(thumbnailDiv);
259 | itemContainer.appendChild(fileNameDiv);
260 |
261 | container.appendChild(itemContainer);
262 | }
263 | }
264 |
265 | private Base64ToFile(base64Data: string, tempfilename: string, contentType: string) {
266 | contentType = contentType || '';
267 | const sliceSize = 1024;
268 | const byteCharacters = atob(base64Data);
269 | const bytesLength = byteCharacters.length;
270 | const slicesCount = Math.ceil(bytesLength / sliceSize);
271 | const byteArrays = new Array(slicesCount);
272 |
273 | for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
274 | const begin = sliceIndex * sliceSize;
275 | const end = Math.min(begin + sliceSize, bytesLength);
276 |
277 | const bytes = new Array(end - begin);
278 | for (let offset = begin, i = 0; offset < end; ++i, ++offset) {
279 | bytes[i] = byteCharacters[offset].charCodeAt(0);
280 | }
281 | byteArrays[sliceIndex] = new Uint8Array(bytes);
282 | }
283 | return new File(byteArrays, tempfilename, { type: contentType });
284 | }
285 |
286 | private ThumbnailClicked(evt: Event): void {
287 | let base64 = (evt.srcElement).src;
288 | this._previewImage.src = base64;
289 | }
290 |
291 | private ClearPreviewImage(evt: Event): void {
292 | this._previewImage.src = "";
293 | }
294 |
295 | private DownloadFile(file: AttachedFile): void {
296 | const myFile = this.Base64ToFile(file.fileContent, file.fileName, file.mimeType);
297 | FileSaver.saveAs(myFile, file.fileName);
298 | }
299 |
300 | private async GetLoadedImageElement(base64: string):Promise{
301 | return new Promise((resolve, reject) => {
302 | let imageDiv = document.createElement("img");
303 | imageDiv.onload = () => resolve(imageDiv);
304 | imageDiv.src = base64;
305 | });
306 | }
307 | }
--------------------------------------------------------------------------------