diff --git a/.gitignore b/.gitignore
index 167234e..2492410 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,4 @@ frontend/.yarn/install-state.gz
livestreams.dat
.vite/deps
archive.txt
+web_config.yml
diff --git a/frontend/package.json b/frontend/package.json
index 77c17b3..a67b2a9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -18,11 +18,12 @@
"@mui/icons-material": "^6.2.0",
"@mui/material": "^6.2.0",
"fp-ts": "^2.16.5",
+ "jotai": "^2.10.3",
+ "jotai-cache": "^0.5.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^6.23.1",
"react-virtuoso": "^4.7.11",
- "jotai": "^2.10.3",
"rxjs": "^7.8.1"
},
"devDependencies": {
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index f53bf48..a87e7fe 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
jotai:
specifier: ^2.10.3
version: 2.10.3(@types/react@19.0.1)(react@19.0.0)
+ jotai-cache:
+ specifier: ^0.5.0
+ version: 0.5.0(jotai@2.10.3(@types/react@19.0.1)(react@19.0.0))
react:
specifier: ^19.0.0
version: 19.0.0
@@ -737,6 +740,11 @@ packages:
is-core-module@2.12.1:
resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==}
+ jotai-cache@0.5.0:
+ resolution: {integrity: sha512-29pUuEfSXL7Ba6lxZmiNDARc73TspWzAzCy0jCkk2uEOnFJ6kaUBZTp/AZSwnIsh1ndfUfM9/QpbLU7uJAQL0A==}
+ peerDependencies:
+ jotai: '>=2.0.0'
+
jotai@2.10.3:
resolution: {integrity: sha512-Nnf4IwrLhNfuz2JOQLI0V/AgwcpxvVy8Ec8PidIIDeRi4KCFpwTFIpHAAcU+yCgnw/oASYElq9UY0YdUUegsSA==}
engines: {node: '>=12.20.0'}
@@ -1512,6 +1520,10 @@ snapshots:
dependencies:
has: 1.0.3
+ jotai-cache@0.5.0(jotai@2.10.3(@types/react@19.0.1)(react@19.0.0)):
+ dependencies:
+ jotai: 2.10.3(@types/react@19.0.1)(react@19.0.0)
+
jotai@2.10.3(@types/react@19.0.1)(react@19.0.0):
optionalDependencies:
'@types/react': 19.0.1
diff --git a/frontend/src/Layout.tsx b/frontend/src/Layout.tsx
index 0d136bc..491e11e 100644
--- a/frontend/src/Layout.tsx
+++ b/frontend/src/Layout.tsx
@@ -16,13 +16,13 @@ import ListItemButton from '@mui/material/ListItemButton'
import ListItemIcon from '@mui/material/ListItemIcon'
import ListItemText from '@mui/material/ListItemText'
import Toolbar from '@mui/material/Toolbar'
-import Typography from '@mui/material/Typography'
import { grey } from '@mui/material/colors'
import { useAtomValue } from 'jotai'
import { useMemo, useState } from 'react'
import { Link, Outlet } from 'react-router-dom'
import { settingsState } from './atoms/settings'
import AppBar from './components/AppBar'
+import { AppTitle } from './components/AppTitle'
import Drawer from './components/Drawer'
import Footer from './components/Footer'
import Logout from './components/Logout'
@@ -76,15 +76,7 @@ export default function Layout() {
>
-
- {settings.appTitle}
-
+
diff --git a/frontend/src/atoms/downloadTemplate.ts b/frontend/src/atoms/downloadTemplate.ts
index babb921..c10985e 100644
--- a/frontend/src/atoms/downloadTemplate.ts
+++ b/frontend/src/atoms/downloadTemplate.ts
@@ -1,12 +1,12 @@
import { getOrElse } from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/function'
-import { atom } from 'jotai'
+import { atomWithCache } from 'jotai-cache'
import { atomWithStorage } from 'jotai/utils'
import { ffetch } from '../lib/httpClient'
import { CustomTemplate } from '../types'
import { serverSideCookiesState, serverURL } from './settings'
-export const cookiesTemplateState = atom>(async (get) =>
+export const cookiesTemplateState = atomWithCache>(async (get) =>
await get(serverSideCookiesState)
? '--cookies=cookies.txt'
: ''
@@ -22,7 +22,7 @@ export const filenameTemplateState = atomWithStorage(
localStorage.getItem('lastFilenameTemplate') ?? ''
)
-export const savedTemplatesState = atom>(async (get) => {
+export const savedTemplatesState = atomWithCache>(async (get) => {
const task = ffetch(`${get(serverURL)}/api/v1/template/all`)
const either = await task()
@@ -30,5 +30,4 @@ export const savedTemplatesState = atom>(async (get) =
either,
getOrElse(() => new Array())
)
-}
-)
\ No newline at end of file
+})
\ No newline at end of file
diff --git a/frontend/src/atoms/settings.ts b/frontend/src/atoms/settings.ts
index 986531f..6e370c8 100644
--- a/frontend/src/atoms/settings.ts
+++ b/frontend/src/atoms/settings.ts
@@ -1,9 +1,9 @@
import { pipe } from 'fp-ts/lib/function'
import { matchW } from 'fp-ts/lib/TaskEither'
+import { atom } from 'jotai'
+import { atomWithStorage } from 'jotai/utils'
import { ffetch } from '../lib/httpClient'
import { prefersDarkMode } from '../utils'
-import { atomWithStorage } from 'jotai/utils'
-import { atom } from 'jotai'
export const languages = [
'catalan',
@@ -121,7 +121,8 @@ export const appTitleState = atomWithStorage(
export const serverAddressAndPortState = atom((get) => {
if (get(servedFromReverseProxySubDirState)) {
return `${get(serverAddressState)}/${get(servedFromReverseProxySubDirState)}/`
- .replaceAll('"', '') // TODO: atomWithStorage put extra double quotes on strings
+ .replaceAll('"', '') // XXX: atomWithStorage uses JSON.stringify to serialize
+ .replaceAll('//', '/') // which puts extra double quotes.
}
if (get(servedFromReverseProxyState)) {
return `${get(serverAddressState)}`
diff --git a/frontend/src/components/AppTitle.tsx b/frontend/src/components/AppTitle.tsx
new file mode 100644
index 0000000..52fbd03
--- /dev/null
+++ b/frontend/src/components/AppTitle.tsx
@@ -0,0 +1,33 @@
+import { Typography } from '@mui/material'
+import { useAtom } from 'jotai'
+import { useEffect } from 'react'
+import { appTitleState } from '../atoms/settings'
+import useFetch from '../hooks/useFetch'
+
+export const AppTitle: React.FC = () => {
+ const [appTitle, setAppTitle] = useAtom(appTitleState)
+
+ const { data } = useFetch<{ title: string }>('/webconfig')
+
+ useEffect(() => {
+ if (data?.title) {
+ setAppTitle(
+ data.title.startsWith('"')
+ ? data.title.substring(1, data.title.length - 1)
+ : data.title
+ )
+ }
+ }, [data])
+
+ return (
+
+ {appTitle.startsWith('"') ? appTitle.substring(1, appTitle.length - 1) : appTitle}
+
+ )
+}
\ No newline at end of file
diff --git a/frontend/src/components/subscriptions/SubscriptionsDialog.tsx b/frontend/src/components/subscriptions/SubscriptionsDialog.tsx
index 88ee34f..c0bd32e 100644
--- a/frontend/src/components/subscriptions/SubscriptionsDialog.tsx
+++ b/frontend/src/components/subscriptions/SubscriptionsDialog.tsx
@@ -15,7 +15,7 @@ import {
Typography
} from '@mui/material'
import { TransitionProps } from '@mui/material/transitions'
-import { matchW } from 'fp-ts/lib/Either'
+import { matchW } from 'fp-ts/lib/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import { useAtomValue } from 'jotai'
import { forwardRef, startTransition, useState } from 'react'
@@ -52,21 +52,16 @@ const SubscriptionsDialog: React.FC = ({ open, onClose }) => {
const baseURL = useAtomValue(serverURL)
- const submit = async (sub: Omit) => {
- const task = ffetch(`${baseURL}/subscriptions`, {
+ const submit = async (sub: Omit) => pipe(
+ ffetch(`${baseURL}/subscriptions`, {
method: 'POST',
body: JSON.stringify(sub)
- })
- const either = await task()
-
- pipe(
- either,
- matchW(
- (l) => pushMessage(l, 'error'),
- (_) => onClose()
- )
+ }),
+ matchW(
+ (l) => pushMessage(l, 'error'),
+ (_) => onClose()
)
- }
+ )()
return (