diff --git a/_i18n/messages.properties b/_i18n/messages.properties
index 37ffeec..3e7b398 100644
--- a/_i18n/messages.properties
+++ b/_i18n/messages.properties
@@ -8,6 +8,11 @@ Toolbar1=Choose Selfie Template
Toolbar2=Choose Your Picture and Upload
Toolbar3=Edit and Download Final Image
Upload=Enhance Your Picture
+SaveAs=Save As
placeholder=Choose File for Upload...
+enterSapCommunityName=Enter SAP Community Name
+yourSapCommunityNameEG=Your community name, e. g.
+close=Close
+enhance=Enhance
gui.loading=Processing Picture
gui.loadingLong=Please wait ...Processing Picture
\ No newline at end of file
diff --git a/app/profilePic/profilePic/controller/App.controller.js b/app/profilePic/profilePic/controller/App.controller.js
index cb03e21..c93e012 100644
--- a/app/profilePic/profilePic/controller/App.controller.js
+++ b/app/profilePic/profilePic/controller/App.controller.js
@@ -7,9 +7,12 @@ sap.ui.define([
"sap/ui/core/Core",
"sap/ui/model/json/JSONModel",
"sap/ui/Device",
- "sap/suite/ui/commons/library"
+ "sap/suite/ui/commons/library",
+ "sap/m/Dialog",
+ "sap/m/Button",
+ "sap/m/Input",
],
- function (BaseController, MessageToast, oCore, JSONModel, Device, SuiteLibrary) {
+ function (BaseController, MessageToast, oCore, JSONModel, Device, SuiteLibrary, Dialog, Button, Input) {
return BaseController.extend("profilePic.controller.App", {
onInit: function () {
@@ -25,7 +28,41 @@ sap.ui.define([
}
},
- uploadPressed: async function (oEvent) {
+ enhancePressed: async function (oEvent) {
+ if (!this.oEnhanceDialog) {
+ var oResourceBundle = this.getView()
+ .getModel("i18n")
+ .getResourceBundle()
+ this.oEnhanceDialog = new Dialog({
+ title: oResourceBundle.getText("enterSapCommunityName"),
+ draggable: true,
+ content: new Input({
+ placeholder: oResourceBundle.getText("yourSapCommunityNameEG") + "dj.adams.sap",
+ value: "{/sapCommunityName}"
+ }),
+ buttons: [
+ new Button({
+ text: oResourceBundle.getText("enhance"),
+ press: function () {
+ this.upload()
+ this.oEnhanceDialog.close()
+ }.bind(this)
+ }),
+ new Button({
+ text: oResourceBundle.getText("close"),
+ press: function () {
+ this.oEnhanceDialog.close()
+ }.bind(this)
+ })
+ ]
+ })
+ this.getView().addDependent(this.oEnhanceDialog)
+ }
+
+ this.oEnhanceDialog.open()
+ },
+
+ upload: async function (oEvent) {
let view = this.getView()
let controller = view.getController()
let oFileUploader = view.byId("fileToUpload")
@@ -35,6 +72,7 @@ sap.ui.define([
}
let param = view.byId("uploadParam")
//param.setValue(oInput.getActivePage())
+ param.setValue(this.getView().getModel().getData().sapCommunityName)
oFileUploader.getParameters()
var oImageEditor = this.getView().byId("image")
oImageEditor.applyVisibleCrop()
diff --git a/app/profilePic/profilePic/view/App.view.xml b/app/profilePic/profilePic/view/App.view.xml
index abb2e60..d5a81c4 100644
--- a/app/profilePic/profilePic/view/App.view.xml
+++ b/app/profilePic/profilePic/view/App.view.xml
@@ -11,10 +11,10 @@
-
+
-
+
diff --git a/routes/profilePic.js b/routes/profilePic.js
index 54472f7..146e4f4 100644
--- a/routes/profilePic.js
+++ b/routes/profilePic.js
@@ -2,9 +2,12 @@ import sharp from 'sharp'
import multer from 'multer'
import lodash from 'lodash'
import * as svg from '../utils/svgRender.js'
-import { promises as fs } from 'fs'
+import { promises as fsPromises } from 'fs'
import * as path from 'path'
+import https from 'https'
+import fs from 'fs'
+
/**
* Route for handing upload, manipulation, and download of the profile picture
* @param {Object} app - Express application instance
@@ -17,10 +20,71 @@ export default function (app) {
* @returns {Promise}
*/
async function loadImageB64(image) {
+ return await fsPromises.readFile(path.resolve(app.baseDir, image), { encoding: 'base64' })
+ }
+
+ /**
+ * Download an image URL to the file system
+ * @param {string} url
+ * @returns {Promise}
+ */
+ async function download(url) {
+ const path = getPathFromImgUrl(url)
+ if (fs.existsSync(path)) return
+ return new Promise((resolve, reject) => {
+ const writeStream = fs.createWriteStream(path)
+ https.get(url, (res) => {
+ res.pipe(writeStream)
+ writeStream.on('finish', () => resolve())
+ }).on('error', (error) => reject(error))
+ })
+ }
+
+ /**
+ * Get the file system path for an image URL
+ * @param {string} url
+ * @returns {string}
+ */
+ function getPathFromImgUrl (url) {
+ return './images/' + path.basename(new URL(url).pathname)
+ }
- return await fs.readFile(path.resolve(app.baseDir, image), { encoding: 'base64' })
+ /**
+ * Get URLs of badges for a SAP community name
+ * @param {string} sapCommunityName
+ * @returns {Promise>}
+ */
+ async function getBadgeUrls (sapCommunityName) {
+ const url = 'https://people-api.services.sap.com/rs/showcaseBadge/' + sapCommunityName;
+ return new Promise((resolve, reject) => {
+ https.get(url, (res) => {
+ let body = ''
+ res.on('data', (chunk) => body += chunk.toString())
+ res.on('end', () => {
+ if (res.statusCode === 200) {
+ let badgeUrls = []
+ JSON.parse(body).forEach((ele) => {
+ badgeUrls.push(ele.imageUrl)
+ })
+ resolve(badgeUrls)
+ }
+ })
+ res.on('error', (error) => reject(error))
+ })
+ })
}
+ /**
+ * Read an svg image from file system and return it as a base64 png string
+ * @param {string} path
+ * @returns {Promise}
+ */
+ async function loadSvgAsPngB64 (path) {
+ const svgBuffer = await fsPromises.readFile(path)
+ const pngBuffer = await sharp(svgBuffer).png().toBuffer()
+ return await pngBuffer.toString('base64')
+ }
+
const limits = {
files: 1, // allow only 1 file per request
fileSize: 1024 * 1024 * 20, // 20 MB (max file size)
@@ -69,16 +133,35 @@ export default function (app) {
//Convert the uploaded content to PNG, rotate based upon metadata and transfer it to a buffer
const uploadContent = await sharp(file.buffer).rotate().png().toBuffer()
const uploadContentMeta = await sharp(uploadContent).metadata()
+
let body =
svg.svgHeader(uploadContentMeta.width, uploadContentMeta.height) +
svg.svgItem(0, 0, 0, uploadContent.toString('base64'), uploadContentMeta.height, uploadContentMeta.width, true) +
- svg.svgItem(0, 0, 0, await loadImageB64('./images/boarder.png'), uploadContentMeta.height, uploadContentMeta.width, true) +
- svg.svgEnd()
+ svg.svgItem(0, 0, 0, await loadImageB64('./images/boarder.png'), uploadContentMeta.height, uploadContentMeta.width, true)
+
+ //Load profile badges from SAP community and add them to the image
+ const badgeUrls = await getBadgeUrls(req.body.sapCommunityName)
+ await Promise.all(badgeUrls.map((badgeUrl) => download(badgeUrl)))
+ const halfImgHeight = uploadContentMeta.height / 2
+ const halfImgWidth = uploadContentMeta.width / 2
+ const badgeRadius = uploadContentMeta.width / 12
+ for (let i = 0; i < badgeUrls.length; i++) {
+ //Parametric equation of a circle: x=radius*cos(angle[rad]) y=radius*sin(angle[rad])
+ //However, y and x axis are switched in svg library. So x is the vertical axis in the following code
+ const x = halfImgHeight - 0.80 * halfImgHeight * Math.sin(Math.PI * (1.22 + i * 0.14)) - badgeRadius
+ const y = halfImgWidth + 0.80 * halfImgWidth * Math.cos(Math.PI * (1.22 + i * 0.14)) - badgeRadius
+ const badgePath = getPathFromImgUrl(badgeUrls[i])
+ const imgB64 = badgeUrls[i].slice(-3) === 'svg'
+ ? await loadSvgAsPngB64(badgePath)
+ : await loadImageB64(badgePath)
+ body += svg.svgItem(x, y, 0, imgB64, badgeRadius * 2, badgeRadius * 2, true)
+ }
+
+ body += svg.svgEnd()
const png = await sharp(Buffer.from(body)).png().toBuffer()
const pngOut = await png.toString('base64')
res.type("image/png").status(200).send(pngOut)
-
})
})
}
\ No newline at end of file