From 03b7ccaa5c152c8d7ed73374be8ad4d4d034845b Mon Sep 17 00:00:00 2001 From: sowgro Date: Thu, 7 Nov 2024 12:40:42 -0500 Subject: Implement sorting and refactor --- extension/src/components/Body.tsx | 60 +++++++++++++++ extension/src/components/Bookmark.tsx | 34 +++++++++ extension/src/components/FolderBody.tsx | 72 ++++++++++++++++++ extension/src/components/FolderButton.tsx | 30 ++++++++ extension/src/components/RadioButtonGroup.tsx | 37 +++++++++ extension/src/components/SettingsEditor.tsx | 105 ++++++++++++++++++++++++++ 6 files changed, 338 insertions(+) create mode 100644 extension/src/components/Body.tsx create mode 100644 extension/src/components/Bookmark.tsx create mode 100644 extension/src/components/FolderBody.tsx create mode 100644 extension/src/components/FolderButton.tsx create mode 100644 extension/src/components/RadioButtonGroup.tsx create mode 100644 extension/src/components/SettingsEditor.tsx (limited to 'extension/src/components') diff --git a/extension/src/components/Body.tsx b/extension/src/components/Body.tsx new file mode 100644 index 0000000..193e68e --- /dev/null +++ b/extension/src/components/Body.tsx @@ -0,0 +1,60 @@ +import React, {useEffect, useState} from "react"; +import SettingsEditor from "./SettingsEditor.tsx"; +import imageUrl from "../assets/settings.svg" +import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode; +import FolderBody from "./FolderBody.tsx"; +import {defaultSettings, ISettings, loadSettings, writeSettings} from "../Settings.ts"; +import {getBrowser} from "../main.tsx"; + +export const Settings = + React.createContext<[ISettings, (arg0: ISettings) => void]>([ + defaultSettings, + () => {} +]); + +/** + * A component for the full body of the application + * Also stores the trees and settings + */ +function Body() { + const [settingsOpen, setSettingsOpen] = useState(false); + const [settings, setSettings] = useState(defaultSettings); + const [bookmarkTree, setBookmarkTree] = useState([]) + const [ogBookmarkTree, setOgBookmarkTree] = useState([]) + useEffect(() => { + loadSettings().then(r => { + setSettings(r); + }) + getBrowser().bookmarks.getTree().then(t => { + setOgBookmarkTree(t); + }) + }, []) + useEffect(() => { + writeSettings(settings); + if (settings?.rootFolder) { + getBrowser().bookmarks.getSubTree(settings.rootFolder).then(t => { + setBookmarkTree(t); + }); + } else { + getBrowser().bookmarks.getTree().then(t => { + setBookmarkTree(t); + }) + } + }, [settings]); + + return ( + + {(() => {switch (settings.backgroundMode) { + case "color": return () + case "image": return () + }})()} + + + {bookmarkTree[0] && ()} + + ) +} + +export default Body \ No newline at end of file diff --git a/extension/src/components/Bookmark.tsx b/extension/src/components/Bookmark.tsx new file mode 100644 index 0000000..75badd7 --- /dev/null +++ b/extension/src/components/Bookmark.tsx @@ -0,0 +1,34 @@ +import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode; + +/** + * A component for a single bookmark + * + * @param props.data The BookmarkTreeNode with the data for the bookmark + */ +function Bookmark(props: {data: BookmarkTreeNode}) { + return( + + Bookmark icon + {props.data.title} + + ); +} + +/** + * Gets the icon for a bookmark + * + * @param u The URL of the link + * @return The URL of the icon + */ +function faviconURL(u: string | undefined) { + if (!u) return ""; + u = new URL(u).hostname.toString(); + const url = new URL('https://www.google.com/s2/favicons'); + url.searchParams.set("sz", "256"); + // u = u.split(".")[u.split(".").length-2] +"."+ u.split(".")[u.split(".").length-1] + url.searchParams.set("domain_url", u); + return url.toString(); + +} + +export default Bookmark; \ No newline at end of file diff --git a/extension/src/components/FolderBody.tsx b/extension/src/components/FolderBody.tsx new file mode 100644 index 0000000..a573a6e --- /dev/null +++ b/extension/src/components/FolderBody.tsx @@ -0,0 +1,72 @@ +import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode; +import Bookmark from "./Bookmark.tsx"; +import FolderButton from "./FolderButton.tsx"; +import {useContext} from "react"; +import {Settings} from "./Body.tsx"; + +/** + * A component that displays the contents of a bookmark folder + * + * @param props.data The BookmarkTreeNode with data for the folder + * @constructor + */ +function FolderBody (props: {data: BookmarkTreeNode}) { + const [settings, _] = useContext(Settings) + + if (!props.data.children) return; + + let content = [...props.data.children].sort(getSortFunction(settings.sort)) + if (settings.foldersFirst) { + let [bookmarks, folders] = separateFolders(content) + content = folders.concat(bookmarks) + } + + return ( +
+ {content.map(child => + child.children + ? + : + )} +
+ ) +} + +/** + * Gets the correct sort function based on the sort setting + * @param sort The sort setting state + * @return The corresponding sort function + */ +function getSortFunction(sort: "from-bookmarks" | "alphabetical" | "recent"): ((a:BookmarkTreeNode, b:BookmarkTreeNode) => number) | undefined { + switch (sort) { + case "alphabetical": return (a, b) => { + return a.title.localeCompare(b.title); + } + case "recent": return (a, b) => { + // @ts-ignore + return a.dateLastUsed - b.dateLastUsed + } + } +} + +/** + * Separate the folders and the bookmarks into two separate lists + * @param content THe bookmark list + * @returns tuple in the format [bookmarks, folders] + */ +function separateFolders(content: BookmarkTreeNode[]) { + let bookmarks = []; + let folders = []; + for (let bookmarkTreeNode of content) { + if (bookmarkTreeNode.children) { + folders.push(bookmarkTreeNode) + } else { + bookmarks.push(bookmarkTreeNode) + } + } + return [bookmarks, folders] +} + + + +export default FolderBody; \ No newline at end of file diff --git a/extension/src/components/FolderButton.tsx b/extension/src/components/FolderButton.tsx new file mode 100644 index 0000000..2d7cc9e --- /dev/null +++ b/extension/src/components/FolderButton.tsx @@ -0,0 +1,30 @@ +import BookmarkTreeNode = browser.bookmarks.BookmarkTreeNode; +import FolderBody from "./FolderBody.tsx"; +import folderIcon from "../assets/folder.svg" +import folderIconOpen from "../assets/folder_open.svg" +import {useState} from "react"; + +/** + * A component for the button used to open a bookmark folder. + * This is themed the same as a bookmark + * + * @param props.data The BookmarkTreeNode containing the data of the folder + */ +function FolderButton(props: {data: BookmarkTreeNode}) { + const [folderOpen, setFolderOpen] = useState(false); + + return( + <> + setFolderOpen(!folderOpen)}> + Folder icon + {props.data.title} + + { folderOpen + && props.data.children + && props.data.children.length > 0 + && ()} + +); +} + +export default FolderButton \ No newline at end of file diff --git a/extension/src/components/RadioButtonGroup.tsx b/extension/src/components/RadioButtonGroup.tsx new file mode 100644 index 0000000..297800d --- /dev/null +++ b/extension/src/components/RadioButtonGroup.tsx @@ -0,0 +1,37 @@ +import React, {ReactElement, useEffect, useId, useState} from "react"; + +/** + * A component for a group of radio buttons where only one can be selected + * + * @param props.children html