diff options
author | sowgro <tpoke.ferrari@gmail.com> | 2025-01-14 16:40:02 -0500 |
---|---|---|
committer | sowgro <tpoke.ferrari@gmail.com> | 2025-01-14 16:40:02 -0500 |
commit | a37e3935e755f9a7f1a81e51d9fee696cac681c2 (patch) | |
tree | c0ca026a1984b4c30eb35320fc303511d01f4494 | |
parent | a8da6090454a1b1b9ca1d977138430f768ec44f1 (diff) | |
download | bookmarks-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.ts | 105 | ||||
-rw-r--r-- | extension/src/components/BMEditor.tsx | 26 | ||||
-rw-r--r-- | extension/src/components/BMIcon.tsx | 17 | ||||
-rw-r--r-- | extension/src/components/Bookmark.tsx | 50 | ||||
-rw-r--r-- | extension/src/index.css | 14 |
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; +} + |