feature(settingsPage): save links to localstorage

This commit is contained in:
Nikurasu 2024-08-24 22:51:13 +02:00
parent 540e79fbff
commit d36cec3d31
Signed by: Nikurasu
GPG key ID: 9E7F14C03EF1F271
13 changed files with 252 additions and 71 deletions

View file

@ -15,9 +15,11 @@
"@mui/icons-material": "^5.16.4",
"@mui/material": "^5.16.4",
"@mui/styled-engine-sc": "6.0.0-alpha.18",
"@types/uuid": "^10.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"styled-components": "^6.1.12",
"uuid": "^10.0.0",
"wouter": "^3.3.1"
},
"devDependencies": {

View file

@ -26,6 +26,9 @@ importers:
'@mui/styled-engine-sc':
specifier: 6.0.0-alpha.18
version: 6.0.0-alpha.18(styled-components@6.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
'@types/uuid':
specifier: ^10.0.0
version: 10.0.0
react:
specifier: ^18.2.0
version: 18.3.1
@ -35,6 +38,9 @@ importers:
styled-components:
specifier: ^6.1.12
version: 6.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
uuid:
specifier: ^10.0.0
version: 10.0.0
wouter:
specifier: ^3.3.1
version: 3.3.1(react@18.3.1)
@ -651,6 +657,9 @@ packages:
'@types/stylis@4.2.5':
resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==}
'@types/uuid@10.0.0':
resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
'@types/webextension-polyfill@0.10.7':
resolution: {integrity: sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==}
@ -1682,6 +1691,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@ -2361,6 +2374,8 @@ snapshots:
'@types/stylis@4.2.5': {}
'@types/uuid@10.0.0': {}
'@types/webextension-polyfill@0.10.7': {}
'@vitejs/plugin-react@4.3.1(vite@5.3.4(@types/node@20.14.11))':
@ -3385,6 +3400,8 @@ snapshots:
util-deprecate@1.0.2: {}
uuid@10.0.0: {}
uuid@8.3.2: {}
vite-plugin-web-extension@4.1.6(@types/node@20.14.11):

View file

@ -11,6 +11,7 @@ import theme from './theme';
import { DrawerOpenContextProvider } from './shared/contexts/DrawerOpenContext';
import { CurrentPageContextProvider } from './shared/contexts/CurrentPageContext';
import { ConfigurableLinksContextProvider } from './shared/contexts/ConfiguarableLinksContext';
import { SnackBarValuesContextProvider } from './shared/contexts/SnackBarValuesConext';
ReactDOM.createRoot(document.getElementById('root')!).render(
@ -19,7 +20,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
<DrawerOpenContextProvider>
<CurrentPageContextProvider>
<ConfigurableLinksContextProvider>
<SnackBarValuesContextProvider>
<App />
</SnackBarValuesContextProvider>
</ConfigurableLinksContextProvider>
</CurrentPageContextProvider>
</DrawerOpenContextProvider>

View file

@ -0,0 +1,16 @@
import { IHomepageLink } from "../interfaces/IHomepageLink";
import * as uuid from 'uuid';
export class HomepageLink implements IHomepageLink {
title: string;
subtitle: string;
hyperlink: string;
uuid: string
constructor() {
this.title = ""
this.subtitle = ""
this.hyperlink = ""
this.uuid = uuid.v4()
}
}

View file

@ -0,0 +1,41 @@
import { useContext } from "react";
import { SnackBarValuesContext } from "../contexts/SnackBarValuesConext";
import { IconButton, Snackbar, SnackbarCloseReason } from "@mui/material";
import CloseIcon from '@mui/icons-material/Close';
export default function() {
const {snackBarValues, setSnackBarValues} = useContext(SnackBarValuesContext)
const handleClose = (
event: React.SyntheticEvent | Event,
reason?: SnackbarCloseReason
) => {
if (reason === 'clickaway') {
return
}
setSnackBarValues({open: false, message: ""})
}
const action = (
<>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={handleClose}
>
<CloseIcon fontSize="small"/>
</IconButton>
</>
)
return (
<Snackbar
open={snackBarValues.open}
autoHideDuration={5000}
message={snackBarValues.message}
onClose={handleClose}
action={action}
/>
)
}

View file

@ -0,0 +1,32 @@
import Sites from '@Data/sites.json'
import { Grid } from "@mui/material";
import LinkCard from "./linkcard";
import { useContext } from 'react';
import { ConfigurableLinksContext } from '../contexts/ConfiguarableLinksContext';
export default function LinkList() {
const {configurableLinks, setConfigurableLinks} = useContext(ConfigurableLinksContext)
return (
<Grid container spacing={2}>
{Sites.sites.map(site => (
<Grid item key={site.title}>
<LinkCard
title={site.title}
subtitle={site.subtitle}
hyperlink={site.hyperlink}
/>
</Grid>
))}
{configurableLinks.map(link => (
<Grid item key={link.title}>
<LinkCard
title={link.title}
subtitle={link.subtitle}
hyperlink={link.hyperlink}
/>
</Grid>
))}
</Grid>
)
}

View file

@ -1,69 +1,120 @@
import { Button, Grid, styled, TextField, Typography } from "@mui/material";
import { useContext, useState } from "react";
import { HomepageLink } from "../interfaces/HomepageLink.interface";
import { ConfigurableLinksContext } from "../contexts/ConfiguarableLinksContext";
const FormGrid = styled(Grid)(() => ({
display: 'flex',
flexDirection: 'column'
}))
interface FormRowProps {
index: number
link: HomepageLink
}
import { useContext, useEffect, useState } from "react";
import { IHomepageLink } from "../interfaces/IHomepageLink";
import { HomepageLink } from "../classes/HomeplageLink";
import { SnackBarValuesContext } from "../contexts/SnackBarValuesConext";
export default function SettingsPage() {
const {configurableLinks, setConfigurableLinks} = useContext(ConfigurableLinksContext)
const [configurableLinks, setConfigurableLinks] = useState<Array<HomepageLink>>(
JSON.parse(localStorage.getItem("configurableLinks") as string) as Array<HomepageLink> ??
new Array<HomepageLink>
)
const {snackBarValues, setSnackBarValues} = useContext(SnackBarValuesContext)
const handleChange = (index: number, event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
let newconfigurableLinks = [...configurableLinks]
newconfigurableLinks[index][event.target.name as keyof HomepageLink] = event.target.value
newconfigurableLinks[index][event.target.name as keyof IHomepageLink] = event.target.value
setConfigurableLinks(newconfigurableLinks)
}
const addFields = () => {
setConfigurableLinks([...configurableLinks, {} as HomepageLink])
const handleBlur = () => {
if (LinkNotEmpty(configurableLinks[configurableLinks.length -1])) {
addFields()
}
if (configurableLinks.length > 1) {
if (LinkEmpty(configurableLinks[configurableLinks.length -2])) {
deleteFieldsAt(configurableLinks.length -2)
}
}
}
function LinkNotEmpty(link: IHomepageLink): Boolean {
return link.title != "" &&
link.subtitle != "" &&
link.hyperlink != ""
}
function LinkEmpty(link: IHomepageLink): Boolean {
return link.title == "" &&
link.subtitle == "" &&
link.hyperlink == ""
}
const addFields = () => {
setConfigurableLinks([...configurableLinks, new HomepageLink()])
}
const deleteFieldsAt = (index: number) => {
setConfigurableLinks(configurableLinks.filter((link) => {return link != configurableLinks[index]}))
}
const saveToLocalStorage = () => {
localStorage.setItem("configurableLinks", JSON.stringify(configurableLinks.filter((link) => {return link != configurableLinks[configurableLinks.length -1]})))
setSnackBarValues({open: true, message: "Saved Links"})
}
useEffect(() => {
addFields()
return () => {
alert("Will close settings")
}
}, [])
return (
<Grid container spacing={1} sx={{mt: 4, mb: 4}}>
<Grid container spacing={1}>
{configurableLinks.map((link, index) =>(
<>
<Grid item xs={4}>
<Grid container item spacing={3} key={link.uuid}>
<Grid item xs={3}>
<TextField
fullWidth
required
label='Title'
name='title'
value={link.title}
key={`title${index}`}
onChange={e => handleChange(index, e)}
onBlur={handleBlur}
/>
</Grid>
<Grid item xs={4}>
<Grid item xs={3}>
<TextField
fullWidth
required
label='Subtitle'
name='subtitle'
value={link.subtitle}
key={`subtitle${index}`}
onChange={e => handleChange(index, e)}
onBlur={handleBlur}
/>
</Grid>
<Grid item xs={4}>
<Grid item xs={3}>
<TextField
fullWidth
required
label='Hyperlink'
name='hyperlink'
value={link.hyperlink}
key={`hyperlink${index}`}
onChange={e => handleChange(index, e)}
onBlur={handleBlur}
/>
</Grid>
</>
{(configurableLinks.length > 1 && index != configurableLinks.length -1)? (
<Grid item xs={2} display={"flex"} alignItems={"center"} justifyContent={"center"}>
<Button
onClick={() => deleteFieldsAt(index)}
variant="contained"
>
Delete
</Button>
</Grid>
):null}
</Grid>
))}
<Grid>
<Button onClick={addFields}>Add field</Button>
<Button onClick={() => saveToLocalStorage()}>Save</Button>
</Grid>
</Grid>
)

View file

@ -1,19 +1,19 @@
import { createContext, Dispatch, SetStateAction, useState } from "react"
import { HomepageLink } from "../interfaces/HomepageLink.interface"
import { IHomepageLink } from "../interfaces/IHomepageLink"
import { ContainerProps } from "@mui/material"
type ConfigurableLinksContextType = {
configurableLinks: Array<HomepageLink>,
setConfigurableLinks: Dispatch<SetStateAction<Array<HomepageLink>>>
configurableLinks: Array<IHomepageLink>,
setConfigurableLinks: Dispatch<SetStateAction<Array<IHomepageLink>>>
}
const ConfigurableLinksContext = createContext<ConfigurableLinksContextType>({
configurableLinks: new Array<HomepageLink>,
configurableLinks: new Array<IHomepageLink>,
setConfigurableLinks: () => {}
})
const ConfigurableLinksContextProvider = (props: ContainerProps) => {
const [configurableLinks, setConfigurableLinks] = useState(new Array<HomepageLink>)
const [configurableLinks, setConfigurableLinks] = useState(new Array<IHomepageLink>)
return (
<ConfigurableLinksContext.Provider value={{configurableLinks, setConfigurableLinks}}>

View file

@ -1,4 +1,3 @@
import { ContainerProps } from "@mui/material";
import { createContext, Dispatch, SetStateAction, useState } from "react";

View file

@ -0,0 +1,25 @@
import { createContext, Dispatch, SetStateAction, useState } from "react"
import { ISnackBarValues } from "../interfaces/ISnackBarValues"
import { ContainerProps } from "@mui/material"
type SnackBarValuesContextType = {
snackBarValues: ISnackBarValues,
setSnackBarValues: Dispatch<SetStateAction<ISnackBarValues>>
}
const SnackBarValuesContext = createContext<SnackBarValuesContextType>({
snackBarValues: {open: false, message: ""} as ISnackBarValues,
setSnackBarValues: () => {}
})
const SnackBarValuesContextProvider = (props: ContainerProps) => {
const [snackBarValues, setSnackBarValues] = useState<ISnackBarValues>({open: false, message: ""})
return (
<SnackBarValuesContext.Provider value={{snackBarValues, setSnackBarValues}}>
{props.children}
</SnackBarValuesContext.Provider>
)
}
export {SnackBarValuesContext, SnackBarValuesContextProvider}

View file

@ -1,4 +1,4 @@
export interface HomepageLink {
export interface IHomepageLink {
title: string
subtitle: string,
hyperlink: string

View file

@ -0,0 +1,4 @@
export interface ISnackBarValues {
open: boolean
message: string
}

View file

@ -3,10 +3,11 @@ import { Box, Button, debounce, FormLabel, Grid, OutlinedInput, styled, TextFiel
import React, { useContext, useState } from 'react'
import MenuBar from '../shared/components/MenuBar'
import LinkCard from '../shared/components/linkcard'
import { Link, Route } from 'wouter'
import MenuDrawer from '../shared/components/menudrawer'
import { CurrentPageContext } from '../shared/contexts/CurrentPageContext'
import SettingsPage from '../shared/components/settingsPage'
import LinkList from '../shared/components/LinkList'
import AppSnackBar from '../shared/components/AppSnackBar'
const useLocalStorage = (storageKey: string, fallbackState: any) => {
const [value, setValue] = React.useState(
@ -21,7 +22,6 @@ const useLocalStorage = (storageKey: string, fallbackState: any) => {
function App() {
const {currentPage, setCurrentPage} = useContext(CurrentPageContext)
const [drawerOpen, setDrawerOpen] = useState(false)
return (
<>
@ -29,22 +29,13 @@ function App() {
<MenuDrawer />
<Box sx={{m: 4}} maxWidth="lg">
{currentPage === 'home' ? (
<Grid container spacing={2}>
{Sites.sites.map(site => (
<Grid item key={site.title}>
<LinkCard
title={site.title}
subtitle={site.subtitle}
hyperlink={site.hyperlink}
/>
</Grid>
))}
</Grid>
<LinkList />
): null}
{currentPage === 'edit' ? (
<SettingsPage />
): null}
</Box>
<AppSnackBar />
</>
)
}