diff --git a/manifest.json b/manifest.json index f8f2e5a..21ea59a 100644 --- a/manifest.json +++ b/manifest.json @@ -7,14 +7,21 @@ "newtab": "frontend/utab.html" }, "permissions": [ - "storage" + "storage", + "unlimitedStorage" + ], + "optional_permissions": [], + "host_permissions": [ + "https://zenquotes.io/*" ], "icons": { "128": "icon.png" }, "incognito": "split", - "background": { - "service_worker": "worker/main.js", - "type": "module" + "browser_specific_settings": { + "gecko": { + "id": "addon@example.com", + "strict_min_version": "58.0" + } } } \ No newline at end of file diff --git a/package.json b/package.json index a11e269..fa3d14c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "copy-static": "mkdir dist/frontend && cp -r static/* dist/frontend/", "build-frontend": "esbuild src/frontend/index.tsx --bundle --outfile=dist/frontend/index.js --minify --sourcemap", "build-worker": "esbuild src/worker/main.ts --bundle --outfile=dist/worker/main.js --minify --sourcemap", - "clean": "rm -rf ./dist" + "clean": "rm -rf ./dist", + "type-check": "tsc --noEmit" }, "author": "", "license": "ISC", diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx index f7c530a..e6e70cc 100644 --- a/src/frontend/App.tsx +++ b/src/frontend/App.tsx @@ -2,13 +2,10 @@ import React, { useEffect } from 'react'; import { NewTab } from './components/NewTab'; import { useAtom } from 'jotai'; import { backgroundImageState } from './state/backgroundImage.state'; -import { IpcService } from './services/ipcService'; export const App: React.FC = () => { const [backgroundImage] = useAtom(backgroundImageState) useEffect(() => { document.body.style.backgroundImage = `url(${backgroundImage})` }, [backgroundImage]); - IpcService.writeFile({ path: 'testpath', fileContent: 'testcontent' }).then(console.log) - return ; }; \ No newline at end of file diff --git a/src/frontend/components/NewTab.tsx b/src/frontend/components/NewTab.tsx index 97b0366..8910587 100644 --- a/src/frontend/components/NewTab.tsx +++ b/src/frontend/components/NewTab.tsx @@ -3,7 +3,7 @@ import { useAtom } from 'jotai' import { WidgetFactory } from './WidgetFactory'; import { gridState } from '../state/grid.state'; import { widgetState } from '../state/widget.state'; -import { GlobalSettingsPanel } from './global-settings/GlobalSettingsPanel'; +import { GlobalSettingsPanel } from './widgets/globalsettings/GlobalSettingsPanel'; import { isIncognitoState } from '../state/incogntio.state'; import { GridOverlay } from './grid-overlay/GridOverlay'; diff --git a/src/frontend/components/Widget.tsx b/src/frontend/components/Widget.tsx index 8a26140..76b0f83 100644 --- a/src/frontend/components/Widget.tsx +++ b/src/frontend/components/Widget.tsx @@ -1,24 +1,23 @@ import React, { useRef, useState, useEffect, ReactElement, useCallback, startTransition } from 'react'; import { WidgetData } from '../types'; -import { CogwheelIcon } from './components/CogwheelIcon'; +import { CogwheelIcon } from './reusable-components/CogwheelIcon'; import { useAtomValue } from 'jotai'; import { editState } from '../state/edit.state'; -import { CloseButton } from './components/Closebutton'; -import { ModalOverlay } from './components/ModalOverlay'; -import { SettingsComponent } from './components/SettingsComponent'; +import { CloseButton } from './reusable-components/Closebutton'; +import { ModalOverlay } from './reusable-components/ModalOverlay'; +import { SettingsComponent } from './reusable-components/SettingsComponent'; +import MovieEditIcon from '@mui/icons-material/MovieEdit'; interface WidgetProps { widgetData: WidgetData; gridSize: { width: number; height: number }; children: React.ReactNode; - settingsContent: ReactElement; + settingsContent?: ReactElement; updateWidget: (id: string, data: Partial) => void; removeWidget: (id: string) => void; - disableRemove?: boolean; - disableEdit?: boolean; } -export const Widget: React.FC = ({ widgetData, gridSize, children, settingsContent, updateWidget, removeWidget, disableEdit, disableRemove }) => { +export const Widget: React.FC = ({ widgetData, gridSize, children, settingsContent, updateWidget, removeWidget }) => { const widgetRef = useRef(null); const modalRef = useRef(null); @@ -62,11 +61,11 @@ export const Widget: React.FC = ({ widgetData, gridSize, children, const rect = widgetRef.current.getBoundingClientRect(); const gridX = snapToGrid(rect.left, gridSize.width); const gridY = snapToGrid(rect.top, gridSize.height); - if (gridX !== widgetData.y || gridY !== widgetData.x) { + if (gridX !== widgetData.x || gridY !== widgetData.Y) { // overwrite the values so prevent a visual "bounce" - widgetData.y = gridX; - widgetData.x = gridY; - updateWidget(widgetData.id, { y: gridX, x: gridY }); + widgetData.x = gridX; + widgetData.y = gridY; + updateWidget(widgetData.id, { y: gridY, x: gridX }); } } }; @@ -101,8 +100,8 @@ export const Widget: React.FC = ({ widgetData, gridSize, children, useEffect(() => { if (widgetRef.current && !isDragging) { - widgetRef.current.style.left = `${widgetData.y * gridSize.width}px`; - widgetRef.current.style.top = `${widgetData.x * gridSize.height}px`; + widgetRef.current.style.left = `${widgetData.x * gridSize.width}px`; + widgetRef.current.style.top = `${widgetData.y * gridSize.height}px`; widgetRef.current.style.width = `${(widgetData.width) * gridSize.width}px`; widgetRef.current.style.height = `${(widgetData.height) * gridSize.height}px`; } @@ -135,7 +134,7 @@ export const Widget: React.FC = ({ widgetData, gridSize, children, className="settings-button" onClick={() => setSettingsOpen(true)} > - + } {children} diff --git a/src/frontend/components/WidgetFactory.tsx b/src/frontend/components/WidgetFactory.tsx index fda6a7d..8c4f0f9 100644 --- a/src/frontend/components/WidgetFactory.tsx +++ b/src/frontend/components/WidgetFactory.tsx @@ -1,13 +1,15 @@ import React, { useCallback, useEffect, useState } from 'react'; import { GlobalSettings, WidgetData, WidgetExport, WidgetTypeValues } from '../types'; import { Widget } from './Widget'; -import { SearchWidget } from './search-widget/SearchWidget'; -import { ClockWidget } from './clock-widget/ClockWidget'; -import { GlobalSettingsWidget } from './global-settings/GlobalSettingsWidget'; import { useAtom } from 'jotai'; import { widgetState } from '../state/widget.state'; import { settingsService } from '../services/settingsService'; +import { QuoteWidget } from './widgets/quote/QuotesWidget'; +import { SearchWidget } from './widgets/search/SearchWidget'; +import { ClockWidget } from './widgets/clock/ClockWidget'; +import { GlobalSettingsWidget } from './widgets/globalsettings/GlobalSettingsWidget'; + export interface WidgetFactoryProps { widget: WidgetData; grid: GlobalSettings['grid']; @@ -16,7 +18,8 @@ export interface WidgetFactoryProps { const WidgetMap: { [key in WidgetTypeValues]: WidgetExport } = { 'clock': ClockWidget, 'search': SearchWidget, - 'globalsettings': GlobalSettingsWidget + 'globalsettings': GlobalSettingsWidget, + 'quote': QuoteWidget } export const WidgetFactory: React.FC = ({ widget, grid }) => { @@ -56,7 +59,7 @@ export const WidgetFactory: React.FC = ({ widget, grid }) => const Wdgt = WidgetMap[widget.type] const component = - const settings = Wdgt.settings({ widget, updateWidget: updateWidgets }) + const settings = Wdgt.settings ? Wdgt.settings({ widget, updateWidget: updateWidgets }) : undefined return ( ) => void - children: ReactElement | ReactElement[] - settingsOpen?: [boolean, Dispatch>] -} - -export function SettingsComponent({ - widgetData, - updateWidget, - children -}: GenericSettingsProps) { - const [isIncognitoAllowed] = useAtom(incognitoAllowedState) - - return <> - {children} - - - - {isIncognitoAllowed && - - } - - -} \ No newline at end of file diff --git a/src/frontend/components/global-settings/tab-contents/BackgroundSettings.tsx b/src/frontend/components/global-settings/tab-contents/BackgroundSettings.tsx deleted file mode 100644 index b2dc787..0000000 --- a/src/frontend/components/global-settings/tab-contents/BackgroundSettings.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { TextField } from "@mui/material"; -import { useAtom } from "jotai"; -import { backgroundImageState } from "../../../state/backgroundImage.state"; - -export function BackgroundSettings() { - const [backgroundImage, setBackgroundImage] = useAtom(backgroundImageState) - - return (<> - setBackgroundImage(e.target.value)} - placeholder="Enter image URL" - /> - ) - -} \ No newline at end of file diff --git a/src/frontend/components/global-settings/tab-contents/UtabSettings.tsx b/src/frontend/components/global-settings/tab-contents/UtabSettings.tsx deleted file mode 100644 index b1d5000..0000000 --- a/src/frontend/components/global-settings/tab-contents/UtabSettings.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Switch } from "@mui/material"; -import { useAtom } from "jotai"; -import { editState } from "../../../state/edit.state"; -import { gridState } from "../../../state/grid.state"; -import { widgetState } from "../../../state/widget.state"; -import { WidgetTypeValues } from "../../../types"; -import { intitialWidgetData } from "../../../state/defaults"; -import { useCallback } from "react"; -import { incognitoAllowedState } from "../../../state/incogntio.state"; - -export function UtabSettings() { - const [editEnabled, setEditEnabled] = useAtom(editState) - const [grid, setGrid] = useAtom(gridState) - const [widgets, setWidgets] = useAtom(widgetState) - - const createWidget = useCallback( - (type: WidgetTypeValues) => { - const data = intitialWidgetData(type) - const updatedWidgets = [...widgets, data] - setWidgets(updatedWidgets) - }, - [widgets, setWidgets] - ); - - const [isIncognitoAllowed] = useAtom(incognitoAllowedState) - - return (<> - Incognito enabled: {isIncognitoAllowed?'yes':'no'} - - - - - ) -} \ No newline at end of file diff --git a/src/frontend/components/components/Closebutton.tsx b/src/frontend/components/reusable-components/Closebutton.tsx similarity index 56% rename from src/frontend/components/components/Closebutton.tsx rename to src/frontend/components/reusable-components/Closebutton.tsx index 24e149a..fef3ad4 100644 --- a/src/frontend/components/components/Closebutton.tsx +++ b/src/frontend/components/reusable-components/Closebutton.tsx @@ -1,4 +1,5 @@ import React, { MouseEvent } from "react" +import CloseIcon from '@mui/icons-material/Close'; type ButtonProps = { onClick: React.MouseEventHandler @@ -6,6 +7,6 @@ type ButtonProps = { export function CloseButton({ onClick }: ButtonProps) { return ( - + ) } \ No newline at end of file diff --git a/src/frontend/components/components/CogwheelIcon.tsx b/src/frontend/components/reusable-components/CogwheelIcon.tsx similarity index 100% rename from src/frontend/components/components/CogwheelIcon.tsx rename to src/frontend/components/reusable-components/CogwheelIcon.tsx diff --git a/src/frontend/components/components/ModalOverlay.tsx b/src/frontend/components/reusable-components/ModalOverlay.tsx similarity index 99% rename from src/frontend/components/components/ModalOverlay.tsx rename to src/frontend/components/reusable-components/ModalOverlay.tsx index fba2508..5663673 100644 --- a/src/frontend/components/components/ModalOverlay.tsx +++ b/src/frontend/components/reusable-components/ModalOverlay.tsx @@ -7,7 +7,6 @@ interface ModalOverlayProps { } export function ModalOverlay({visible, setVisible, children}: ModalOverlayProps) { - const modalRef = useRef(null); return visible &&
diff --git a/src/frontend/components/reusable-components/SettingsComponent.tsx b/src/frontend/components/reusable-components/SettingsComponent.tsx new file mode 100644 index 0000000..ca33c15 --- /dev/null +++ b/src/frontend/components/reusable-components/SettingsComponent.tsx @@ -0,0 +1,136 @@ +import { Dispatch, ReactElement, useState } from "react" +import { WidgetData } from "../../types" +import { SetStateAction, useAtom } from "jotai" +import { incognitoAllowedState } from "../../state/incogntio.state" +import { Box, List, ListItem, ListItemIcon, ListItemText, MenuItem, Select, Switch, Tab, Tabs, TextField, Typography } from "@mui/material" + +import BorderHorizontalIcon from '@mui/icons-material/BorderHorizontal'; +import BorderVerticalIcon from '@mui/icons-material/BorderVertical'; +import BorderOuterIcon from '@mui/icons-material/BorderOuter'; +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import { TabPanel } from "./TabPanel" + +type GenericSettingsProps = { + widgetData: WidgetData + updateWidget: (id: string, data: Partial) => void + children?: ReactElement | ReactElement[] + settingsOpen?: [boolean, Dispatch>] +} + +export function SettingsComponent({ + widgetData, + updateWidget, + children +}: GenericSettingsProps) { + const [isIncognitoAllowed] = useAtom(incognitoAllowedState) + + const [tab, setTab] = useState(0); + + const handleChange = (event: React.SyntheticEvent, newValue: number) => { + setTab(newValue); + }; + + return + + + {children != undefined && } + + + + + + + + + + + updateWidget(widgetData.id, { width: Math.max(1, parseInt(e.target.value)) })} + id="width-number" + type="number" + variant="standard" + slotProps={{ + inputLabel: { + shrink: true, + }, + }} + /> + + + + + + + + updateWidget(widgetData.id, { height: Math.max(1, parseInt(e.target.value)) })} + type="number" + variant="standard" + slotProps={{ + inputLabel: { + shrink: true, + }, + }} + /> + + + + + + + + updateWidget(widgetData.id, { background: !widgetData.background })} + /> + + + + + + + + {isIncognitoAllowed ? ( + + ):( + + Enable extension in Incognito mode to edit + + )} + + + + + {children != undefined && + + {children} + + } + +} \ No newline at end of file diff --git a/src/frontend/components/reusable-components/TabPanel.tsx b/src/frontend/components/reusable-components/TabPanel.tsx new file mode 100644 index 0000000..c7ac7a7 --- /dev/null +++ b/src/frontend/components/reusable-components/TabPanel.tsx @@ -0,0 +1,28 @@ +import { Box, Typography } from "@mui/material"; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +export function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} \ No newline at end of file diff --git a/src/frontend/components/clock-widget/ClockWidget.tsx b/src/frontend/components/widgets/clock/ClockWidget.tsx similarity index 97% rename from src/frontend/components/clock-widget/ClockWidget.tsx rename to src/frontend/components/widgets/clock/ClockWidget.tsx index 32f4f2c..46e1a07 100644 --- a/src/frontend/components/clock-widget/ClockWidget.tsx +++ b/src/frontend/components/widgets/clock/ClockWidget.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { ClockWidgetData, WidgetExport } from '../../types'; +import { ClockWidgetData, WidgetExport } from '../../../types'; interface ClockWidgetProps { widget: ClockWidgetData; diff --git a/src/frontend/components/global-settings/GlobalSettingsPanel.tsx b/src/frontend/components/widgets/globalsettings/GlobalSettingsPanel.tsx similarity index 62% rename from src/frontend/components/global-settings/GlobalSettingsPanel.tsx rename to src/frontend/components/widgets/globalsettings/GlobalSettingsPanel.tsx index 96fd71e..dfa4c3f 100644 --- a/src/frontend/components/global-settings/GlobalSettingsPanel.tsx +++ b/src/frontend/components/widgets/globalsettings/GlobalSettingsPanel.tsx @@ -1,16 +1,17 @@ import { useAtom } from "jotai"; -import { ModalOverlay } from "../components/ModalOverlay"; +import { ModalOverlay } from "../../reusable-components/ModalOverlay"; import { useState } from "react"; -import { globalSettingsOpenState } from "../../state/globalSettingsOpen.state"; -import { Box, Tab, Tabs, Typography } from "@mui/material"; +import { globalSettingsOpenState } from "../../../state/globalSettingsOpen.state"; +import { Box, Tab, Tabs } from "@mui/material"; import { UtabSettings } from "./tab-contents/UtabSettings"; import { BackgroundSettings } from "./tab-contents/BackgroundSettings"; import { SearchEngineSettings } from "./tab-contents/SearchEngineSettings"; +import { TabPanel } from "../../reusable-components/TabPanel"; const settingsComponents = { 'uTab': UtabSettings, 'Background': BackgroundSettings, - 'Search': SearchEngineSettings + 'Search': SearchEngineSettings, } export function GlobalSettingsPanel() { @@ -29,7 +30,7 @@ export function GlobalSettingsPanel() { variant="scrollable" value={tab} onChange={handleChange} - aria-label="Vertical tabs example" + aria-label="Vertical tabs" sx={{ borderRight: 1, borderColor: 'divider', textAlign: 'end' }} > {Object.keys(settingsComponents).map(key => )} @@ -46,28 +47,3 @@ export function GlobalSettingsPanel() { ) } -interface TabPanelProps { - children?: React.ReactNode; - index: number; - value: number; -} - -function TabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; - - return ( - - ); -} \ No newline at end of file diff --git a/src/frontend/components/global-settings/GlobalSettingsWidget.tsx b/src/frontend/components/widgets/globalsettings/GlobalSettingsWidget.tsx similarity index 71% rename from src/frontend/components/global-settings/GlobalSettingsWidget.tsx rename to src/frontend/components/widgets/globalsettings/GlobalSettingsWidget.tsx index e2b7f43..5f28147 100644 --- a/src/frontend/components/global-settings/GlobalSettingsWidget.tsx +++ b/src/frontend/components/widgets/globalsettings/GlobalSettingsWidget.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { CogwheelIcon } from '../components/CogwheelIcon'; +import { CogwheelIcon } from '../../reusable-components/CogwheelIcon'; import { useAtom } from 'jotai'; -import { WidgetData, WidgetExport } from '../../types'; -import { globalSettingsOpenState } from '../../state/globalSettingsOpen.state'; +import { WidgetData, WidgetExport } from '../../../types'; +import { globalSettingsOpenState } from '../../../state/globalSettingsOpen.state'; interface GlobalSettingsProps { addWidget: (widget: WidgetData) => void, @@ -17,6 +17,5 @@ const GlobalSettingsComponent: React.FC = ({ addWidget }) = }; export const GlobalSettingsWidget: WidgetExport = { - settings: () => <>, widget: GlobalSettingsComponent } \ No newline at end of file diff --git a/src/frontend/components/widgets/globalsettings/tab-contents/BackgroundSettings.tsx b/src/frontend/components/widgets/globalsettings/tab-contents/BackgroundSettings.tsx new file mode 100644 index 0000000..bd00e22 --- /dev/null +++ b/src/frontend/components/widgets/globalsettings/tab-contents/BackgroundSettings.tsx @@ -0,0 +1,19 @@ +import { Box, TextField } from "@mui/material"; +import { useAtom } from "jotai"; +import { backgroundImageState } from "../../../../state/backgroundImage.state"; + +export function BackgroundSettings() { + const [backgroundImage, setBackgroundImage] = useAtom(backgroundImageState) + + return ( + + setBackgroundImage(e.target.value)} + placeholder="Enter image URL" + /> + + ) + +} \ No newline at end of file diff --git a/src/frontend/components/global-settings/tab-contents/SearchEngineSettings.tsx b/src/frontend/components/widgets/globalsettings/tab-contents/SearchEngineSettings.tsx similarity index 95% rename from src/frontend/components/global-settings/tab-contents/SearchEngineSettings.tsx rename to src/frontend/components/widgets/globalsettings/tab-contents/SearchEngineSettings.tsx index 2afbe78..abe4028 100644 --- a/src/frontend/components/global-settings/tab-contents/SearchEngineSettings.tsx +++ b/src/frontend/components/widgets/globalsettings/tab-contents/SearchEngineSettings.tsx @@ -1,6 +1,6 @@ import { Box, Button, IconButton, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; import { useAtom } from "jotai"; -import { availableEnginesState } from "../../../state/searchEngines.state"; +import { availableEnginesState } from "../../../../state/searchEngines.state"; import DeleteIcon from '@mui/icons-material/Delete'; import AddIcon from '@mui/icons-material/Add'; import TextField from '@mui/material/TextField'; @@ -19,8 +19,6 @@ export function SearchEngineSettings() { [aliasInput]: searchStringInput } - console.log(newEngines) - setAvailableSearchEngines(newEngines) setAliasInput('') setSearchStringInput('') @@ -38,7 +36,7 @@ export function SearchEngineSettings() { return ( - +
Alias @@ -78,6 +76,5 @@ export function SearchEngineSettings() {
- ) } \ No newline at end of file diff --git a/src/frontend/components/widgets/globalsettings/tab-contents/UtabSettings.tsx b/src/frontend/components/widgets/globalsettings/tab-contents/UtabSettings.tsx new file mode 100644 index 0000000..e75e08a --- /dev/null +++ b/src/frontend/components/widgets/globalsettings/tab-contents/UtabSettings.tsx @@ -0,0 +1,58 @@ +import { Box, List, ListItem, ListItemIcon, ListItemText, Switch } from "@mui/material"; +import { useAtom } from "jotai"; +import { editState } from "../../../../state/edit.state"; +import { gridState } from "../../../../state/grid.state"; +import { widgetState } from "../../../../state/widget.state"; +import { WidgetTypeValues } from "../../../../types"; +import { intitialWidgetData } from "../../../../state/defaults"; +import { useCallback } from "react"; +import DesignServicesIcon from '@mui/icons-material/DesignServices'; +import GridOnIcon from '@mui/icons-material/GridOn'; + +export function UtabSettings() { + const [editEnabled, setEditEnabled] = useAtom(editState) + const [grid, setGrid] = useAtom(gridState) + const [widgets, setWidgets] = useAtom(widgetState) + + const createWidget = useCallback( + (type: WidgetTypeValues) => { + const data = intitialWidgetData(type) + const updatedWidgets = [...widgets, data] + setWidgets(updatedWidgets) + }, + [widgets, setWidgets] + ); + + return ( + + + + + + + + setEditEnabled(!editEnabled)} + /> + + + + + + + + setGrid({ ...grid, show: !grid.show })} + /> + + + + + + + ) +} \ No newline at end of file diff --git a/src/frontend/components/widgets/quote/QuotesWidget.tsx b/src/frontend/components/widgets/quote/QuotesWidget.tsx new file mode 100644 index 0000000..77cdbca --- /dev/null +++ b/src/frontend/components/widgets/quote/QuotesWidget.tsx @@ -0,0 +1,64 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { QuoteWidgetData, WidgetExport } from '../../../types'; +import { Box } from '@mui/material'; + +type Quote = { + quote: string, + author: string, +} + +interface QuoteWidgetProps { + widget: QuoteWidgetData; + updateWidget: (id: string, update: Partial) => void +} + +const getQuote = async (): Promise => { + try { + const response = await fetch("https://zenquotes.io/?api=random", { method: "GET", mode: "cors" }); + var data = await response.json(); + return { + quote: data[0].q, + author: data[0].a + }; + }catch(e){ + return { + quote: String(e), + author: "https://zenquotes.io/?api=random" + } + } +} + +export const QuoteWidgetComponent: React.FC = ({ widget, updateWidget }) => { + + const [quote, setQuote] = useState() + + const updateQuote = useCallback(async () => { + const quote = await getQuote() + console.log("QUOTE", quote) + setQuote(quote) + updateWidget(widget.id, { lastRefresh: Date.now() }) + }, []) + + if (!quote || (Date.now() - widget.lastRefresh > widget.refreshInterval)) { + updateQuote() + } + + return <> +
{quote?.quote}
+
{quote?.author}
+ ; +}; + +export const QuoteWidgetSettings = ({ widget, updateWidget }: QuoteWidgetProps) => { + + return ( + + + + ); +}; + +export const QuoteWidget: WidgetExport = { + widget: QuoteWidgetComponent, + settings: QuoteWidgetSettings +} \ No newline at end of file diff --git a/src/frontend/components/search-widget/SearchWidget.tsx b/src/frontend/components/widgets/search/SearchWidget.tsx similarity index 88% rename from src/frontend/components/search-widget/SearchWidget.tsx rename to src/frontend/components/widgets/search/SearchWidget.tsx index ca06861..51cfb65 100644 --- a/src/frontend/components/search-widget/SearchWidget.tsx +++ b/src/frontend/components/widgets/search/SearchWidget.tsx @@ -1,8 +1,7 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { GlobalSettings, SearchWidgetData, WidgetExport } from '../../types'; -import { availableEnginesState } from '../../state/searchEngines.state'; -import { useAtom, useAtomValue } from 'jotai'; -import { TextField } from '@mui/material'; +import React, { useCallback, useEffect } from 'react'; +import { SearchWidgetData, WidgetExport } from '../../../types'; +import { availableEnginesState } from '../../../state/searchEngines.state'; +import { useAtom } from 'jotai'; interface SearchWidgetProps { widget: SearchWidgetData; diff --git a/src/frontend/services/ipcService.ts b/src/frontend/services/ipcService.ts index d76f089..2ea03bc 100644 --- a/src/frontend/services/ipcService.ts +++ b/src/frontend/services/ipcService.ts @@ -1,4 +1,3 @@ import { FrontendBindings } from '../../shared/RPC' export const IpcService = FrontendBindings - diff --git a/src/frontend/state/defaults.ts b/src/frontend/state/defaults.ts index 8a4517e..bc7d0d2 100644 --- a/src/frontend/state/defaults.ts +++ b/src/frontend/state/defaults.ts @@ -4,41 +4,63 @@ export const intitialWidgetData = (type: WidgetTypeValues): WidgetTypeMap[typeof switch (type) { case 'clock': return { + type, + id: `${type}-${Date.now()}`, + showIn: 'both', + height: 6, width: 12, - y: 30, - x: 12, + x: 30, + y: 3, + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, showSeconds: false, use24Hour: true, - type: 'clock', background: true, - showIn: 'both', - id: `${type}-${Date.now()}`, } case 'search': return { + type, + id: `${type}-${Date.now()}`, + showIn: 'both', + height: 2, width: 20, - y: 26, - x: 12, - id: `${type}-${Date.now()}`, - type: 'search', - engines: ['Google'], + x: 26, + y: 17, background: true, - showIn: 'both', + + + engines: ['Google'], } case 'globalsettings': return { + type, + id: `${type}-${Date.now()}`, + showIn: 'both', + background: false, height: 2, width: 2, y: 2, x: 2, - id: 'globalsettings', - type: 'globalsettings', permanent: true, - showIn: 'both' + } + case 'quote': + return { + type, + id: `${type}-${Date.now()}`, + showIn: 'both', + + background: false, + height: 6, + width: 72, + y: 30, + x: 0, + + showAuthor: true, + refreshInterval: 86400000, // 1 day + lastRefresh: 0 } } } @@ -51,9 +73,10 @@ export const DEFAULT_SETTINGS: GlobalSettings = { }, backgroundUrl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Cat_August_2010-4.jpg/2880px-Cat_August_2010-4.jpg', widgets: [ - intitialWidgetData('search'), + intitialWidgetData('search'), intitialWidgetData('clock'), - intitialWidgetData('globalsettings') + intitialWidgetData('globalsettings'), + intitialWidgetData('quote') ], editEnabled: true, searchEngines: { diff --git a/src/frontend/types.ts b/src/frontend/types.ts index 0e229dc..2757087 100644 --- a/src/frontend/types.ts +++ b/src/frontend/types.ts @@ -21,25 +21,32 @@ export type ClockWidgetData = WidgetData<'clock'> & { timezone: string; } +export type QuoteWidgetData = WidgetData<'quote'> & { + showAuthor: boolean; + refreshInterval: number; + lastRefresh: number; +} + export type GlobalSettingsData = WidgetData<'globalsettings'> & { permanent: true; showIn: 'both' } export type WidgetTypeValues = ( - SearchWidgetData | - ClockWidgetData | - GlobalSettingsData + | SearchWidgetData + | ClockWidgetData + | GlobalSettingsData + | QuoteWidgetData )['type'] -export type WidgetTypeMap = { - [Key in WidgetType]: WidgetData -} & { +export type WidgetTypeMap = { 'clock': ClockWidgetData, 'search': SearchWidgetData, 'globalsettings': GlobalSettingsData, + 'quote': QuoteWidgetData } + export interface GlobalSettings { grid: { show: boolean; @@ -54,5 +61,5 @@ export interface GlobalSettings { export type WidgetExport, S = (...a:any[]) =>JSX.Element > = { widget: W, - settings: S + settings?: S } \ No newline at end of file diff --git a/src/shared/RPC.ts b/src/shared/RPC.ts index f9eb467..107fce1 100644 --- a/src/shared/RPC.ts +++ b/src/shared/RPC.ts @@ -1,31 +1,37 @@ import { ExposedEndpoints } from "../worker/Endpoints" -type NamedFunction = { name: NameT } & ((a: ArgT) => RetT) +type NamedFunction = { name: NameT } & ((...args: ArgT) => RetT); type Promised = T extends Promise ? T : Promise -const wrapIpc = (fn: NamedFunction): NamedFunction ? R : Promise, N> => { - const func = function (message: A) { +const wrapIpc = ( + fn: NamedFunction +): NamedFunction, NameT> => { + const func = function (...args: ArgT): Promise { return new Promise((res, rej) => { chrome.runtime.sendMessage( { endpoint: fn.name, - args: message + args, }, - (response) => { res(response) } + (response) => { res(response); } ) }) } - Object.defineProperty(fn, "name", { value: fn.name }); - return func as NamedFunction ? R : Promise, N> -} + Object.defineProperty(func, "name", { value: fn.name }); + return func as NamedFunction, NameT>; +}; export type RPCs = typeof ExposedEndpoints export type FrontendIpcService = { - [endpoint in keyof RPCs]: NamedFunction[0], Promised>, string> + [endpoint in keyof RPCs]: NamedFunction< + Parameters, + Promised>, + endpoint + > } export const FrontendBindings: FrontendIpcService = Object.entries(ExposedEndpoints) - .map(([endpoint, fn]) => ({ [endpoint]: wrapIpc(fn) })) - .reduce((prev, curr) => Object.assign(prev, curr), {}) as FrontendIpcService + .map(([endpoint, fn]) => ({ [endpoint]: wrapIpc(fn as any) })) + .reduce((prev, curr) => Object.assign(prev, curr), {}) as FrontendIpcService \ No newline at end of file diff --git a/src/worker/Endpoints.ts b/src/worker/Endpoints.ts index e1f404d..81897f6 100644 --- a/src/worker/Endpoints.ts +++ b/src/worker/Endpoints.ts @@ -1,6 +1,9 @@ export const ExposedEndpoints = { - writeFile: (message: { path: string, fileContent: string }) => { - console.log("writing", message.fileContent, "to", message.path) + writeFile: (filename: string, fileContent: string) => { return "OK" + }, + + writeFileAsync: async (path: string, fileContent: string) => { + return "async OK" } } as const; \ No newline at end of file diff --git a/src/worker/main.ts b/src/worker/main.ts index e4c8ce9..1c59bee 100644 --- a/src/worker/main.ts +++ b/src/worker/main.ts @@ -1,7 +1,13 @@ import { ExposedEndpoints } from "./Endpoints"; chrome.runtime.onMessage.addListener((param: { endpoint: keyof typeof ExposedEndpoints, args: any }, sender, sendReponse) => { - const returnValue = ExposedEndpoints[param.endpoint].apply({}, param.args) + const returnValue = (ExposedEndpoints[param.endpoint] as Function).apply(undefined, param.args) + if(returnValue instanceof Promise){ + returnValue.then(value => { + sendReponse(value) + }) + return true; + } if(returnValue !== undefined){ sendReponse(returnValue) return true; diff --git a/static/styles.css b/static/styles.css index 0652843..f23067d 100644 --- a/static/styles.css +++ b/static/styles.css @@ -26,6 +26,11 @@ body { border-radius: 5px; } +.globalsettings { + overflow: visible !important; + padding: 0px !important; +} + .aero { background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(10px); @@ -138,8 +143,7 @@ body { padding: 20px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); - min-width: 400px; - max-width: 60vw; + width: 800px; text-align: center; } diff --git a/static/widget-styles.css b/static/widget-styles.css index ee17467..74c4dc5 100644 --- a/static/widget-styles.css +++ b/static/widget-styles.css @@ -5,6 +5,12 @@ font-style: normal; } +.clock { + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + /* Styles for ClockWidget time display */ .clock>.time { color: #ffffff; @@ -21,6 +27,21 @@ font-size: 15cqmin; } +/* Styles for ClockWidget time display */ +.quote>.quote-text { + color: #AEAEAE; + font-style: italic; + text-align: center; + font-size: 25cqmin; +} + +.quote>.author { + font-size: small; + text-align: center; + color: #AEAEAE; + font-size: 15cqh; +} + /* Styles for SearchWidget */ .search form { display: flex;