More typing and fixes

This commit is contained in:
Sean Edmunds
2025-04-25 15:02:00 -05:00
parent 74893ac68d
commit b280909ec9
31 changed files with 230 additions and 110 deletions

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ dist-ssr
*.sw? *.sw?
.env .env
package-lock.json

View File

@@ -1,8 +1,9 @@
import { Divider, Group } from '@mantine/core'; import { Divider, Group } from '@mantine/core';
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import type { SearchShortcut } from 'types';
import HintItem from './HintItem'; import HintItem from './HintItem';
function HelpSection({ name, items }: { name: string; items: object[] }) { function HelpSection({ name, items }: { name: string; items: SearchShortcut[] }) {
const { theme } = useCommonHooks(); const { theme } = useCommonHooks();
return ( return (
<> <>
@@ -30,8 +31,10 @@ function HelpSection({ name, items }: { name: string; items: object[] }) {
}} }}
> >
{items {items
?.toSorted((a: any, b: any) => (a.command > b.command ? 1 : -1)) ?.toSorted((a: SearchShortcut, b: SearchShortcut) =>
.map((item: any) => ( a.command > b.command ? 1 : -1,
)
.map((item: SearchShortcut) => (
<HintItem key={item.command} command={item.command} name={item.name} /> <HintItem key={item.command} command={item.command} name={item.name} />
))} ))}
</Group> </Group>

View File

@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
import { FaLongArrowAltDown, FaLongArrowAltUp, FaWind } from 'react-icons/fa'; import { FaLongArrowAltDown, FaLongArrowAltUp, FaWind } from 'react-icons/fa';
import { WiBarometer, WiHumidity, WiSunrise, WiSunset } from 'react-icons/wi'; import { WiBarometer, WiHumidity, WiSunrise, WiSunset } from 'react-icons/wi';
import { themeIconClasses } from 'styles'; import { themeIconClasses } from 'styles';
import type { CurrentWeather as CW } from 'types'; import type { CurrentWeather as CW, SunState } from 'types';
import WeatherIcon from './WeatherIcon'; import WeatherIcon from './WeatherIcon';
export default function CurrentWeather({ current }: { current: CW }) { export default function CurrentWeather({ current }: { current: CW }) {
@@ -15,12 +15,12 @@ export default function CurrentWeather({ current }: { current: CW }) {
lon: currentLocation.lon, lon: currentLocation.lon,
}); });
const [sunStates, setSunStates] = useState<any>([]); const [sunStates, setSunStates] = useState<SunState[]>([]);
useEffect(() => { useEffect(() => {
if (sunStateData) { if (sunStateData) {
setSunStates( setSunStates(
sunStateData.results.map((item: any) => { sunStateData.results.map((item: SunState) => {
return { return {
date: item.date, date: item.date,
sunrise: item.sunrise, sunrise: item.sunrise,

View File

@@ -1,11 +1,15 @@
import { Group, Stack, Text } from '@mantine/core'; import { Group, Stack, Text } from '@mantine/core';
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import { textClasses } from 'styles'; import { textClasses } from 'styles';
import type { ExtendedForecast } from 'types';
import WeatherIcon from './WeatherIcon'; import WeatherIcon from './WeatherIcon';
export default function ExtendedForecast({ forecast }: any) { export default function ExtendedForecastCard({
const day = forecast.slice(2, 14).filter((item: any) => item.isDaytime); forecast,
const night = forecast.slice(2, 14).filter((item: any) => !item.isDaytime); }: { forecast: ExtendedForecast[] }) {
const day = forecast.slice(2, 14).filter((item: ExtendedForecast) => item.isDaytime);
const night = forecast.slice(2, 14).filter((item: ExtendedForecast) => !item.isDaytime);
const { isMobile } = useCommonHooks(); const { isMobile } = useCommonHooks();
return ( return (
<Stack <Stack
@@ -30,7 +34,7 @@ export default function ExtendedForecast({ forecast }: any) {
</Text> </Text>
</Group> </Group>
<Group w={'100%'} align='center' justify='space-between'> <Group w={'100%'} align='center' justify='space-between'>
{day.map((item: any, index: number) => { {day.map((item: ExtendedForecast, index: number) => {
const short = item.shortForecast.includes('And') const short = item.shortForecast.includes('And')
? item.shortForecast.substring(0, item.shortForecast.indexOf('And')) ? item.shortForecast.substring(0, item.shortForecast.indexOf('And'))
: item.shortForecast.includes('then') : item.shortForecast.includes('then')

View File

@@ -10,8 +10,11 @@ import {
XAxis, XAxis,
YAxis, YAxis,
} from 'recharts'; } from 'recharts';
import type { HourlyForecast } from 'types';
export default function WeatherGraphs({ hourlyForecast }: any) { export default function WeatherGraphs({
hourlyForecast,
}: { hourlyForecast: HourlyForecast[] }) {
function CustomTooltip({ payload, active }: any) { function CustomTooltip({ payload, active }: any) {
if (active) { if (active) {
return ( return (
@@ -48,7 +51,7 @@ export default function WeatherGraphs({ hourlyForecast }: any) {
<XAxis <XAxis
type='category' type='category'
stroke={'var(--color-text-pri)'} stroke={'var(--color-text-pri)'}
dataKey={(data: any) => { dataKey={(data: HourlyForecast) => {
return new Date(data.startTime).toLocaleTimeString('en-US', { return new Date(data.startTime).toLocaleTimeString('en-US', {
hour: 'numeric', hour: 'numeric',
minute: 'numeric', minute: 'numeric',
@@ -74,7 +77,7 @@ export default function WeatherGraphs({ hourlyForecast }: any) {
unit={'°F'} unit={'°F'}
type='monotone' type='monotone'
name='Dew Point' name='Dew Point'
dataKey={(data: any) => { dataKey={(data: HourlyForecast) => {
return (data.dewpoint.value * 9) / 5 + 32; return (data.dewpoint.value * 9) / 5 + 32;
}} }}
stroke={'var(--color-text-acc)'} stroke={'var(--color-text-acc)'}
@@ -88,7 +91,7 @@ export default function WeatherGraphs({ hourlyForecast }: any) {
<XAxis <XAxis
type='category' type='category'
stroke={'var(--color-text-pri)'} stroke={'var(--color-text-pri)'}
dataKey={(data: any) => { dataKey={(data: HourlyForecast) => {
return new Date(data.startTime).toLocaleTimeString('en-US', { return new Date(data.startTime).toLocaleTimeString('en-US', {
hour: 'numeric', hour: 'numeric',
minute: 'numeric', minute: 'numeric',
@@ -102,7 +105,7 @@ export default function WeatherGraphs({ hourlyForecast }: any) {
unit={'%'} unit={'%'}
type='monotone' type='monotone'
name='Precipitation' name='Precipitation'
dataKey={(data: any) => { dataKey={(data: HourlyForecast) => {
return data.probabilityOfPrecipitation.value; return data.probabilityOfPrecipitation.value;
}} }}
stroke={'var(--color-text-pri)'} stroke={'var(--color-text-pri)'}
@@ -113,7 +116,7 @@ export default function WeatherGraphs({ hourlyForecast }: any) {
unit={'%'} unit={'%'}
type='monotone' type='monotone'
name='Humidity' name='Humidity'
dataKey={(data: any) => { dataKey={(data: HourlyForecast) => {
return data.relativeHumidity.value; return data.relativeHumidity.value;
}} }}
stroke={'var(--color-text-acc)'} stroke={'var(--color-text-acc)'}

View File

@@ -4,9 +4,9 @@ import { modalClasses } from 'styles';
import { useCommonHooks, useWeatherAPI } from 'hooks'; import { useCommonHooks, useWeatherAPI } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import type { CurrentWeather as CW, ExtendedForecast as EF } from 'types'; import type { CurrentWeather as CW, ExtendedForecast, HourlyForecast } from 'types';
import CurrentWeather from './CurrentWeather'; import CurrentWeather from './CurrentWeather';
import ExtendedForecast from './ExtendedForecast'; import ExtendedForecastCard from './ExtendedForecastCard';
import WeatherGraphs from './WeatherGraphs'; import WeatherGraphs from './WeatherGraphs';
export function WeatherModal({ export function WeatherModal({
@@ -19,7 +19,7 @@ export function WeatherModal({
closeWeatherModal, closeWeatherModal,
}: { }: {
current: CW; current: CW;
extendedForecast: EF[]; extendedForecast: ExtendedForecast[];
weatherModalOpened: boolean; weatherModalOpened: boolean;
setSearchValue: (value: string) => void; setSearchValue: (value: string) => void;
setProviderLabel: (label: string) => void; setProviderLabel: (label: string) => void;
@@ -33,14 +33,13 @@ export function WeatherModal({
x: currentLocation.x, x: currentLocation.x,
y: currentLocation.y, y: currentLocation.y,
}); });
const [hourlyForecast, setHourlyForecast] = useState<any>([]); const [hourlyForecast, setHourlyForecast] = useState<HourlyForecast[]>([]);
useEffect(() => { useEffect(() => {
if (hourly) { if (hourly) {
setHourlyForecast(hourly.properties.periods.slice(1, 25)); setHourlyForecast(hourly.properties.periods.slice(1, 25));
} }
}, [hourly]); }, [hourly]);
return ( return (
<Modal.Root <Modal.Root
fullScreen={isMobile} fullScreen={isMobile}
@@ -79,7 +78,7 @@ export function WeatherModal({
<Stack w={'100%'}> <Stack w={'100%'}>
<CurrentWeather current={current} /> <CurrentWeather current={current} />
<WeatherGraphs hourlyForecast={hourlyForecast} /> <WeatherGraphs hourlyForecast={hourlyForecast} />
<ExtendedForecast forecast={extendedForecast} /> <ExtendedForecastCard forecast={extendedForecast} />
</Stack> </Stack>
</Center> </Center>
</Modal.Body> </Modal.Body>

View File

@@ -3,12 +3,12 @@ import { useRef } from 'react';
import { Flip, toast } from 'react-toastify'; import { Flip, toast } from 'react-toastify';
const url = `${import.meta.env.PROD ? import.meta.env.VITE_DOMAIN_PROD : import.meta.env.VITE_DOMAIN_DEV}/${import.meta.env.VITE_BASE_URL}`; const url = `${import.meta.env.PROD ? import.meta.env.VITE_DOMAIN_PROD : import.meta.env.VITE_DOMAIN_DEV}/${import.meta.env.VITE_BASE_URL}`;
const headers = { const headers: HeadersInit = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Credentials': true, 'Access-Control-Allow-Credentials': 'true',
'Sec-Fetch-Mode': 'no-cors', 'Sec-Fetch-Mode': 'no-cors',
'Sec-Fetch-Site': 'same-site', 'Sec-Fetch-Site': 'same-site',
}; };
@@ -49,7 +49,7 @@ export function useMutationAPI() {
endpoint: string, endpoint: string,
body: object | undefined | null, body: object | undefined | null,
) => { ) => {
const options: any = { const options: { method: string; headers: HeadersInit | undefined; body: string } = {
method: method, method: method,
headers: headers, headers: headers,
body: JSON.stringify(body), body: JSON.stringify(body),

View File

@@ -13,7 +13,7 @@ export function useCommonHooks() {
key: 'theme', key: 'theme',
defaultValue: 'blackboard', defaultValue: 'blackboard',
}); });
const [appFilter, setAppFilter] = useLocalStorage<[string]>({ const [appFilter, setAppFilter] = useLocalStorage<string[]>({
key: 'appFilter', key: 'appFilter',
defaultValue: ['All'], defaultValue: ['All'],
}); });
@@ -29,7 +29,7 @@ export function useCommonHooks() {
key: 'bookmarkSort', key: 'bookmarkSort',
defaultValue: 'A>Z', defaultValue: 'A>Z',
}); });
const [bookmarkFilter, setBookmarkFilter] = useLocalStorage<[string]>({ const [bookmarkFilter, setBookmarkFilter] = useLocalStorage<string[]>({
key: 'bookmarkFilter', key: 'bookmarkFilter',
defaultValue: ['All'], defaultValue: ['All'],
}); });

View File

@@ -7,6 +7,7 @@ import { stackClasses } from 'styles';
import type { import type {
CurrentWeather as CW, CurrentWeather as CW,
ExtendedForecast as EF, ExtendedForecast as EF,
ExtendedForecast,
Message, Message,
PersonalData, PersonalData,
} from 'types'; } from 'types';
@@ -177,7 +178,7 @@ export function Homepage() {
useEffect(() => { useEffect(() => {
if (forecast) { if (forecast) {
setExtendedForecast( setExtendedForecast(
forecast.properties.periods.map((item: any) => ({ forecast.properties.periods.map((item: ExtendedForecast) => ({
detailedForecast: item.detailedForecast, detailedForecast: item.detailedForecast,
isDaytime: item.isDaytime, isDaytime: item.isDaytime,
name: item.name, name: item.name,
@@ -196,6 +197,7 @@ export function Homepage() {
}); });
} }
}, [forecast]); }, [forecast]);
return ( return (
<Stack <Stack
gap={0} gap={0}

View File

@@ -5,7 +5,7 @@ import { textClasses } from 'styles';
import type { Application } from 'types'; import type { Application } from 'types';
import AppItem from './AppItem'; import AppItem from './AppItem';
function Applications({ contentType }: { contentType: [string] }) { function Applications({ contentType }: { contentType: string[] }) {
const { data: apps, isFetching: isAppsFetching } = useQueryAPI({ const { data: apps, isFetching: isAppsFetching } = useQueryAPI({
endpoint: 'api/apps', endpoint: 'api/apps',
}); });
@@ -17,16 +17,24 @@ function Applications({ contentType }: { contentType: [string] }) {
setApplicationData( setApplicationData(
apps apps
.filter((a: Application) => a.enabled === 1) .filter((a: Application) => a.enabled === 1)
.sort((a: any, b: any) => { .sort((a: Application, b: Application) => {
switch (appSort) { switch (appSort) {
case 'A>Z': case 'A>Z':
return a.name > b.name ? 1 : -1; return a.name > b.name ? 1 : -1;
case 'Z>A': case 'Z>A':
return a.name < b.name ? 1 : -1; return a.name < b.name ? 1 : -1;
case 'Newest': case 'Newest':
return a.id < b.id ? 1 : -1; if (a.id && b.id) {
return a.id < b.id ? 1 : -1;
}
return -1;
case 'Oldest': case 'Oldest':
return a.id > b.id ? 1 : -1; if (a.id && b.id) {
return a.id > b.id ? 1 : -1;
}
return -1;
case 'Custom': case 'Custom':
return a.index > b.index ? 1 : -1; return a.index > b.index ? 1 : -1;
} }

View File

@@ -5,7 +5,7 @@ import { textClasses } from 'styles';
import type { Bookmark, BookmarkGroup } from 'types'; import type { Bookmark, BookmarkGroup } from 'types';
import LinkGroups from './LinkGroups'; import LinkGroups from './LinkGroups';
function Links({ contentType }: { contentType: [string] }) { function Links({ contentType }: { contentType: string[] }) {
const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({ const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({
endpoint: 'api/links', endpoint: 'api/links',
}); });
@@ -28,18 +28,26 @@ function Links({ contentType }: { contentType: [string] }) {
return category; return category;
}); });
setGroups( setGroups(
groupList.sort((a: any, b: any) => { groupList.sort((a: BookmarkGroup, b: BookmarkGroup) => {
switch (bookmarkSort) { switch (bookmarkSort) {
case 'A>Z': case 'A>Z':
return a.categoryName > b.categoryName ? 1 : -1; return a.categoryName > b.categoryName ? 1 : -1;
case 'Z>A': case 'Z>A':
return a.categoryName < b.categoryName ? 1 : -1; return a.categoryName < b.categoryName ? 1 : -1;
case 'Newest': case 'Newest':
return a.id < b.id ? 1 : -1; if (a.id && b.id) {
return a.id < b.id ? 1 : -1;
}
return -1;
case 'Oldest': case 'Oldest':
return a.id > b.id ? 1 : -1; if (a.id && b.id) {
return a.id > b.id ? 1 : -1;
}
return -1;
case 'Custom': case 'Custom':
return a.index > b.index ? 1 : -1; return a.categoryIndex > b.categoryIndex ? 1 : -1;
} }
}), }),
); );

View File

@@ -2,7 +2,7 @@ import { Group, Stack, Text } from '@mantine/core';
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { textClasses } from 'styles'; import { textClasses } from 'styles';
import type { CurrentWeather as CW } from 'types'; import type { CurrentWeather } from 'types';
function Header({ function Header({
greet, greet,
@@ -15,8 +15,8 @@ function Header({
}: { }: {
greet: string; greet: string;
date: string; date: string;
current: CW; current: CurrentWeather;
setCurrent: (current: CW) => void; setCurrent: (current: CurrentWeather) => void;
weather: any; weather: any;
basicWeather: any; basicWeather: any;
openWeatherModal: () => void; openWeatherModal: () => void;

View File

@@ -67,11 +67,11 @@ export default function Search({
const [placeholderValue, setPlaceholderValue] = useState(''); const [placeholderValue, setPlaceholderValue] = useState('');
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
const handleSearchHistoryItem = (item: any) => { const handleSearchHistoryItem = ({ option }: { option: SearchHistoryItem }) => {
const url = const url =
providers.find((provider: Provider) => provider.prefix === item.option.prefix) providers.find((provider: Provider) => provider.prefix === option.prefix)?.url ??
?.url ?? 'https://www.google.com/search?q='; 'https://www.google.com/search?q=';
open(`${url}${item.option.searchTerm}`, '_self'); open(`${url}${option.searchTerm}`, '_self');
}; };
const handleAppSearchItem = (item: any) => { const handleAppSearchItem = (item: any) => {
@@ -425,7 +425,7 @@ export default function Search({
useEffect(() => { useEffect(() => {
const splittedSearch = searchValue.toLowerCase().trim().split(' '); const splittedSearch = searchValue.toLowerCase().trim().split(' ');
const filtered = searchHistory const filtered = searchHistory
.filter((item: any) => { .filter((item: SearchHistoryItem) => {
const option = `${item.prefix} ${item.searchTerm}`; const option = `${item.prefix} ${item.searchTerm}`;
const words = option.toLowerCase().trim().split(' '); const words = option.toLowerCase().trim().split(' ');
return splittedSearch.every((searchWord) => return splittedSearch.every((searchWord) =>
@@ -450,7 +450,7 @@ export default function Search({
<Select <Select
classNames={selectClasses} classNames={selectClasses}
className={selectClasses.search} className={selectClasses.search}
data={searchHistory.map((item: any) => ({ data={searchHistory.map((item: SearchHistoryItem) => ({
label: `${item.provider}`, label: `${item.provider}`,
value: `${item.prefix} ${item.searchTerm} ${item.dateTimeStamp}`, value: `${item.prefix} ${item.searchTerm} ${item.dateTimeStamp}`,
...item, ...item,

View File

@@ -1,6 +1,7 @@
import { import {
ActionIcon, ActionIcon,
Button, Button,
type ComboboxItem,
Group, Group,
Menu, Menu,
MultiSelect, MultiSelect,
@@ -169,7 +170,9 @@ function SettingsButton() {
macAddress: wolClient.macAddress, macAddress: wolClient.macAddress,
}; };
}) })
.toSorted((a: any, b: any) => a.label.localeCompare(b.label)) || [] .toSorted((a: ComboboxItem, b: ComboboxItem) =>
a.label.localeCompare(b.label),
) ?? []
} }
w='92%' w='92%'
classNames={{ classNames={{
@@ -179,8 +182,6 @@ function SettingsButton() {
option: selectClasses.menuSelectOption, option: selectClasses.menuSelectOption,
}} }}
comboboxProps={{ withinPortal: false }} comboboxProps={{ withinPortal: false }}
onChange={(_value, option: any) => setSelectedClient(option)}
onLoad={(option: any) => setSelectedClient(option)}
/> />
</Stack> </Stack>
<Stack <Stack
@@ -252,7 +253,9 @@ function SettingsButton() {
label: playlist.name, label: playlist.name,
}; };
}) })
.toSorted((a: any, b: any) => a.label.localeCompare(b.label)) ?? [] .toSorted((a: ComboboxItem, b: ComboboxItem) =>
a.label.localeCompare(b.label),
) ?? []
} }
w='92%' w='92%'
classNames={{ classNames={{
@@ -262,7 +265,7 @@ function SettingsButton() {
option: selectClasses.menuSelectOption, option: selectClasses.menuSelectOption,
}} }}
comboboxProps={{ withinPortal: false }} comboboxProps={{ withinPortal: false }}
onChange={(_value, option: any) => { onChange={(_value, option: ComboboxItem) => {
setCurrentThemePlaylist(option.value); setCurrentThemePlaylist(option.value);
if (option.value !== 'Time of Day') { if (option.value !== 'Time of Day') {
setThemeName('random'); setThemeName('random');
@@ -281,7 +284,6 @@ function SettingsButton() {
); );
} }
}} }}
onLoad={(option: any) => setCurrentThemePlaylist(option)}
/> />
</Stack> </Stack>
</Stack> </Stack>
@@ -329,7 +331,7 @@ function SettingsButton() {
<MultiSelect <MultiSelect
label='Filter' label='Filter'
data={ data={
categories.toSorted((a: any, b: any) => categories.toSorted((a: ComboboxItem, b: ComboboxItem) =>
a.label.localeCompare(b.label), a.label.localeCompare(b.label),
) ?? [] ) ?? []
} }
@@ -341,7 +343,7 @@ function SettingsButton() {
: 'No apps selected' : 'No apps selected'
} }
defaultValue={appFilter} defaultValue={appFilter}
onChange={(data: any) => { onChange={(data: string[]) => {
setAppFilter(data); setAppFilter(data);
}} }}
classNames={multiSelectClasses} classNames={multiSelectClasses}
@@ -359,7 +361,7 @@ function SettingsButton() {
option: selectClasses.menuSelectOption, option: selectClasses.menuSelectOption,
}} }}
comboboxProps={{ withinPortal: false }} comboboxProps={{ withinPortal: false }}
onChange={(_value, option: any) => { onChange={(_value, option: ComboboxItem) => {
setAppSort(option.value); setAppSort(option.value);
}} }}
/> />
@@ -408,7 +410,7 @@ function SettingsButton() {
<MultiSelect <MultiSelect
label='Filter' label='Filter'
data={ data={
categories.toSorted((a: any, b: any) => categories.toSorted((a: ComboboxItem, b: ComboboxItem) =>
a.label.localeCompare(b.label), a.label.localeCompare(b.label),
) ?? [] ) ?? []
} }
@@ -420,7 +422,7 @@ function SettingsButton() {
: 'No bookmarks selected' : 'No bookmarks selected'
} }
defaultValue={bookmarkFilter} defaultValue={bookmarkFilter}
onChange={(data: any) => { onChange={(data: string[]) => {
setBookmarkFilter(data); setBookmarkFilter(data);
}} }}
classNames={multiSelectClasses} classNames={multiSelectClasses}
@@ -438,7 +440,7 @@ function SettingsButton() {
option: selectClasses.menuSelectOption, option: selectClasses.menuSelectOption,
}} }}
comboboxProps={{ withinPortal: false }} comboboxProps={{ withinPortal: false }}
onChange={(_value, option: any) => { onChange={(_value, option: ComboboxItem) => {
setBookmarkSort(option.value); setBookmarkSort(option.value);
}} }}
/> />

View File

@@ -3,6 +3,8 @@ import {
Box, Box,
Button, Button,
Card, Card,
type ComboboxItem,
type ComboboxLikeRenderOptionInput,
Divider, Divider,
Grid, Grid,
GridCol, GridCol,
@@ -179,13 +181,13 @@ const ApplicationSettings = () => {
zIndex: 3000, zIndex: 3000,
withinPortal: false, withinPortal: false,
}} }}
onChange={(event: any) => { onChange={(value: string | null) => {
setNewApplication({ setNewApplication({
...newApplication, ...newApplication,
icon: event, icon: value ?? '',
}); });
}} }}
renderOption={(item: any) => { renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
const Icon = allIcons[item.option.value]; const Icon = allIcons[item.option.value];
return ( return (
<Group> <Group>

View File

@@ -2,6 +2,8 @@ import {
ActionIcon, ActionIcon,
Button, Button,
Card, Card,
type ComboboxItem,
type ComboboxLikeRenderOptionInput,
Group, Group,
Select, Select,
Stack, Stack,
@@ -194,13 +196,13 @@ function DebouncedApplication({
zIndex: 3000, zIndex: 3000,
withinPortal: false, withinPortal: false,
}} }}
onChange={(event: any) => { onChange={(value: string | null) => {
setUpdate({ setUpdate({
...app, ...app,
icon: event, icon: value,
}); });
}} }}
renderOption={(item: any) => { renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
const Icon = allIcons[item.option.value]; const Icon = allIcons[item.option.value];
return ( return (
<Group> <Group>

View File

@@ -10,7 +10,7 @@ import SettingsHeader from '../Shared/SettingsHeader';
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem'; import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
const BookmarkSettings = () => { const BookmarkSettings = () => {
const defaultGroup = { const defaultGroup = {
id: undefined, id: null,
categoryName: '', categoryName: '',
categoryIndex: 0, categoryIndex: 0,
filterName: '', filterName: '',

View File

@@ -29,10 +29,14 @@ function SettingsHeader({
setSearchValue, setSearchValue,
searchPlaceholder, searchPlaceholder,
}: { }: {
setDragging: any; setDragging: undefined | ((dragging: boolean) => void);
openModal: () => void; openModal: () => void;
title: string; title: string;
tooltip: any; tooltip: {
title: string;
highlightList: string[];
description: string;
};
searchValue: string | undefined; searchValue: string | undefined;
setSearchValue: (searchValue: string) => void; setSearchValue: (searchValue: string) => void;
searchPlaceholder: string | undefined; searchPlaceholder: string | undefined;

View File

@@ -16,7 +16,7 @@ function ThemeCard({
cardTheme: Theme; cardTheme: Theme;
handleOpen: (cardTheme: Theme) => void; handleOpen: (cardTheme: Theme) => void;
allThemes: Theme[]; allThemes: Theme[];
themePlaylists: any; themePlaylists: ThemePlaylist[];
}) { }) {
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
const [shiftEdit, setShiftEdit] = useState<number>(0); const [shiftEdit, setShiftEdit] = useState<number>(0);
@@ -274,8 +274,8 @@ function ThemeCard({
onClick={() => { onClick={() => {
const themeList = JSON.parse( const themeList = JSON.parse(
themePlaylists?.find( themePlaylists?.find(
(t: ThemePlaylist) => t.name === currentThemePlaylist, (t: ThemePlaylist) => t?.name === currentThemePlaylist,
).themeList, )?.themeList ?? '[]',
); );
const filteredThemes = allThemes.filter((t: Theme) => const filteredThemes = allThemes.filter((t: Theme) =>
themeList.includes(t.id), themeList.includes(t.id),

View File

@@ -143,7 +143,7 @@ function ThemeModal({
<Modal.Content> <Modal.Content>
<Modal.Header style={{ justifyContent: 'center' }}> <Modal.Header style={{ justifyContent: 'center' }}>
<Group pos={'absolute'}> <Group pos={'absolute'}>
<Text>Name:</Text> <Text c={theme.backgroundColor}>Name:</Text>
<TextInput <TextInput
className={textInputClasses.default} className={textInputClasses.default}
classNames={textInputClasses} classNames={textInputClasses}

View File

@@ -32,7 +32,6 @@ function ThemePlaylistCard({
const themeList = JSON.parse( const themeList = JSON.parse(
cardPlaylist.themeList.name === 'Time of Day' ? '[]' : cardPlaylist.themeList, cardPlaylist.themeList.name === 'Time of Day' ? '[]' : cardPlaylist.themeList,
); );
return ( return (
<Group <Group
gap={0} gap={0}
@@ -253,7 +252,7 @@ function ThemePlaylistCard({
<Stack gap={4}> <Stack gap={4}>
{themeList.length > 0 && {themeList.length > 0 &&
themeList themeList
.sort((a: any, b: any) => (a > b ? 1 : -1)) .sort((a: number, b: number) => (a > b ? 1 : -1))
.map((item: number) => { .map((item: number) => {
return ( return (
<ThemeMiniCard key={item} item={item} allThemes={allThemes} /> <ThemeMiniCard key={item} item={item} allThemes={allThemes} />

View File

@@ -10,7 +10,7 @@ import {
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { modalClasses, textInputClasses } from 'styles'; import { modalClasses, textInputClasses } from 'styles';
import type { Theme } from 'types'; import type { Theme, ThemePlaylist } from 'types';
import ThemeMiniCard from './ThemeMiniCard'; import ThemeMiniCard from './ThemeMiniCard';
import TimeOfDay from './TimeOfDay'; import TimeOfDay from './TimeOfDay';
@@ -19,6 +19,7 @@ function ThemePlaylistModal({
handleClose, handleClose,
createPlaylist, createPlaylist,
selectedPlaylist, selectedPlaylist,
defaultPlaylist,
setSelectedPlaylist, setSelectedPlaylist,
handleSavePlaylist, handleSavePlaylist,
allThemes, allThemes,
@@ -26,13 +27,15 @@ function ThemePlaylistModal({
opened: boolean; opened: boolean;
handleClose: () => void; handleClose: () => void;
createPlaylist: any; createPlaylist: any;
selectedPlaylist: any; selectedPlaylist: ThemePlaylist;
setSelectedPlaylist: (selectedPlaylist: any) => void; defaultPlaylist: ThemePlaylist;
setSelectedPlaylist: (selectedPlaylist: ThemePlaylist) => void;
handleSavePlaylist: () => void; handleSavePlaylist: () => void;
allThemes: Theme[]; allThemes: Theme[];
}) { }) {
const { theme, isMobile, modalStyle } = useCommonHooks(); const { theme, isMobile, modalStyle } = useCommonHooks();
const [themeList, setThemeList] = useState<any>('[]'); const [themeList, setThemeList] = useState<any>([]);
useEffect(() => { useEffect(() => {
if (selectedPlaylist?.themeList) { if (selectedPlaylist?.themeList) {
setThemeList(JSON.parse(selectedPlaylist?.themeList)); setThemeList(JSON.parse(selectedPlaylist?.themeList));
@@ -40,12 +43,8 @@ function ThemePlaylistModal({
}, [selectedPlaylist]); }, [selectedPlaylist]);
if (selectedPlaylist?.name === undefined) { if (selectedPlaylist?.name === undefined) {
setSelectedPlaylist({ setSelectedPlaylist(defaultPlaylist);
name: 'New Playlist',
themeList: '[]',
});
} }
return ( return (
<Modal.Root <Modal.Root
size={'50%'} size={'50%'}
@@ -64,7 +63,7 @@ function ThemePlaylistModal({
<Modal.Content> <Modal.Content>
<Modal.Header style={{ justifyContent: 'center' }}> <Modal.Header style={{ justifyContent: 'center' }}>
<Group pos={'absolute'}> <Group pos={'absolute'}>
<Text>Name:</Text> <Text c={theme.backgroundColor}>Name:</Text>
<TextInput <TextInput
className={textInputClasses.default} className={textInputClasses.default}
classNames={textInputClasses} classNames={textInputClasses}
@@ -145,7 +144,7 @@ function ThemePlaylistModal({
onClick={() => { onClick={() => {
handleClose(); handleClose();
setThemeList('[]'); setThemeList('[]');
setSelectedPlaylist({}); setSelectedPlaylist(defaultPlaylist);
}} }}
> >
Cancel Cancel

View File

@@ -18,9 +18,16 @@ function ThemePlaylists() {
const { data: themePlaylists } = useQueryAPI({ const { data: themePlaylists } = useQueryAPI({
endpoint: themePlaylistsEndpoint, endpoint: themePlaylistsEndpoint,
}); });
const defaultPlaylist = {
id: undefined,
default: 0,
name: 'New Playlist',
themeList: '[]',
};
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false);
const [selectedPlaylist, setSelectedPlaylist] = useState<ThemePlaylist | undefined>(); const [selectedPlaylist, setSelectedPlaylist] =
useState<ThemePlaylist>(defaultPlaylist);
const { currentThemePlaylist, theme, isMobile } = useCommonHooks(); const { currentThemePlaylist, theme, isMobile } = useCommonHooks();
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
@@ -31,7 +38,7 @@ function ThemePlaylists() {
}; };
const handleClose = () => { const handleClose = () => {
setSelectedPlaylist(undefined); setSelectedPlaylist(defaultPlaylist);
close(); close();
}; };
@@ -50,6 +57,7 @@ function ThemePlaylists() {
opened={opened} opened={opened}
handleClose={handleClose} handleClose={handleClose}
selectedPlaylist={selectedPlaylist} selectedPlaylist={selectedPlaylist}
defaultPlaylist={defaultPlaylist}
setSelectedPlaylist={setSelectedPlaylist} setSelectedPlaylist={setSelectedPlaylist}
handleSavePlaylist={handleSavePlaylist} handleSavePlaylist={handleSavePlaylist}
allThemes={allThemes} allThemes={allThemes}

View File

@@ -1,5 +1,11 @@
import { Group, Select, Text } from '@mantine/core'; import {
import type { Theme } from 'types'; type ComboboxItem,
type ComboboxLikeRenderOptionInput,
Group,
Select,
Text,
} from '@mantine/core';
import type { Theme, ThemePlaylist } from 'types';
function ThemeSelect({ function ThemeSelect({
allThemes, allThemes,
@@ -10,14 +16,15 @@ function ThemeSelect({
timeIndex, timeIndex,
}: { }: {
allThemes: Theme[]; allThemes: Theme[];
createPlaylist: any; createPlaylist: (arg0: object) => void;
themeList: any; themeList: Array<string>;
selectedPlaylist: any; selectedPlaylist: ThemePlaylist;
index: string; index: number;
timeIndex: string; timeIndex: string;
}) { }) {
const theme = allThemes.find((theme: Theme) => `${theme.id}` === themeList[index]); const theme: Theme | undefined = allThemes.find(
(theme: Theme) => `${theme.id}` === themeList[index],
);
return ( return (
<Group> <Group>
<Text w={'100px'}>{timeIndex}</Text> <Text w={'100px'}>{timeIndex}</Text>
@@ -34,7 +41,7 @@ function ThemeSelect({
})} })}
onChange={(data) => { onChange={(data) => {
const newThemeList = themeList; const newThemeList = themeList;
newThemeList[index] = data; newThemeList[index] = data as string;
createPlaylist({ createPlaylist({
method: 'PATCH', method: 'PATCH',
endpoint: 'api/themePlaylists', endpoint: 'api/themePlaylists',
@@ -60,8 +67,10 @@ function ThemeSelect({
borderRadius: '8px', borderRadius: '8px',
}, },
}} }}
renderOption={({ option }: any) => { renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
const theme = allThemes?.find((theme: Theme) => `${theme.id}` === option.value); const theme = allThemes?.find(
(theme: Theme) => `${theme.id}` === item.option.value,
);
return ( return (
<Group <Group
gap={0} gap={0}
@@ -86,7 +95,10 @@ function ThemeSelect({
borderRadius: '8px', borderRadius: '8px',
}} }}
> >
{allThemes.find((theme: Theme) => `${theme.id}` === option.value)?.name} {
allThemes.find((theme: Theme) => `${theme.id}` === item.option.value)
?.name
}
</Text> </Text>
</Group> </Group>
); );

View File

@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
import { FaDice } from 'react-icons/fa'; import { FaDice } from 'react-icons/fa';
import { FiChevronRight } from 'react-icons/fi'; import { FiChevronRight } from 'react-icons/fi';
import { switchClasses } from 'styles'; import { switchClasses } from 'styles';
import type { Theme } from 'types'; import type { Theme, ThemePlaylist } from 'types';
import SettingsHeader from '../Shared/SettingsHeader'; import SettingsHeader from '../Shared/SettingsHeader';
import ThemeCard from './ThemeCard'; import ThemeCard from './ThemeCard';
import ThemeModal from './ThemeModal'; import ThemeModal from './ThemeModal';
@@ -19,9 +19,10 @@ function ThemesSettings() {
const { data: backgroundData, isFetched: isBackgroundFetched } = useQueryAPI({ const { data: backgroundData, isFetched: isBackgroundFetched } = useQueryAPI({
endpoint: backgroundsEndpoint, endpoint: backgroundsEndpoint,
}); });
const { data: themePlaylists } = useQueryAPI({ const { data: themePlaylistData } = useQueryAPI({
endpoint: 'api/themePlaylists', endpoint: 'api/themePlaylists',
}); });
const [themePlaylists, setThemePlaylists] = useState<ThemePlaylist[]>([]);
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [opened, { open, close }] = useDisclosure(false); const [opened, { open, close }] = useDisclosure(false);
const [colorId, setColorId] = useState<string>(''); const [colorId, setColorId] = useState<string>('');
@@ -79,6 +80,13 @@ function ThemesSettings() {
setBackgroundList(['None', ...backgroundData]); setBackgroundList(['None', ...backgroundData]);
} }
}, [backgroundData, isBackgroundFetched]); }, [backgroundData, isBackgroundFetched]);
useEffect(() => {
if (themePlaylistData) {
setThemePlaylists(themePlaylistData);
}
}, [themePlaylistData]);
return ( return (
<Stack w={'100%'} align='center'> <Stack w={'100%'} align='center'>
<ThemeModal <ThemeModal

View File

@@ -1,5 +1,5 @@
import { Stack } from '@mantine/core'; import { Stack } from '@mantine/core';
import type { Theme } from 'types'; import type { Theme, ThemePlaylist } from 'types';
import ThemeSelect from './ThemeSelect'; import ThemeSelect from './ThemeSelect';
function TimeOfDay({ function TimeOfDay({
@@ -7,7 +7,12 @@ function TimeOfDay({
themeList, themeList,
allThemes, allThemes,
selectedPlaylist, selectedPlaylist,
}: { createPlaylist: any; themeList: any; allThemes: Theme[]; selectedPlaylist: any }) { }: {
createPlaylist: () => void;
themeList: Array<string>;
allThemes: Theme[];
selectedPlaylist: ThemePlaylist;
}) {
return ( return (
<Stack> <Stack>
<ThemeSelect <ThemeSelect
@@ -15,7 +20,7 @@ function TimeOfDay({
createPlaylist={createPlaylist} createPlaylist={createPlaylist}
themeList={themeList} themeList={themeList}
selectedPlaylist={selectedPlaylist} selectedPlaylist={selectedPlaylist}
index='0' index={0}
timeIndex='00:00 - 05:59' timeIndex='00:00 - 05:59'
/> />
<ThemeSelect <ThemeSelect
@@ -23,7 +28,7 @@ function TimeOfDay({
createPlaylist={createPlaylist} createPlaylist={createPlaylist}
themeList={themeList} themeList={themeList}
selectedPlaylist={selectedPlaylist} selectedPlaylist={selectedPlaylist}
index='1' index={1}
timeIndex='06:00 - 11:59' timeIndex='06:00 - 11:59'
/> />
<ThemeSelect <ThemeSelect
@@ -31,7 +36,7 @@ function TimeOfDay({
createPlaylist={createPlaylist} createPlaylist={createPlaylist}
themeList={themeList} themeList={themeList}
selectedPlaylist={selectedPlaylist} selectedPlaylist={selectedPlaylist}
index='2' index={2}
timeIndex='12:00 - 17:59' timeIndex='12:00 - 17:59'
/> />
<ThemeSelect <ThemeSelect
@@ -39,7 +44,7 @@ function TimeOfDay({
createPlaylist={createPlaylist} createPlaylist={createPlaylist}
themeList={themeList} themeList={themeList}
selectedPlaylist={selectedPlaylist} selectedPlaylist={selectedPlaylist}
index='3' index={3}
timeIndex='18:00 - 23:59' timeIndex='18:00 - 23:59'
/> />
</Stack> </Stack>

View File

@@ -1,5 +1,5 @@
export interface BookmarkGroup { export interface BookmarkGroup {
id: number | undefined; id: number | null;
categoryName: string; categoryName: string;
categoryIndex: number; categoryIndex: number;
filterName: string; filterName: string;

View File

@@ -1,4 +1,10 @@
export type { CurrentWeather, OutlookWeather, ExtendedForecast } from './weather'; export type {
CurrentWeather,
OutlookWeather,
ExtendedForecast,
HourlyForecast,
SunState,
} from './weather';
export type { Application, AppCategory } from './application'; export type { Application, AppCategory } from './application';
export type { Bookmark } from './bookmark'; export type { Bookmark } from './bookmark';
export type { BookmarkGroup } from './bookmarkGroup'; export type { BookmarkGroup } from './bookmarkGroup';
@@ -7,6 +13,6 @@ export type { Message } from './message';
export type { Provider } from './provider'; export type { Provider } from './provider';
export type { Theme, ThemePlaylist } from './theme'; export type { Theme, ThemePlaylist } from './theme';
export type { WolClient } from './wolClient'; export type { WolClient } from './wolClient';
export type { SearchHistoryItem } from './searchHistory'; export type { SearchHistoryItem, SearchShortcut } from './search';
export type { User } from './user'; export type { User } from './user';
export type { Location } from './location'; export type { Location } from './location';

View File

@@ -5,3 +5,8 @@ export interface SearchHistoryItem {
searchTerm: string; searchTerm: string;
dateTimeStamp: string; dateTimeStamp: string;
} }
export interface SearchShortcut {
name: string;
command: string;
}

View File

@@ -14,5 +14,5 @@ export interface ThemePlaylist {
id: number | undefined; id: number | undefined;
name: string; name: string;
default: number; default: number;
themeList: number[]; themeList: string;
} }

View File

@@ -31,3 +31,43 @@ export interface ExtendedForecast {
temperature: number; temperature: number;
temperatureUnit: string; temperatureUnit: string;
} }
export interface HourlyForecast {
detailedForecast: string;
dewpoint: DewPoint;
endTime: string;
icon: string;
isDaytime: boolean;
name: string;
number: number;
probabilityOfPrecipitation: ProbabilityOfPrecipitation;
relativeHumidity: RelativeHumidity;
shortForecast: string;
startTime: string;
temperature: number;
temperatureTrend: string;
temperatureUnit: string;
windDirection: string;
windSpeed: string;
}
export interface ProbabilityOfPrecipitation {
unitCode: string;
value: number;
}
export interface RelativeHumidity {
unitCode: string;
value: number;
}
export interface DewPoint {
unitCode: string;
value: number;
}
export interface SunState {
date: string;
sunrise: string;
sunset: string;
}