From 57c206cc1ff3c585bf4859b3bd1dfa6eb9e6cbc5 Mon Sep 17 00:00:00 2001 From: Jan Schmitz-Hermes Date: Wed, 17 Jul 2024 08:43:17 +0200 Subject: [PATCH] add freepik api without key --- README.md | 6 ++ src/taskpane/iconDownloadUtils.ts | 55 ++++++++++++++++ src/taskpane/taskpane.css | 8 ++- src/taskpane/taskpane.html | 7 ++ src/taskpane/taskpane.ts | 105 +++++++++++++++++++++++++++++- src/taskpane/types.ts | 4 ++ 6 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 src/taskpane/iconDownloadUtils.ts create mode 100644 src/taskpane/types.ts diff --git a/README.md b/README.md index 4292d26..9f0ab34 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,12 @@ command: npm run stop ``` +## Debugging +After running the following command, the developer console is available with right click into the add-in window and selecting element information. +`defaults write com.microsoft.Powerpoint OfficeWebAddinDeveloperExtras -bool true` + +⚠️ Mac App Store builds of Office do not support the OfficeWebAddinDeveloperExtras flag. + ## Helpful links [PowerPoint Api-Documentation](https://learn.microsoft.com/de-de/javascript/api/powerpoint?view=powerpoint-js-preview) diff --git a/src/taskpane/iconDownloadUtils.ts b/src/taskpane/iconDownloadUtils.ts new file mode 100644 index 0000000..46ae76a --- /dev/null +++ b/src/taskpane/iconDownloadUtils.ts @@ -0,0 +1,55 @@ +import { FetchIconResponse } from "./types"; + +export async function fetchIcons(searchTerm: string): Promise> { + const url = `https://hammerhead-app-fj5ps.ondigitalocean.app/icons?term=${searchTerm}&family-id=300&filters[shape]=outline&filters[color]=solid-black&filters[free_svg]=premium`; + const requestHeaders = new Headers(); + requestHeaders.append("X-Freepik-API-Key", "XXX"); + const requestOptions = { + method: "GET", + headers: requestHeaders, + }; + + try { + const result = await fetch(url, requestOptions); + const response = await result.json(); + return response.data + .filter((obj) => obj.author.name === "Smashicons" && obj.family.name === "Basic Miscellany Lineal") + .map((obj) => ({ + id: obj.id.toString(), + url: obj.thumbnails[0].url, + })) + .slice(0, 50); + } catch (e) { + throw new Error("Error fetching icons: " + e); + } +} + +export async function getDownloadPathForIconWith(id: string) { + const url = `https://hammerhead-app-fj5ps.ondigitalocean.app/icons/${id}/download?format=png`; + const requestHeaders = new Headers(); + requestHeaders.append("X-Freepik-API-Key", "XXX"); + const requestOptions = { + method: "GET", + headers: requestHeaders, + }; + + try { + const result = await fetch(url, requestOptions); + const response = await result.json(); + return response.data.url; + } catch (e) { + throw new Error("Error getting download url: " + e); + } +} + +export async function downloadIconWith(url: string) { + const requestOptions = { + method: "GET", + }; + + try { + return await fetch(url, requestOptions); + } catch (e) { + throw new Error("Error downloading icon: " + e); + } +} diff --git a/src/taskpane/taskpane.css b/src/taskpane/taskpane.css index 67c1c8c..fa605ee 100644 --- a/src/taskpane/taskpane.css +++ b/src/taskpane/taskpane.css @@ -139,6 +139,12 @@ ul { width: 31%; } +.icon-search-button { + width: 31%; +} + +.icon-results {} + .grid-button { width: 49% } @@ -164,4 +170,4 @@ ul { .dropdown-menu.show { display: block; -} \ No newline at end of file +} diff --git a/src/taskpane/taskpane.html b/src/taskpane/taskpane.html index ae6c084..5d0c88c 100644 --- a/src/taskpane/taskpane.html +++ b/src/taskpane/taskpane.html @@ -107,6 +107,13 @@

L21's-Logo

+

Icons

+ +
+ + +
+ diff --git a/src/taskpane/taskpane.ts b/src/taskpane/taskpane.ts index b690772..c181531 100644 --- a/src/taskpane/taskpane.ts +++ b/src/taskpane/taskpane.ts @@ -9,6 +9,8 @@ import { base64Images } from "../../base64Image"; import * as M from "../../lib/materialize/js/materialize.min"; import { runPowerPoint } from "./powerPointUtil"; import { columnLineName, rowLineName, createColumns, createRows } from "./rowsColumns"; +import { getDownloadPathForIconWith, downloadIconWith, fetchIcons } from "./iconDownloadUtils"; +import { FetchIconResponse } from "./types"; Office.onReady((info) => { if (info.host === Office.HostType.PowerPoint) { @@ -29,6 +31,10 @@ Office.onReady((info) => { document.querySelectorAll(".logo-button").forEach((button) => { (button as HTMLElement).onclick = () => insertImageByBase64(button.getAttribute("data-value")); }); + + initDropdownPlaceholder(); + addIconSearch(); + insertIconOnClickOnPreview(); } }); @@ -39,7 +45,7 @@ function initRowsAndColumnsButtons() { document.querySelectorAll(".row-button").forEach((button) => { (button as HTMLElement).onclick = () => { createRows(Number(button.getAttribute("data-value"))); - } + }; }); document.querySelectorAll(".column-button").forEach((button) => { @@ -96,7 +102,7 @@ export async function insertSticker(color) { const shapes = powerPointContext.presentation.getSelectedSlides().getItemAt(0).shapes; const textBox = shapes.addTextBox( localStorage.getItem("initials") + ", " + today.toDateString() + "\n", - { height: 50, left: 50, top: 50, width: 150 } + { height: 50, left: 50, top: 50, width: 150 } ); textBox.name = "Square"; textBox.fill.setSolidColor(rgbToHex(color)); @@ -107,6 +113,7 @@ export async function insertSticker(color) { function rgbToHex(rgb: String) { const regex = /(\d+),\s*(\d+),\s*(\d+)/; const matches = rgb.match(regex); + function componentToHex(c: String) { const hex = Number(c).toString(16); return hex.length === 1 ? "0" + hex : hex; @@ -132,3 +139,97 @@ export async function addBackground(backgroundColor?: string) { selectedImage.fill.setSolidColor(backgroundColor); }); } + +function addIconPreviewWith(icons: FetchIconResponse[]) { + for (let i = 0; i < icons.length; i += 5) { + const iconPreviewElement = document.getElementById("icon-previews"); + const listElement = document.createElement("li"); + const anchorElement = document.createElement("a"); + iconPreviewElement.appendChild(listElement); + listElement.appendChild(anchorElement); + + icons.slice(i, i + 5).forEach((icon) => { + const iconPreviewElement = document.createElement("img"); + iconPreviewElement.id = icon.id; + iconPreviewElement.src = icon.url; + iconPreviewElement.width = 45; + iconPreviewElement.height = 45; + anchorElement.appendChild(iconPreviewElement); + }); + } +} + +async function insertBase64ImageOn(event) { + const imageSizeInPixels = 500; + const path = await getDownloadPathForIconWith(event.target.id); + let base64Image: string = await downloadIconWith(path) + .then((response) => response.blob()) + .then( + (blob) => + new Promise((resolve, reject) => { + const img = new Image(); + const reader = new FileReader(); + reader.onload = () => { + img.onload = () => { + const canvas = document.createElement("canvas"); + canvas.width = imageSizeInPixels; + canvas.height = imageSizeInPixels; + const ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0, imageSizeInPixels, imageSizeInPixels); + resolve(canvas.toDataURL("image/png")); + }; + img.onerror = reject; + img.src = reader.result as string; + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }) + ); + + if (base64Image.startsWith("data:")) { + const PREFIX = "base64,"; + const base64Idx = base64Image.indexOf(PREFIX); + base64Image = base64Image.substring(base64Idx + PREFIX.length); + } + + Office.context.document.setSelectedDataAsync( + base64Image, + { coercionType: Office.CoercionType.Image }, + (asyncResult) => { + if (asyncResult.status === Office.AsyncResultStatus.Failed) { + console.error(`Insert image failed. Code: ${asyncResult.error.code}. Message: ${asyncResult.error.message}`); + } + } + ); +} + +function addIconSearch() { + document.getElementById("icons").onclick = async () => { + document.querySelectorAll("#icon-previews li").forEach((li) => li.remove()); + + try { + const searchTerm = (document.getElementById("icon-search-input")).value; + const result = await fetchIcons(searchTerm); + addIconPreviewWith(result); + } catch (e) { + throw new Error("Error retrieving icon urls: " + e); + } + }; +} + +function insertIconOnClickOnPreview() { + document.getElementById("icon-previews").addEventListener("click", (event) => insertBase64ImageOn(event), false); +} + +function initDropdownPlaceholder() { + const iconPreviewElement = document.getElementById("icon-previews"); + for (let i = 0; i < 15; i++) { + const spanElement = document.createElement("span"); + spanElement.innerText = "Loading..."; + const anchorElement = document.createElement("a"); + const listElement = document.createElement("li"); + iconPreviewElement.appendChild(listElement); + listElement.appendChild(anchorElement); + anchorElement.appendChild(spanElement); + } +} diff --git a/src/taskpane/types.ts b/src/taskpane/types.ts new file mode 100644 index 0000000..0301b24 --- /dev/null +++ b/src/taskpane/types.ts @@ -0,0 +1,4 @@ +export type FetchIconResponse = { + id: string; + url: string; +};