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 (
+
+ {value === index && (
+
+ {children}
+
+ )}
+
+ );
+}
\ 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 (
-
- {value === index && (
-
- {children}
-
- )}
-
- );
-}
\ 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;