From 85ffd8e68b7dec9a034b227b4d9fafaac8f8b78e Mon Sep 17 00:00:00 2001 From: Shreyas Garimella Date: Thu, 30 Jan 2025 21:33:33 -0500 Subject: [PATCH 1/3] Customize BUtton implemented! Changed SneakyLinks modal to havea flexbox inside as well. big change --- extension/src/components/SneakyLinks.tsx | 333 ++++++++++++++++++++--- 1 file changed, 301 insertions(+), 32 deletions(-) diff --git a/extension/src/components/SneakyLinks.tsx b/extension/src/components/SneakyLinks.tsx index 1c88c6d7..369c973a 100644 --- a/extension/src/components/SneakyLinks.tsx +++ b/extension/src/components/SneakyLinks.tsx @@ -1,42 +1,65 @@ -import Table from "react-bootstrap/Table"; import Canvas from "../images/canvas.png"; import Docs from "../images/google-docs.png"; import Gmail from "../images/gmail.png"; import GCal from "../images/gcal.png"; +import {Button, Form, Modal} from "react-bootstrap" +import {useState} from "react"; import React from "react"; -import WidgetHeader from "./widget/WidgetHeader"; +import { FiEdit2 } from "react-icons/fi"; +// import WidgetHeader from "./widget/WidgetHeader"; import { EventTypes, useMixpanel } from "../context/MixpanelContext"; +import Container from 'react-bootstrap/Container'; +import Col from 'react-bootstrap/Col'; +import Image from 'react-bootstrap/Image'; +import Row from 'react-bootstrap/Row'; +import { StorageKeys, useStorage } from "../context/StorageContext"; + + + +let selectedIcon = 0; + +const LINKS = [ + "https://canvas.princeton.edu/", "https://mail.google.com/", "https://calendar.google.com/", "https://docs.google.com/" + ]; type SneakyLink = { href: string; + pos: number; id: string; alt: string; src: string; style?: React.CSSProperties; }; -const sneakyLinks: SneakyLink[] = [ +// const storage = useStorage(); + + +const initialSneakyLinks: SneakyLink[] = [ { - href: "https://canvas.princeton.edu/", + href: LINKS[0], + pos: 0, id: "canvas", alt: "Canvas", src: Canvas, }, { - href: "https://mail.google.com/", + href: LINKS[1], + pos: 1, id: "gmail", alt: "Gmail", src: Gmail, style: { paddingTop: "8px" }, }, { - href: "https://calendar.google.com/", + href: LINKS[2], + pos: 2, id: "gcal", alt: "GCal", src: GCal, }, { - href: "https://docs.google.com/", + href: LINKS[3], + pos: 3, id: "docs", alt: "Docs", src: Docs, @@ -44,37 +67,283 @@ const sneakyLinks: SneakyLink[] = [ ]; function SneakyLinksTable() { + const storage = useStorage(); + + const getBase64Data = (imageUrl: string): Promise => { + return new Promise((resolve, reject) => { + fetch(imageUrl) + .then((response) => response.blob()) + .then((blob) => { + const reader = new FileReader(); + reader.onloadend = () => { + resolve(reader.result as string); + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }) + .catch(reject); + }); + }; + + + +function httpify(url: string) { + if (!url.startsWith('http://') && !url.startsWith('https://')) { + return 'https://' + url; + } + return url; +} + +function checkUrl(url: string) { + const pattern = + /(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?\/[a-zA-Z0-9]{2,}|((https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?)|(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?/g; + return pattern.test(url); +} + + +const handleUpdate = async (e: React.FormEvent) => { + e.preventDefault(); + let newUrl = (document.getElementById('url-update') as HTMLInputElement).value; + console.log(newUrl); + + if(newUrl === "") { + newUrl = "https://canvas.princeton.edu/"; + } + + if(!checkUrl(newUrl)) { + alert("Invalid URL"); + return; + } + + newUrl = httpify(newUrl); + + let imageUrl = "https://logo.clearbit.com/" + newUrl; + console.log(imageUrl); + const updatedLinks = [...JSON.parse(sneakyLinks)]; + updatedLinks[selectedIcon].href = newUrl; + if(isImageUploaded) { + updatedLinks[selectedIcon].src = JSON.parse(sneakyLinks)[selectedIcon].src; + } + + else { + try { + const base64data = await getBase64Data(imageUrl); + updatedLinks[selectedIcon].src = base64data; + } catch (error) { + console.error("Error fetching image or converting to base64", error); + alert("Error updating the icon image."); + return; + } + } + console.log("Update forced!"); + setSneakyLinks(JSON.stringify(updatedLinks)); + storage.setLocalStorage(StorageKeys.LINKS, JSON.stringify(updatedLinks)); + // setTempUpdatedLinks(updatedLinks); + setCustomizerOpen(false); + + setImageUploadOpen(false); + + + }; + + + const handleIconSelection = (e: React.ChangeEvent) => { + selectedIcon = Number(e.target.value); + (document.getElementById('url-update') as HTMLInputElement).disabled = false; + console.log(selectedIcon); + }; + +const handleImageUpload = (e: React.ChangeEvent) => { + e.preventDefault(); + const files = (e.target as HTMLInputElement).files; + + if (files && files.length > 0) { + const file = files[0]; + const reader = new FileReader(); + + reader.onload = (readerEvent) => { + const img = document.createElement('img'); + img.src = readerEvent.target?.result as string; + img.onload = () => { + const width = img.width; + const height = img.height; + const size = Math.min(width, height); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + if (ctx) { + canvas.width = size; + canvas.height = size; + ctx.drawImage( + img, + (width - size) / 2, + (height - size) / 2, + size, + size, + 0, + 0, + size, + size + ); + + // Get the base64 data from the canvas + const base64Data = canvas.toDataURL('image/png'); + + // Process the base64 data (for example, updating the icon) + const updatedLinks = [...JSON.parse(sneakyLinks)]; + updatedLinks[selectedIcon].src = base64Data; + setSneakyLinks(JSON.stringify(updatedLinks)); + setImageUploaded(true); + } + }; + + // Handle error if the image fails to load + img.onerror = () => { + alert('There was an error loading the image.'); + }; + }; + + reader.readAsDataURL(file); // Read the file as a Data URL + } else { + alert('No file selected.'); + } +}; + const mixpanel = useMixpanel(); + const imageLabel = "Upload custom icon? (leave unchecked for image from web)" + const [sneakyLinks, setSneakyLinks] = useState( + storage.getLocalStorageDefault(StorageKeys.LINKS, JSON.stringify(initialSneakyLinks)) + ); + + + const [isCustomizerOpen, setCustomizerOpen] = useState(false); + // const [tempUpdatedLinks, setTempUpdatedLinks] = useState(JSON.parse(JSON.stringify(sneakyLinks))); + const [isImageUploadOpen, setImageUploadOpen] = useState(false); + const [isImageUploaded, setImageUploaded] = useState(false); return (
- - - - - {sneakyLinks.map((link) => ( - - ))} - - -
- - mixpanel.trackEvent(EventTypes.LINKS_CLICK, link.id) - } - > - {link.alt} - -
+ + setCustomizerOpen(false)}> + + Customize Quick Links + + + +
+ + + +

Select an icon to replace:

+ + {JSON.parse(sneakyLinks).map((link : SneakyLink) => ( + + + + ))} + + + + {JSON.parse(sneakyLinks).map((link : SneakyLink) => ( + + + + ))} + + + + + Enter new url to replace selected icon: + + + + {{setImageUploadOpen(!isImageUploadOpen)} {setImageUploaded(false)}}} /> + {isImageUploadOpen && } +
+ + + +
+
+ + {/* Header Section */} + + +

Sneaky Links

+ + + + +
+ + {/* Link Icons Section */} + + {JSON.parse(sneakyLinks).map((link: SneakyLink) => ( + + mixpanel.trackEvent(EventTypes.LINKS_CLICK, link.id)} + > + {link.alt} + + + ))} + +
+ +
); } export default SneakyLinksTable; + From a881724f822906ee93b9e84488b3957f189df9ac Mon Sep 17 00:00:00 2001 From: Shreyas Garimella Date: Thu, 30 Jan 2025 21:34:36 -0500 Subject: [PATCH 2/3] Storage Context with links --- extension/src/context/StorageContext.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extension/src/context/StorageContext.tsx b/extension/src/context/StorageContext.tsx index e603bcef..f1a7467b 100644 --- a/extension/src/context/StorageContext.tsx +++ b/extension/src/context/StorageContext.tsx @@ -6,6 +6,7 @@ export enum StorageKeys { WIDGET = "campusWidget", NAME = "name", DHALL = "dhall", + LINKS = "links" } type Storage = { @@ -67,6 +68,8 @@ const storageContext: Storage = { return [key, value]; } }) + + ); delete localStorageObject[StorageKeys.DATA]; From 8e4af128ff0277863c5935741edaa530fd8666e8 Mon Sep 17 00:00:00 2001 From: Shreyas Garimella Date: Thu, 30 Jan 2025 23:08:03 -0500 Subject: [PATCH 3/3] removed styling from icons because there was a weird effect on the second link --- extension/src/components/SneakyLinks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/components/SneakyLinks.tsx b/extension/src/components/SneakyLinks.tsx index 369c973a..8ffe9e54 100644 --- a/extension/src/components/SneakyLinks.tsx +++ b/extension/src/components/SneakyLinks.tsx @@ -332,7 +332,7 @@ const handleImageUpload = (e: React.ChangeEvent) => { alt={link.alt} className="link-icon" src={link.src} - style={link.style} + />