aboutsummaryrefslogtreecommitdiff
path: root/extension/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'extension/src/components')
-rw-r--r--extension/src/components/Body.tsx17
-rw-r--r--extension/src/components/Bookmark-backup.txt161
-rw-r--r--extension/src/components/Bookmark.tsx71
-rw-r--r--extension/src/components/FolderButton.tsx25
-rw-r--r--extension/src/components/SettingsEditor.tsx13
5 files changed, 250 insertions, 37 deletions
diff --git a/extension/src/components/Body.tsx b/extension/src/components/Body.tsx
index 6498a0a..e575d89 100644
--- a/extension/src/components/Body.tsx
+++ b/extension/src/components/Body.tsx
@@ -1,6 +1,7 @@
import React, {useEffect, useState} from "react";
import SettingsEditor from "./SettingsEditor.tsx";
-import imageUrl from "../assets/settings.svg"
+import SettingsIcon from "../assets/settings.svg?react";
+import EditIcon from "../assets/edit.svg?react";
import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode;
import FolderBody from "./FolderBody.tsx";
import {defaultSettings, ISettings, loadSettings, writeSettings} from "../Settings.ts";
@@ -33,7 +34,7 @@ function Body() {
useEffect(() => {
writeSettings(settings);
- if (settings?.rootFolder) {
+ if (settings.rootFolder) {
getBrowser().bookmarks.getSubTree(settings.rootFolder).then(t => {
setSelectedBookmarkTree(t);
});
@@ -51,9 +52,15 @@ function Body() {
case "image": return (<style>{"body {background-image: url(\"" + settings.backgroundImage + "\"); }"}</style>)
}})()}
<style>{"body > .folderBody, a {color: " + settings.foregroundColor + "; }"}</style>
- <button id="settings-button" onClick={_ => setSettingsOpen(!settingsOpen)}>
- <img alt="open settings" src={imageUrl}/>
- </button>
+ <div id={"action-area"}>
+ {settings.editMode && <span>Move mode: Drag bookmarks to change order</span>}
+ <button onClick={_ => setSettings({...settings, editMode: !settings.editMode})}>
+ <EditIcon fill={settings.editMode? "lime" : "currentColor"}/>
+ </button>
+ <button onClick={_ => setSettingsOpen(!settingsOpen)}>
+ <SettingsIcon/>
+ </button>
+ </div>
<SettingsEditor tree={fullBookmarkTree!} isOpen={[settingsOpen, setSettingsOpen]}/>
{selectedBookmarkTree[0] && (<FolderBody data={selectedBookmarkTree[0]}/>)}
</Settings.Provider>
diff --git a/extension/src/components/Bookmark-backup.txt b/extension/src/components/Bookmark-backup.txt
new file mode 100644
index 0000000..4d1eb60
--- /dev/null
+++ b/extension/src/components/Bookmark-backup.txt
@@ -0,0 +1,161 @@
+import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode;
+import React, {SyntheticEvent, useEffect} from "react";
+import {getBrowser} from "../main.tsx";
+import {Settings} from "./Body.tsx";
+import react from "@vitejs/plugin-react";
+
+/**
+ * A component for a single bookmark
+ *
+ * @param props.data The BookmarkTreeNode with the data for the bookmark
+ */
+function Bookmark(props: {data: BookmarkTreeNode}) {
+ let [favicon, setFavicon] = React.useState<string | null>(null);
+ let [iconMode, setIconMode] = React.useState<"large" | "small" | "letter">("letter");
+ let [settings, setSettings] = React.useContext(Settings);
+ let [bgColor, setBgColor] = React.useState<[number, number, number] | null>(null)
+ // let [dropRight, setDropRight] = React.useState(false);
+ // let [dropLeft, setDropLeft] = React.useState(false);
+ // let [dropCenter, setDropCenter] = React.useState(false);
+
+ useEffect(() => {
+ setBgColor([255, 0, 0])
+ // faviconURL(props.data.url).then(o => o && setFavicon(o))
+ faviconURL(props.data).then(r => {
+ if (r) {
+ setFavicon(r)
+ setIconMode("small");
+ }
+ })
+ }, []);
+
+ // useEffect(() => {
+ // console.log("signal received", dropCenter)
+ // }, [dropCenter]);
+
+ function handleImageLoad(e: SyntheticEvent<HTMLImageElement, Event>) {
+ if (e.currentTarget.naturalWidth >= 75 || favicon!.startsWith("data:image/svg+xml")) {
+ setIconMode("large")
+ }
+ }
+
+ return(
+ <div className={"bookmark"}>
+ <a draggable={settings.editMode} href={props.data.url}
+ // onDragStart={e => {
+ // console.log("dragStart")
+ // e.dataTransfer.dropEffect = "move";
+ // e.dataTransfer.setData("bm-id", props.data.id);
+ // }}
+ >
+ <div className={"icon-box " + (iconMode)}
+ style={bgColor ? {"--icon-bg": `rgba(${bgColor[0]}, ${bgColor[1]}, ${bgColor[2]}, 0.2)`} as React.CSSProperties : undefined}>
+ {favicon ? (
+ <img alt="Bookmark icon"
+ draggable={false}
+ src={favicon}
+ onLoad={handleImageLoad}
+ />
+ ) : (
+ <span className={"letter"}>{new URL(props.data.url!).hostname.charAt(0)}</span>
+ )}
+
+ </div>
+ <span>{props.data.title}</span>
+ </a>
+ {/*<div className={"drop-targets"}>*/}
+ {/* <div*/}
+ {/* className={"left"}*/}
+ {/* onDragEnter={_ => setDropLeft(true)}*/}
+ {/* onDragLeave={_ => setDropRight(false)}*/}
+ {/* onDrop={e => getBrowser().bookmarks.move(e.dataTransfer.getData("bm-id"), {parentId: props.data.parentId, index: props.data.index})}*/}
+ {/* style={dropLeft ? undefined : {visibility: "hidden"}}*/}
+ {/* // hidden={!dropLeft}*/}
+ {/* >*/}
+ {/* <div/>*/}
+ {/* </div>*/}
+ {/* <div*/}
+ {/* className={"right"}*/}
+ {/* onDragEnter={_ => setDropLeft(true)}*/}
+ {/* onDragLeave={_ => setDropRight(false)}*/}
+ {/* onDrop={e => getBrowser().bookmarks.move(e.dataTransfer.getData("bm-id"), {parentId: props.data.parentId, index: (props.data.index! + 1)})}*/}
+ {/* style={dropRight ? undefined : {visibility: "hidden"}}*/}
+ {/* // hidden={!dropRight}*/}
+ {/* >*/}
+ {/* <div/>*/}
+ {/* </div>*/}
+ {/* <div*/}
+ {/* className={"center"}*/}
+ {/* onDragOver={e => {*/}
+ {/* e.preventDefault()*/}
+ {/* // console.log("dropped")*/}
+ {/* }}*/}
+ {/* onDragEnter={e => {*/}
+ {/* e.preventDefault()*/}
+ {/* setDropCenter(true)*/}
+ {/* console.log("enter")*/}
+ {/* }}*/}
+ {/* onDragLeave={_ => {*/}
+ {/* setDropCenter(false)*/}
+ {/* console.log("exit")*/}
+ {/* }}*/}
+ {/* onDrop={e => {*/}
+ {/* e.preventDefault();*/}
+ {/* console.log("dropped")*/}
+ {/* }}*/}
+ {/* style={dropCenter ? undefined : {visibility: "hidden"}}*/}
+ {/* // hidden={!dropCenter}*/}
+ {/* >*/}
+ {/* <span>+</span>*/}
+ {/* </div>*/}
+ {/*</div>*/}
+ </div>
+ );
+}
+
+/**
+ * Gets the icon for a bookmark
+ *
+ * @param data The URL of the link
+ * @return The URL of the icon
+ */
+async function faviconURL(data: BookmarkTreeNode) {
+ let i = (await getBrowser().storage.local.get("icon-cache-"+data.id))["icon-cache-"+data.id];
+ if (i) 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 hashColor(url:string) {
+// hashCode(String)
+// }
+//
+// function hashCode(str: string) {
+// let hash = 0;
+// for (let i = 0, len = str.length; i < len; i++) {
+// let chr = str.charCodeAt(i);
+// hash = (hash << 5) - hash + chr;
+// hash |= 0; // Convert to 32bit integer
+// }
+// return hash;
+// }
+
+export default Bookmark; \ No newline at end of file
diff --git a/extension/src/components/Bookmark.tsx b/extension/src/components/Bookmark.tsx
index 839a9f5..d1c1756 100644
--- a/extension/src/components/Bookmark.tsx
+++ b/extension/src/components/Bookmark.tsx
@@ -1,7 +1,9 @@
import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode;
-import GlobeIcon from "../assets/globe.svg"
import React, {SyntheticEvent, useEffect} from "react";
import {getBrowser} from "../main.tsx";
+import {Settings} from "./Body.tsx";
+import ColorThief from 'colorthief'
+import react from "@vitejs/plugin-react";
/**
* A component for a single bookmark
@@ -9,27 +11,51 @@ import {getBrowser} from "../main.tsx";
* @param props.data The BookmarkTreeNode with the data for the bookmark
*/
function Bookmark(props: {data: BookmarkTreeNode}) {
- let [favicon, setFavicon] = React.useState(GlobeIcon);
- let [isSmall, setSmall] = React.useState(true);
+ let [favicon, setFavicon] = React.useState<string | null>(null);
+ let [iconMode, setIconMode] = React.useState<"large" | "small" | "letter">("letter");
+ let [settings, setSettings] = React.useContext(Settings);
+ let [bgColor, setBgColor] = React.useState<[number, number, number] | null>(null)
+
useEffect(() => {
- // faviconURL(props.data.url).then(o => o && setFavicon(o))
faviconURL(props.data).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(!bgColor) {
+ setBgColor(new ColorThief().getColor(e.currentTarget))
+ }
+ }
+
return(
- <a className="bookmark draggable" href={props.data.url}>
- <div className={"icon-box" + (isSmall ? " small" : "")}>
- <img alt="Bookmark icon"
- src={favicon}
- onLoad={(e) => setSmall(e.currentTarget.naturalWidth < 75 && !favicon.startsWith("data:image/svg+xml"))}
- ></img>
- </div>
- <span>{props.data.title}</span>
- </a>
+ <div className={"bookmark"}>
+ <a draggable={settings.editMode} href={props.data.url}>
+ <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.data.url!);
+ if (!bgColor) {
+ setBgColor(hashStringToColor(url.hostname));
+ }
+ 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!}/>)
+ }
+ }})()}
+ </div>
+ <span>{props.data.title}</span>
+ </a>
+ </div>
);
}
@@ -42,12 +68,13 @@ function Bookmark(props: {data: BookmarkTreeNode}) {
async function faviconURL(data: BookmarkTreeNode) {
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()) : GlobeIcon;
+ let imgData = resp.ok ? await toDataURL(url.toString()) : null;
getBrowser().storage.local.set({["icon-cache-"+data.id]: imgData});
return imgData;
}
@@ -64,4 +91,20 @@ function toDataURL(url:string):string {
}))
}
+function djb2(str: string){
+ var 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] {
+ var hash = djb2(str);
+ var r = (hash & 0xFF0000) >> 16;
+ var g = (hash & 0x00FF00) >> 8;
+ var b = hash & 0x0000FF;
+ return [r, g, b];
+}
+
export default Bookmark; \ No newline at end of file
diff --git a/extension/src/components/FolderButton.tsx b/extension/src/components/FolderButton.tsx
index 42e5d60..2b59d56 100644
--- a/extension/src/components/FolderButton.tsx
+++ b/extension/src/components/FolderButton.tsx
@@ -1,8 +1,9 @@
import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode;
import FolderBody from "./FolderBody.tsx";
-import folderIcon from "../assets/folder.svg"
-import folderIconOpen from "../assets/folder_open.svg"
+import FolderIcon from "../assets/folder.svg?react"
+import FolderIconOpen from "../assets/folder_open.svg?react"
import {useState} from "react";
+import bookmark from "./Bookmark.tsx";
/**
* A component for the button used to open a bookmark folder.
@@ -15,16 +16,18 @@ function FolderButton(props: {data: BookmarkTreeNode}) {
return(
<>
- <a className="bookmark draggable" onClick={() => setFolderOpen(!folderOpen)}>
- <div className="icon-box">
- <img alt="Folder icon" src={folderOpen ? folderIconOpen : folderIcon}/>
- </div>
- <span>{props.data.title}</span>
- </a>
+ <div className={"bookmark"}>
+ <a onClick={() => setFolderOpen(!folderOpen)}>
+ <div className="icon-box">
+ {folderOpen ? <FolderIconOpen/> : <FolderIcon/>}
+ </div>
+ <span>{props.data.title}</span>
+ </a>
+ </div>
{folderOpen
- && props.data.children
- && props.data.children.length > 0
- && (<FolderBody data={props.data}/>)}
+ && props.data.children
+ && props.data.children.length > 0
+ && (<FolderBody data={props.data}/>)}
</>
);
}
diff --git a/extension/src/components/SettingsEditor.tsx b/extension/src/components/SettingsEditor.tsx
index edd99f9..d11158e 100644
--- a/extension/src/components/SettingsEditor.tsx
+++ b/extension/src/components/SettingsEditor.tsx
@@ -1,8 +1,9 @@
import RadioButtonGroup from "./RadioButtonGroup.tsx";
import React, {useContext} from "react";
-import imageUrl from "../assets/close.svg"
+import CloseIcon from "../assets/close.svg?react"
import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode;
import {Settings} from "./Body.tsx";
+import {getBrowser} from "../main.tsx";
/**
* A component for the settings sidebar
@@ -17,7 +18,7 @@ function SettingsEditor(props: {tree: BookmarkTreeNode[], isOpen: [boolean, Rea
return (
<div id="settings-menu" className={open ? "open" : "closed"}>
<button id="settings-close" onClick={_ => setOpen(false)}>
- <img alt="close settings" src={imageUrl}/>
+ <CloseIcon/>
</button>
<h1>Settings</h1>
@@ -28,7 +29,6 @@ function SettingsEditor(props: {tree: BookmarkTreeNode[], isOpen: [boolean, Rea
}}>
<option value={"from-bookmarks"}>From Bookmarks</option>
<option value={"alphabetical"}>Alphabetical</option>
- <option value={"frequency"}>Frequency</option>
<option value={"recent"}>Recently used</option>
</RadioButtonGroup>
<br/>
@@ -79,10 +79,9 @@ function SettingsEditor(props: {tree: BookmarkTreeNode[], isOpen: [boolean, Rea
)}
</select>
- {/*<br/>*/}
- {/*<span>sort: {settings.sort}</span>*/}
- {/*<span>rootFolder: {settings.rootFolder}</span>*/}
- {/*<span>bgmode: {settings.backgroundMode}</span>*/}
+ <h3>Icon Cache</h3>
+ <button onClick={_ => getBrowser().storage.local.clear()}>Clear Icon Cache</button>
+
</div>
)
}