diff options
author | sowgro <tpoke.ferrari@gmail.com> | 2024-12-31 00:49:03 -0500 |
---|---|---|
committer | sowgro <tpoke.ferrari@gmail.com> | 2024-12-31 00:49:03 -0500 |
commit | 41814aa14040aa038c17ee0728532b0e341c5953 (patch) | |
tree | 78ae14228e888a18be5ad685a5277489e8fefc48 | |
parent | 7cbba179124ed5a849f9d8c09fc61ec057810e13 (diff) | |
download | bookmarks-home-41814aa14040aa038c17ee0728532b0e341c5953.tar.gz bookmarks-home-41814aa14040aa038c17ee0728532b0e341c5953.tar.bz2 bookmarks-home-41814aa14040aa038c17ee0728532b0e341c5953.zip |
Refactor BMIcon
-rw-r--r-- | extension/src/components/BMEditor.tsx | 32 | ||||
-rw-r--r-- | extension/src/components/BMIcon.tsx | 98 | ||||
-rw-r--r-- | extension/src/components/Bookmark.tsx | 54 | ||||
-rw-r--r-- | extension/src/components/RadioButtonGroup.tsx | 8 |
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" |