aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsowgro <tpoke.ferrari@gmail.com>2025-01-14 16:40:02 -0500
committersowgro <tpoke.ferrari@gmail.com>2025-01-14 16:40:02 -0500
commita37e3935e755f9a7f1a81e51d9fee696cac681c2 (patch)
treec0ca026a1984b4c30eb35320fc303511d01f4494
parenta8da6090454a1b1b9ca1d977138430f768ec44f1 (diff)
downloadbookmarks-home-a37e3935e755f9a7f1a81e51d9fee696cac681c2.tar.gz
bookmarks-home-a37e3935e755f9a7f1a81e51d9fee696cac681c2.tar.bz2
bookmarks-home-a37e3935e755f9a7f1a81e51d9fee696cac681c2.zip
rewrite icon system to cache better
-rw-r--r--extension/src/Icons.ts105
-rw-r--r--extension/src/components/BMEditor.tsx26
-rw-r--r--extension/src/components/BMIcon.tsx17
-rw-r--r--extension/src/components/Bookmark.tsx50
-rw-r--r--extension/src/index.css14
5 files changed, 151 insertions, 61 deletions
diff --git a/extension/src/Icons.ts b/extension/src/Icons.ts
new file mode 100644
index 0000000..b386713
--- /dev/null
+++ b/extension/src/Icons.ts
@@ -0,0 +1,105 @@
+import {getBrowser} from "./main.tsx";
+import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode;
+import {RGBColor} from "colorthief";
+
+interface IconCacheEntry {
+ url?: string,
+ data: string,
+ setByUser: boolean,
+ source: "google" | "site" | "custom"
+}
+
+async function getIcon(bmData: BookmarkTreeNode, setIcon: (icon: string) => void) {
+ let id = bmData.id;
+ let bmUrl = bmData.url!;
+ let cache: IconCacheEntry = (await getBrowser().storage.local.get("icon-cache-" + id))["icon-cache-" + id];
+
+ if (cache) {
+ // if there is an icon in the cache, set that
+ setIcon(cache.data);
+ if (cache.setByUser) {
+ // if the user was the one who set the icon, then quit
+ return;
+ }
+
+ if (cache.source === "google") {
+ // if the cached icon came from google check to see if iconGrabber found an icon
+ iconFromSite(id).then(async r => {
+ if (r) {
+ // set the icon and update the cache
+ let iconData = toDataURL(r);
+ setIcon(r);
+ let newCache: IconCacheEntry = {
+ url: r,
+ data: await iconData,
+ setByUser: false,
+ source: "site"
+ }
+ await getBrowser().storage.local.set({["icon-cache-" +id]: newCache})
+ }
+ })
+ }
+ } else {
+
+ iconFromSite(id).then(async r => {
+ if (r) {
+ // set the icon and update the cache
+ let iconData = toDataURL(r);
+ setIcon(r);
+ let newCache: IconCacheEntry = {
+ url: r,
+ data: await iconData,
+ setByUser: false,
+ source: "site"
+ }
+ await getBrowser().storage.local.set({["icon-cache-" +id]: newCache})
+ } else {
+ iconFromGoogle(bmUrl).then(async r => {
+ if (r) {
+ // set the icon and update the cache
+ let iconData = toDataURL(r);
+ setIcon(r);
+ let newCache: IconCacheEntry = {
+ url: r,
+ data: await iconData,
+ setByUser: false,
+ source: "google"
+ }
+ await getBrowser().storage.local.set({["icon-cache-" +id]: newCache})
+ }
+ })
+ }
+ })
+ }
+}
+
+async function iconFromGoogle(bmUrl: string): Promise<string | undefined> {
+ 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)
+ if (resp.ok) {
+ return url.toString()
+ }
+}
+
+async function iconFromSite(id: string): Promise<string | undefined> {
+ let icons_aval: string[] = (await getBrowser().storage.local.get("icon-aval-" + id))["icon-aval-" + id];
+ if (icons_aval && icons_aval.length > 0) {
+ return icons_aval[0];
+ }
+}
+
+async function toDataURL(url: string) {
+ let response = await fetch(url);
+ let blob: Blob = await response.blob();
+ return await new Promise<string>((resolve, reject) => {
+ const reader = new FileReader()
+ // @ts-ignore
+ reader.onloadend = () => resolve(reader.result)
+ reader.onerror = reject
+ reader.readAsDataURL(blob)
+ });
+}
+
+export {type IconCacheEntry, getIcon} \ No newline at end of file
diff --git a/extension/src/components/BMEditor.tsx b/extension/src/components/BMEditor.tsx
index 4ebdaa2..b97d21f 100644
--- a/extension/src/components/BMEditor.tsx
+++ b/extension/src/components/BMEditor.tsx
@@ -3,6 +3,7 @@ import CloseIcon from "../assets/close.svg?react"
import {ActiveEdit} from "./Body.tsx";
import {getBrowser} from "../main.tsx";
import RadioButtonGroup from "./RadioButtonGroup.tsx";
+import BMIcon from "./BMIcon.tsx";
function BMEditor() {
@@ -48,20 +49,21 @@ function BMEditor() {
<h3>Icon</h3>
<h4>Found on the site</h4>
- {/*{ iconOptions &&*/}
- {/*<RadioButtonGroup value={undefined} children={*/}
- {/* iconOptions.map(s => {*/}
- {/* return { props: {*/}
- {/* value: s,*/}
- {/* children: ()*/}
- {/* }}*/}
- {/* }*/}
- {/* )}*/}
- {/*/>}*/}
+ <div className={"icon-selector"}>
+ { iconOptions &&
+ // <RadioButtonGroup value={undefined} children={
+ iconOptions.map(s =>
+ <BMIcon imgSrc={s}/>
+ )
+ // />
+ }
+ </div>
<h4>From Google</h4>
- <img src={gIcon}/>
+ <div className={"icon-selector"}>
+ <BMIcon imgSrc={gIcon}/>
+ </div>
<h4>Custom</h4>
- <p>TODO</p>
+ <button className={"default"}>Upload</button>
</>)}
</div>
);
diff --git a/extension/src/components/BMIcon.tsx b/extension/src/components/BMIcon.tsx
index 5e2f9c9..8af7115 100644
--- a/extension/src/components/BMIcon.tsx
+++ b/extension/src/components/BMIcon.tsx
@@ -15,14 +15,18 @@ function BMIcon(props: {imgSrc?: string, bmUrl?:string}) {
}
function handleImageError() {
- let url = new URL(props.bmUrl!);
- setBgColor(hashStringToColor(url.hostname))
+ if (props.bmUrl) {
+ let url = new URL(props.bmUrl);
+ setBgColor(hashStringToColor(url.hostname))
+ }
setIconMode("letter");
}
if (!props.imgSrc) {
- let url = new URL(props.bmUrl!);
- bgColor = hashStringToColor(url.hostname)
+ if (props.bmUrl) {
+ let url = new URL(props.bmUrl);
+ bgColor = hashStringToColor(url.hostname)
+ }
iconMode = "letter"
}
@@ -31,7 +35,10 @@ function BMIcon(props: {imgSrc?: string, bmUrl?:string}) {
style={bgColor ? {"--icon-bg": `rgba(${bgColor[0]}, ${bgColor[1]}, ${bgColor[2]}, 0.2)`} as React.CSSProperties : undefined}>
{(() => { switch (iconMode) {
case "letter": {
- return (<span className={"letter"}>{new URL(props.bmUrl!).hostname.charAt(0)}</span>)
+ return (<span className={"letter"}>{ props.bmUrl
+ ? new URL(props.bmUrl).hostname.charAt(0)
+ : '?'
+ }</span>)
}
case "small": {
return (<img alt="Bookmark icon" src={props.imgSrc}/>)
diff --git a/extension/src/components/Bookmark.tsx b/extension/src/components/Bookmark.tsx
index 760fa44..cbd8af6 100644
--- a/extension/src/components/Bookmark.tsx
+++ b/extension/src/components/Bookmark.tsx
@@ -5,6 +5,7 @@ import {ActiveDrag, ActiveEdit, Settings} from "./Body.tsx";
import DropTargets from "./DropTargets.tsx";
import ContextMenu from "./ContextMenu.tsx";
import BMIcon from "./BMIcon.tsx";
+import {getIcon} from "../Icons.ts";
/**
* A component for a single bookmark
@@ -84,7 +85,7 @@ function Bookmark(props: {id: string}) {
return(
<div className={"bookmark"}>
<a href={bmData.url} draggable={settings.editMode} onDrag={handleDrag} onDragEnd={handleDragEnd}>
- <IconPre id={bmData.id} bmUrl={bmData.url!}/>
+ <IconPre bmData={bmData}/>
<span>{bmData.title}</span>
</a>
{settings.editMode && <ContextMenu onEdit={handleEdit} onDelete={handleDelete}/>}
@@ -96,52 +97,13 @@ function Bookmark(props: {id: string}) {
export default Bookmark;
-function IconPre(props: {bmUrl: string, id:string}) {
- const [data, setData] = useState()
+function IconPre(props: {bmData: BookmarkTreeNode}) {
+ const [data, setData] = useState<string>()
useEffect(() => {
- findIcon(props.bmUrl, props.id).then(r => {
- setData(r);
- })
+ getIcon(props.bmData, setData)
}, []);
// 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)
- }))
+ return <BMIcon bmUrl={props.bmData.url} imgSrc={data}/>
} \ No newline at end of file
diff --git a/extension/src/index.css b/extension/src/index.css
index 72f29db..48710d0 100644
--- a/extension/src/index.css
+++ b/extension/src/index.css
@@ -34,6 +34,8 @@ body {
transform: translateX(100%);
transition: all 0.2s ease-in-out;
+
+ overflow: auto;
}
#settings-menu.open {
@@ -344,6 +346,18 @@ button:hover:not(.default) {
}
}
+.icon-selector {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 15px;
+}
+
+.icon-selector .icon-box {
+ width: 75px;
+ margin: 15px;
+}
+