aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsowgro <tpoke.ferrari@gmail.com>2024-12-31 00:49:03 -0500
committersowgro <tpoke.ferrari@gmail.com>2024-12-31 00:49:03 -0500
commit41814aa14040aa038c17ee0728532b0e341c5953 (patch)
tree78ae14228e888a18be5ad685a5277489e8fefc48
parent7cbba179124ed5a849f9d8c09fc61ec057810e13 (diff)
downloadbookmarks-home-41814aa14040aa038c17ee0728532b0e341c5953.tar.gz
bookmarks-home-41814aa14040aa038c17ee0728532b0e341c5953.tar.bz2
bookmarks-home-41814aa14040aa038c17ee0728532b0e341c5953.zip
Refactor BMIcon
-rw-r--r--extension/src/components/BMEditor.tsx32
-rw-r--r--extension/src/components/BMIcon.tsx98
-rw-r--r--extension/src/components/Bookmark.tsx54
-rw-r--r--extension/src/components/RadioButtonGroup.tsx8
4 files changed, 106 insertions, 86 deletions
diff --git a/extension/src/components/BMEditor.tsx b/extension/src/components/BMEditor.tsx
index 2579bdb..4ebdaa2 100644
--- a/extension/src/components/BMEditor.tsx
+++ b/extension/src/components/BMEditor.tsx
@@ -2,17 +2,25 @@ import React, {useContext, useEffect, useState} from "react";
import CloseIcon from "../assets/close.svg?react"
import {ActiveEdit} from "./Body.tsx";
import {getBrowser} from "../main.tsx";
+import RadioButtonGroup from "./RadioButtonGroup.tsx";
function BMEditor() {
const [activeEdit, setActiveEdit] = useContext(ActiveEdit);
const [iconOptions, setIconOptions] = useState<string[]>([]);
+ const [gIcon, setGIcon] = useState<string | undefined>(undefined);
useEffect(() => {
if (!activeEdit) return;
+
+ const gURL = new URL('https://www.google.com/s2/favicons');
+ gURL.searchParams.set("sz", "256");
+ gURL.searchParams.set("domain_url", activeEdit.url!);
+ setGIcon(gURL.toString());
+
getBrowser().storage.local.get("icon-aval-"+activeEdit.id).then( r => {
- setIconOptions(r["icon-aval-"+activeEdit.id]);
+ setIconOptions(r["icon-aval-" + activeEdit.id]);
});
}, [activeEdit]);
@@ -39,13 +47,21 @@ function BMEditor() {
</>)}
<h3>Icon</h3>
- {/*<RadioButtonGroup value={undefined}>*/}
- {iconOptions && iconOptions.map(s =>
- // <option value={s}>
- <img src={s}/>
- // </option>
- )}
- {/*</RadioButtonGroup>*/}
+ <h4>Found on the site</h4>
+ {/*{ iconOptions &&*/}
+ {/*<RadioButtonGroup value={undefined} children={*/}
+ {/* iconOptions.map(s => {*/}
+ {/* return { props: {*/}
+ {/* value: s,*/}
+ {/* children: ()*/}
+ {/* }}*/}
+ {/* }*/}
+ {/* )}*/}
+ {/*/>}*/}
+ <h4>From Google</h4>
+ <img src={gIcon}/>
+ <h4>Custom</h4>
+ <p>TODO</p>
</>)}
</div>
);
diff --git a/extension/src/components/BMIcon.tsx b/extension/src/components/BMIcon.tsx
index e7c2a26..d8622fb 100644
--- a/extension/src/components/BMIcon.tsx
+++ b/extension/src/components/BMIcon.tsx
@@ -1,99 +1,49 @@
-import React, {SyntheticEvent, useEffect} from "react";
+import React, {SyntheticEvent, useEffect, useState} from "react";
import ColorThief from "colorthief";
import {getBrowser} from "../main.tsx";
-function BMIcon (props: {url: string, id: string}) {
+function BMIcon(props: {imgSrc?: string, bmUrl?:string}) {
- let [favicon, setFavicon] = React.useState<string | null>(null);
- let [iconMode, setIconMode] = React.useState<"large" | "small" | "letter">("letter");
+ let [iconMode, setIconMode] = React.useState<"large" | "small" | "letter">("large");
let [bgColor, setBgColor] = React.useState<[number, number, number] | null>(null)
- let [bgColorPriority, setBgColorPriority] = React.useState(0);
-
- useEffect(() => {
- faviconURL(props).then(r => {
- if (r) {
- setFavicon(r)
- setIconMode("small");
- }
- })
- }, []);
function handleImageLoad(e: SyntheticEvent<HTMLImageElement, Event>) {
- if (e.currentTarget.naturalWidth >= 75 || favicon!.startsWith("data:image/svg+xml")) {
- setIconMode("large")
- } else if(bgColorPriority < 2) {
- setBgColor(new ColorThief().getColor(e.currentTarget))
- setBgColorPriority(2);
+ if (e.currentTarget.naturalWidth < 75 && !props.imgSrc!.startsWith("data:image/svg+xml")) {
+ setBgColor(new ColorThief().getColor(e.currentTarget));
+ setIconMode("small");
}
}
+ function handleImageError() {
+ let url = new URL(props.bmUrl!);
+ setBgColor(hashStringToColor(url.hostname))
+ setIconMode("letter");
+ }
+
return (
<div className={"icon-box " + (iconMode)}
style={bgColor ? {"--icon-bg": `rgba(${bgColor[0]}, ${bgColor[1]}, ${bgColor[2]}, 0.2)`} as React.CSSProperties : undefined}>
- {(() => {
- switch (iconMode) {
- case "letter": {
- let url = new URL(props.url);
- if (bgColorPriority < 1) {
- setBgColor(hashStringToColor(url.hostname));
- setBgColorPriority(1);
- }
- return (<span className={"letter"}>{url.hostname.charAt(0)}</span>)
- }
- case "small": {
- return (<img alt="Bookmark icon" src={favicon!} onLoad={handleImageLoad}/>)
- }
- case "large": {
- return (<img alt="Bookmark icon" src={favicon!}/>)
- }
+ {(() => { switch (iconMode) {
+ case "letter": {
+ return (<span className={"letter"}>{new URL(props.bmUrl!).hostname.charAt(0)}</span>)
+ }
+ case "small": {
+ return (<img alt="Bookmark icon" src={props.imgSrc}/>)
}
- })()}
- </div>
+ case "large": {
+ return (<img alt="Bookmark icon" src={props.imgSrc} onLoad={handleImageLoad} onError={handleImageError}/>)
+ }
+ }})()}
+ </div>
)
}
-/**
- * Gets the icon for a bookmark
- *
- * @param data The URL of the link
- * @return The URL of the icon
- */
-async function faviconURL(data: {url: string, id:string}) {
- let i = (await getBrowser().storage.local.get("icon-cache-"+data.id))["icon-cache-"+data.id];
- if (i) return i
- // if (i == null) return i;
-
- const url = new URL('https://www.google.com/s2/favicons');
- url.searchParams.set("sz", "256");
- url.searchParams.set("domain_url", data.url!);
- let resp = await fetch(url)
- let imgData = resp.ok ? await toDataURL(url.toString()) : null;
- getBrowser().storage.local.set({["icon-cache-"+data.id]: imgData});
- return imgData;
-}
-
-function toDataURL(url: string): string {
- // @ts-ignore
- return fetch(url)
- .then(response => response.blob())
- .then(blob => new Promise((resolve, reject) => {
- const reader = new FileReader()
- reader.onloadend = () => resolve(reader.result)
- reader.onerror = reject
- reader.readAsDataURL(blob)
- }))
-}
-
-function djb2(str: string){
+function hashStringToColor(str: string): [number, number, number] {
let hash = 5381;
for (var i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */
}
- return hash;
-}
-function hashStringToColor(str: string): [number, number, number] {
- let hash = djb2(str);
let r = (hash & 0xFF0000) >> 16;
let g = (hash & 0x00FF00) >> 8;
let b = hash & 0x0000FF;
diff --git a/extension/src/components/Bookmark.tsx b/extension/src/components/Bookmark.tsx
index 0e36b56..a57f7f7 100644
--- a/extension/src/components/Bookmark.tsx
+++ b/extension/src/components/Bookmark.tsx
@@ -84,7 +84,7 @@ function Bookmark(props: {id: string}) {
return(
<div className={"bookmark"}>
<a href={bmData.url} draggable={settings.editMode} onDrag={handleDrag} onDragEnd={handleDragEnd}>
- <BMIcon url={bmData.url!} id={bmData.title}/>
+ <IconPre id={bmData.id} bmUrl={bmData.url!}/>
<span>{bmData.title}</span>
</a>
{settings.editMode && <ContextMenu onEdit={handleEdit} onDelete={handleDelete}/>}
@@ -94,4 +94,54 @@ function Bookmark(props: {id: string}) {
);
}
-export default Bookmark; \ No newline at end of file
+export default Bookmark;
+
+function IconPre(props: {bmUrl: string, id:string}) {
+ const [data, setData] = useState()
+
+ useEffect(() => {
+ findIcon(props.bmUrl, props.id).then(r => {
+ setData(r);
+ })
+ }, []);
+
+ if (!data) return;
+ return <BMIcon bmUrl={props.bmUrl} imgSrc={data}/>
+}
+
+/**
+ * 1. check for icon cache
+ * 2. check if the user selected an icon
+ * 3. use the best icon from the available icons
+ * 4. search googles icon database
+ */
+async function findIcon(bmUrl: string, id:string) {
+ let cachedIcon = (await getBrowser().storage.local.get("icon-cache-"+id))["icon-cache-"+id];
+ if (cachedIcon) return cachedIcon
+
+ // let selectedUrl: string[] = (await getBrowser().storage.local.get("icon-aval-"+data.id))["icon-aval-"+data.id]
+ // if (selectedUrl.length > 0) {
+ // await getBrowser().storage.local.set({["icon-cache-"+data.id]: selectedUrl[0]});
+ // return selectedUrl[0];
+ // }
+
+ const url = new URL('https://www.google.com/s2/favicons');
+ url.searchParams.set("sz", "256");
+ url.searchParams.set("domain_url", bmUrl);
+ let resp = await fetch(url)
+ let imgData = resp.ok ? await toDataURL(url.toString()) : null;
+ getBrowser().storage.local.set({["icon-cache-"+bmUrl]: imgData});
+ return imgData;
+}
+
+function toDataURL(url: string): string {
+ // @ts-ignore
+ return fetch(url)
+ .then(response => response.blob())
+ .then(blob => new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.onloadend = () => resolve(reader.result)
+ reader.onerror = reject
+ reader.readAsDataURL(blob)
+ }))
+} \ No newline at end of file
diff --git a/extension/src/components/RadioButtonGroup.tsx b/extension/src/components/RadioButtonGroup.tsx
index 297800d..ff5835d 100644
--- a/extension/src/components/RadioButtonGroup.tsx
+++ b/extension/src/components/RadioButtonGroup.tsx
@@ -1,4 +1,8 @@
import React, {ReactElement, useEffect, useId, useState} from "react";
+//
+// function RadioEntry(props: {value: any, children: ReactElement}) {
+// return props.children
+// }
/**
* A component for a group of radio buttons where only one can be selected
@@ -7,7 +11,7 @@ import React, {ReactElement, useEffect, useId, useState} from "react";
* @param props.value The option which is selected
* @param props.onChange A function that will be called when the selected option changes
*/
-function RadioButtonGroup(props: { children: ReactElement<HTMLOptionElement>[], value: any, onChange?: (arg0: any) => void }) {
+function RadioButtonGroup(props: { children: {props: {value: any, children: ReactElement}}[], value: any, onChange?: (arg0: any) => void }) {
const [selected, setSelected] = useState(props.value);
useEffect(() => {
setSelected(props.value);
@@ -18,7 +22,7 @@ function RadioButtonGroup(props: { children: ReactElement<HTMLOptionElement>[],
return (
<div className="radio-group">
- { props.children.map((item) => (
+ { props.children && props.children.map((item) => (
<label>
<input
type="radio"