This commit is contained in:
Sean Edmunds
2025-04-15 20:21:25 -05:00
parent 2e72b76e95
commit 78dec160b6
34 changed files with 225 additions and 466 deletions

View File

@@ -8,7 +8,6 @@ import type {
CurrentWeather as CW,
ExtendedForecast as EF,
Message,
OutlookWeather,
PersonalData,
} from 'types';
import Applications from './components/Applications/Applications';
@@ -85,31 +84,31 @@ export function Homepage() {
const { data: weatherMetadata } = useOSMAPI({ address: personal?.[0].physicalAddress });
const { data: points } = useWeatherAPI({
endpoint: 'points',
lat: weatherMetadata?.[0].lat || currentLocation.lat,
lon: weatherMetadata?.[0].lon || currentLocation.lon,
lat: weatherMetadata?.[0].lat ?? currentLocation.lat,
lon: weatherMetadata?.[0].lon ?? currentLocation.lon,
});
const { data: basicWeather } = useWeatherAPI({
endpoint: 'baseline',
office: points?.properties.gridId || currentLocation.office,
x: points?.properties.gridX || currentLocation.x,
y: points?.properties.gridY || currentLocation.y,
office: points?.properties.gridId ?? currentLocation.office,
x: points?.properties.gridX ?? currentLocation.x,
y: points?.properties.gridY ?? currentLocation.y,
});
const { data: stations } = useWeatherAPI({
endpoint: 'stations',
office: points?.properties.gridId || currentLocation.office,
x: points?.properties.gridX || currentLocation.x,
y: points?.properties.gridY || currentLocation.y,
office: points?.properties.gridId ?? currentLocation.office,
x: points?.properties.gridX ?? currentLocation.x,
y: points?.properties.gridY ?? currentLocation.y,
});
const { data: weather } = useWeatherAPI({
endpoint: 'current',
station:
stations?.features[0].properties.stationIdentifier || currentLocation.station,
stations?.features[0].properties.stationIdentifier ?? currentLocation.station,
});
const { data: forecast } = useWeatherAPI({
endpoint: 'forecast',
office: points?.properties.gridId || currentLocation.office,
x: points?.properties.gridX || currentLocation.x,
y: points?.properties.gridY || currentLocation.y,
office: points?.properties.gridId ?? currentLocation.office,
x: points?.properties.gridX ?? currentLocation.x,
y: points?.properties.gridY ?? currentLocation.y,
});
const [current, setCurrent] = useState<CW>({
icon: '',
@@ -125,17 +124,7 @@ export function Homepage() {
windDirection: 0,
pressure: 0,
});
const [outlook, setOutlook] = useState<OutlookWeather[]>([
{
name: '',
icon: '',
isDayTime: true,
temperature: 0,
precipitation: 0,
windSpeed: '0 mph',
windDirection: 'N',
},
]);
const [extendedForecast, setExtendedForecast] = useState<EF[]>([
{
detailedForecast: '',
@@ -150,7 +139,7 @@ export function Homepage() {
useEffect(() => {
if (weather !== undefined && basicWeather !== undefined) {
setCurrent({
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '',
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '',
textDescription: weather.features[0]?.properties.textDescription,
temperature: Math.round(
(weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
@@ -162,7 +151,7 @@ export function Homepage() {
(basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
),
precipitation:
weather.features[0]?.properties.precipitationLastHour.value || 'N/A',
weather.features[0]?.properties.precipitationLastHour.value ?? 'N/A',
humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value),
heatIndex:
basicWeather.properties.heatIndex?.values[0].value === null
@@ -186,30 +175,6 @@ export function Homepage() {
}, [weather, basicWeather]);
useEffect(() => {
if (forecast) {
setOutlook([
{
name: forecast.properties.periods[0].name,
icon: forecast.properties.periods[0].icon.replace('medium', 'large'),
isDayTime: forecast.properties.periods[0].isDaytime,
temperature: forecast.properties.periods[0].temperature,
precipitation: Math.round(
forecast.properties.periods[0].probabilityOfPrecipitation.value,
),
windSpeed: forecast.properties.periods[0].windSpeed,
windDirection: forecast.properties.periods[0].windDirection,
},
{
name: forecast.properties.periods[1].name,
icon: forecast.properties.periods[1].icon.replace('medium', 'large'),
isDayTime: forecast.properties.periods[1].isDaytime,
temperature: forecast.properties.periods[1].temperature,
precipitation: Math.round(
forecast.properties.periods[1].probabilityOfPrecipitation.value,
),
windSpeed: forecast.properties.periods[1].windSpeed,
windDirection: forecast.properties.periods[1].windDirection,
},
]);
setExtendedForecast(
forecast.properties.periods.map((item: any) => ({
detailedForecast: item.detailedForecast,
@@ -241,25 +206,18 @@ export function Homepage() {
<>
<Search
current={current}
outlook={outlook}
extendedForecast={extendedForecast}
weather={weather}
basicWeather={basicWeather}
weatherModalOpened={weatherModalOpened}
closeWeatherModal={closeWeatherModal}
openWeatherModal={openWeatherModal}
/>
<Header
date={date}
greet={displayedGreeting}
date={date}
current={current}
setCurrent={setCurrent}
outlook={outlook}
extendedForecast={extendedForecast}
weather={weather}
basicWeather={basicWeather}
weatherModalOpened={weatherModalOpened}
closeWeatherModal={closeWeatherModal}
openWeatherModal={openWeatherModal}
/>
<Applications contentType={appFilter} />
@@ -269,28 +227,19 @@ export function Homepage() {
<>
<Stack align='center'>
<LargeHeader
date={date}
greet={displayedGreeting}
current={current}
setCurrent={setCurrent}
outlook={outlook}
extendedForecast={extendedForecast}
date={date}
weather={weather}
basicWeather={basicWeather}
weatherModalOpened={weatherModalOpened}
closeWeatherModal={closeWeatherModal}
openWeatherModal={openWeatherModal}
setCurrent={setCurrent}
/>
<Grid grow columns={24} pos={'absolute'} bottom={'1rem'} w={'50%'}>
<CurrentWeather span={18} current={current} />
<CurrentWeather current={current} />
</Grid>
</Stack>
<Search
current={current}
outlook={outlook}
extendedForecast={extendedForecast}
weather={weather}
basicWeather={basicWeather}
weatherModalOpened={weatherModalOpened}
closeWeatherModal={closeWeatherModal}
openWeatherModal={openWeatherModal}

View File

@@ -6,7 +6,7 @@ import * as si from 'react-icons/si';
import * as vsc from 'react-icons/vsc';
import { stackClasses, textClasses } from 'styles';
function AppItem({ url, icon, name }: any) {
function AppItem({ url, icon, name }: { url: string; icon: string; name: string }) {
const allIcons: any = { ...fa6, ...vsc, ...si };
const Icon = allIcons[icon];
const { isMobile } = useCommonHooks();

View File

@@ -5,7 +5,7 @@ import { textClasses } from 'styles';
import type { Application } from 'types';
import AppItem from './AppItem';
function Applications({ contentType }: any) {
function Applications({ contentType }: { contentType: [string] }) {
const { data: apps, isFetching: isAppsFetching } = useQueryAPI({
endpoint: 'api/apps',
});
@@ -59,14 +59,13 @@ function Applications({ contentType }: any) {
contentType.includes(application.filterName) ||
contentType.includes('All'),
)
.map((application: any) => {
.map((application: Application) => {
return (
<AppItem
key={application.name}
name={application.name}
url={application.url}
icon={application.icon}
category={application.category}
/>
);
})}

View File

@@ -5,7 +5,7 @@ import { textClasses } from 'styles';
import type { Bookmark, BookmarkGroup } from 'types';
import LinkGroups from './LinkGroups';
function Links({ contentType }: any) {
function Links({ contentType }: { contentType: [string] }) {
const { data: linkData, isFetched: linksIsFetched } = useQueryAPI({
endpoint: 'api/links',
});

View File

@@ -1,8 +1,9 @@
import { Stack, Text } from '@mantine/core';
import { textClasses } from 'styles';
import type { Bookmark } from 'types';
import LinkItem from './LinkItem';
function LinkGroups({ links, category }: any) {
function LinkGroups({ links, category }: { links: Bookmark[]; category: string }) {
return (
<Stack
style={{
@@ -17,8 +18,8 @@ function LinkGroups({ links, category }: any) {
{category}
</Text>
{links
?.sort((a: any, b: any) => (a.index > b.index ? 1 : -1))
.map((link: any) => (
?.toSorted((a: Bookmark, b: Bookmark) => (a.index > b.index ? 1 : -1))
.map((link: Bookmark) => (
<LinkItem key={link.name} link={link} />
))}
</Stack>

View File

@@ -1,7 +1,8 @@
import { Text } from '@mantine/core';
import { textClasses } from 'styles';
import type { Bookmark } from 'types';
function LinkItem({ link }: any) {
function LinkItem({ link }: { link: Bookmark }) {
return (
<Text
className={textClasses.linkItem}

View File

@@ -2,6 +2,7 @@ import { Group, Stack, Text } from '@mantine/core';
import { useCommonHooks } from 'hooks';
import { useEffect, useState } from 'react';
import { textClasses } from 'styles';
import type { CurrentWeather as CW } from 'types';
function Header({
greet,
@@ -11,7 +12,15 @@ function Header({
weather,
basicWeather,
openWeatherModal,
}: any) {
}: {
greet: string;
date: string;
current: CW;
setCurrent: (current: CW) => void;
weather: any;
basicWeather: any;
openWeatherModal: () => void;
}) {
const [time, setTime] = useState(new Date());
const { isMobile, currentUser, fixDigits } = useCommonHooks();
@@ -25,7 +34,7 @@ function Header({
useEffect(() => {
if (weather !== undefined && basicWeather !== undefined) {
setCurrent({
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '',
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '',
textDescription: weather.features[0]?.properties.textDescription,
temperature: Math.round(
(weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
@@ -37,7 +46,7 @@ function Header({
(basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
),
precipitation:
weather.features[0]?.properties.precipitationLastHour.value || 'N/A',
weather.features[0]?.properties.precipitationLastHour.value ?? 'N/A',
humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value),
heatIndex:
basicWeather.properties.heatIndex?.values[0].value === null
@@ -128,17 +137,17 @@ function Header({
>
<Text
component='h2'
mr={4}
className={textClasses.dateTime}
classNames={textClasses}
mr={4}
>
{current.temperature}°F
</Text>
<Text
mb={6}
className={textClasses.dateTime}
classNames={textClasses}
fz={14}
mb={6}
>
{current.highTemp}°F/{current.lowTemp}°F
</Text>

View File

@@ -2,8 +2,21 @@ import { Stack, Text } from '@mantine/core';
import { useCommonHooks } from 'hooks';
import { useEffect, useState } from 'react';
import { textClasses } from 'styles';
import type { CurrentWeather as CW } from 'types';
function LargeHeader({ greet, date, setCurrent, weather, basicWeather }: any) {
function LargeHeader({
greet,
date,
setCurrent,
weather,
basicWeather,
}: {
greet: string;
date: string;
weather: any;
basicWeather: any;
setCurrent: (current: CW) => void;
}) {
const [time, setTime] = useState(new Date());
const { isMobile, currentUser, fixDigits } = useCommonHooks();
@@ -17,7 +30,7 @@ function LargeHeader({ greet, date, setCurrent, weather, basicWeather }: any) {
useEffect(() => {
if (weather !== undefined && basicWeather !== undefined) {
setCurrent({
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') || '',
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '',
textDescription: weather.features[0]?.properties.textDescription,
temperature: Math.round(
(weather.features[0]?.properties.temperature.value * 9) / 5 + 32,
@@ -29,7 +42,7 @@ function LargeHeader({ greet, date, setCurrent, weather, basicWeather }: any) {
(basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
),
precipitation:
weather.features[0]?.properties.precipitationLastHour.value || 'N/A',
weather.features[0]?.properties.precipitationLastHour.value ?? 'N/A',
humidity: Math.round(weather.features[0]?.properties.relativeHumidity.value),
heatIndex:
basicWeather.properties.heatIndex?.values[0].value === null

View File

@@ -12,16 +12,21 @@ import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { FiEdit, FiTrash } from 'react-icons/fi';
import { actionIconClasses, selectClasses } from 'styles';
import type { Application, Provider } from 'types';
import type { Application, CurrentWeather, ExtendedForecast, Provider } from 'types';
export default function Search({
current,
outlook,
extendedForecast,
weatherModalOpened,
closeWeatherModal,
openWeatherModal,
}: any) {
}: {
current: CurrentWeather;
extendedForecast: ExtendedForecast[];
weatherModalOpened: boolean;
closeWeatherModal: () => void;
openWeatherModal: () => void;
}) {
const { data: providers, isFetched: providersIsFetched } = useQueryAPI({
endpoint: 'api/providers',
});
@@ -29,14 +34,13 @@ export default function Search({
const { data: apps, isFetching: isAppsFetching } = useQueryAPI({
endpoint: 'api/apps',
});
const [applicationData, setApplicationData] = useState<any>([]);
const [applicationData, setApplicationData] = useState<Application[]>([]);
const { data: history, isFetched: historyIsFetched } = useQueryAPI({
endpoint: 'api/searchHistory',
});
const [searchHistory, setSearchHistory] = useState<any>([]);
const [filteredHistory, setFilteredHistory] = useState<any>([]);
const { mutateAsync: createHistoryItem } = useMutationAPI();
const { mutateAsync: deleteHistoryItem } = useMutationAPI();
const { mutateAsync } = useMutationAPI();
const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] =
useDisclosure(false);
const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure();
@@ -59,7 +63,7 @@ export default function Search({
const handleSearchHistoryItem = (item: any) => {
const url =
providers.find((provider: any) => provider.prefix === item.option.prefix)?.url ||
providers.find((provider: any) => provider.prefix === item.option.prefix)?.url ??
'https://www.google.com/search?q=';
open(`${url}${item.option.searchTerm}`, '_self');
};
@@ -73,7 +77,7 @@ export default function Search({
let subtext = '';
if (item.target.value.startsWith('/')) {
option =
item.target.value.substr(1, item.target.value.indexOf(' ') - 1) ||
item.target.value.substr(1, item.target.value.indexOf(' ') - 1) ??
item.target.value.substr(1);
subtext = item.target.value.substr(2 + option.length);
} else if (item.target.value.startsWith('!')) {
@@ -87,7 +91,7 @@ export default function Search({
if (providerLabel === 'Show help') {
openProviderModal();
} else if (item.target.value.startsWith('/')) {
createHistoryItem({
mutateAsync({
method: 'POST',
endpoint: 'api/searchHistory',
body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext },
@@ -100,16 +104,18 @@ export default function Search({
const url = applicationData.find(
(app: Application) =>
app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1),
).url;
)?.url;
const validUrl =
url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`;
url?.startsWith('http://') || url?.startsWith('https://')
? url
: `http://${url}`;
open(validUrl, '_self');
} else if (urlPattern.test(item.target.value)) {
if (/^(https?:\/\/).*/i.test(item.target.value))
window.location = item.target.value;
else open(`https://${item.target.value}`, '_self');
} else {
createHistoryItem({
mutateAsync({
method: 'POST',
endpoint: 'api/searchHistory',
body: { prefix: providerPrefix, provider: providerLabel, searchTerm: subtext },
@@ -220,8 +226,8 @@ export default function Search({
setProviderLabel('Show weather');
} else if (
applicationData?.find(
(app: any) =>
app.baseUrl.substr(1).toLowerCase() === item.target.value.substr(1),
(app: Application) =>
app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1),
)
) {
setProviderLabel(`Launch ${item.target.value.substr(1)}`);
@@ -239,10 +245,10 @@ export default function Search({
const splittedSearch = search.toLowerCase().trim().split(' ');
if (splittedSearch[0].startsWith('!')) {
return apps
.filter((app: any) =>
.filter((app: Application) =>
app.name.toLowerCase().includes(splittedSearch[0].substring(1).toLowerCase()),
)
.map((app: any) => {
.map((app: Application) => {
return {
label: app.name,
value: app.baseUrl,
@@ -257,7 +263,7 @@ export default function Search({
.toLowerCase()
.trim()
.split(' ')
.some((word: any) => word.includes(searchWord)),
.some((word: string) => word.includes(searchWord)),
);
})
.slice(0, 10);
@@ -327,7 +333,7 @@ export default function Search({
classNames={actionIconClasses}
onClick={(e) => {
e.stopPropagation();
deleteHistoryItem({
mutateAsync({
method: 'DELETE',
endpoint: 'api/searchHistory',
body: { id: item.option.id },
@@ -417,7 +423,7 @@ export default function Search({
const option = `${item.prefix} ${item.searchTerm}`;
const words = option.toLowerCase().trim().split(' ');
return splittedSearch.every((searchWord) =>
words.some((word: any) => word.includes(searchWord)),
words.some((word: string) => word.includes(searchWord)),
);
})
.slice(0, 10);
@@ -482,20 +488,19 @@ export default function Search({
setSearchValue={setSearchValue}
setProviderLabel={setProviderLabel}
setProviderURL={setProviderURL}
providerModalOpened={providerModalOpened}
applicationData={applicationData}
providerData={providerData}
providerModalOpened={providerModalOpened}
closeProviderModal={closeProviderModal}
/>
<WeatherModal
current={current}
extendedForecast={extendedForecast}
weatherModalOpened={weatherModalOpened}
setSearchValue={setSearchValue}
setProviderLabel={setProviderLabel}
setProviderURL={setProviderURL}
current={current}
outlook={outlook}
extendedForecast={extendedForecast}
closeWeatherModal={closeWeatherModal}
weatherModalOpened={weatherModalOpened}
/>
</Stack>
);

View File

@@ -38,7 +38,7 @@ function SettingsButton() {
const { data: themePlaylists } = useQueryAPI({
endpoint: 'api/themePlaylists',
});
const { mutateAsync: wakeRequest } = useMutationAPI();
const { mutateAsync } = useMutationAPI();
const [allThemeData, setAllThemeData] = useState<Theme[]>([]);
const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]);
const [selectedClient, setSelectedClient] = useState<WolClient>(wolClients?.[0]);
@@ -64,7 +64,7 @@ function SettingsButton() {
setCurrentThemePlaylist,
} = useCommonHooks();
const handleWakeRequest = () => {
wakeRequest({
mutateAsync({
method: 'POST',
endpoint: 'api/wakeClient',
body: selectedClient || wolClients?.[0],
@@ -151,7 +151,7 @@ function SettingsButton() {
defaultValue={wolClientsData?.[0]?.hostname}
data={
wolClientsData
?.map((wolClient: any) => {
?.map((wolClient: WolClient) => {
return {
value: wolClient.hostname,
label: `${wolClient.hostname} (${wolClient.macAddress})`,
@@ -241,7 +241,7 @@ function SettingsButton() {
label: playlist.name,
};
})
.toSorted((a: any, b: any) => a.label.localeCompare(b.label)) || []
.toSorted((a: any, b: any) => a.label.localeCompare(b.label)) ?? []
}
w='92%'
classNames={{

View File

@@ -27,7 +27,7 @@ export function Settings() {
const { tabValue } = useParams();
return (
<Stack h={'100%'} gap={0}>
<Group pos={'relative'} gap={0} pb={'1rem'}>
<Group gap={0} style={{ position: 'relative', paddingBottom: '1rem' }}>
<ActionIcon
className={actionIconClasses.backButton}
classNames={actionIconClasses}
@@ -51,9 +51,6 @@ export function Settings() {
onChange={(value) =>
navigate(`/${import.meta.env.VITE_BASE_URL}/settings/${value}`)
}
style={{
overflow: 'hidden',
}}
>
<Tabs.List style={{ justifyContent: isMobile ? 'space-between' : undefined }}>
<Tabs.Tab px={isMobile ? '.9rem' : undefined} value='personalization'>

View File

@@ -45,7 +45,7 @@ const ApplicationSettings = () => {
});
const [applicationData, setApplicationData] = useState<Application[]>([]);
const [applicationList, setApplicationList] = useState<Application[]>([]);
const { theme, isMobile } = useCommonHooks();
const { theme, isMobile, reorder } = useCommonHooks();
const [newApplication, setNewApplication] = useState<Application>(defaultApplication);
const { mutateAsync } = useMutationAPI();
const [searchValue, setSearchValue] = useState('');
@@ -53,13 +53,6 @@ const ApplicationSettings = () => {
const [dragging, setDragging] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const reorder = (list: any, startIndex: any, endIndex: any) => {
const result = Array.from(list);
const [removed]: any = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
function onDragEnd(result: any) {
if (!result.destination) {
return;

View File

@@ -28,19 +28,12 @@ const BookmarkSettings = () => {
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
const [newGroup, setNewGroup] = useState<BookmarkGroup>(defaultGroup);
const [groupList, setGroupList] = useState<BookmarkGroup[]>([]);
const { theme, isMobile } = useCommonHooks();
const { theme, isMobile, reorder } = useCommonHooks();
const { mutateAsync } = useMutationAPI();
const [searchValue, setSearchValue] = useState('');
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const reorder = (list: any, startIndex: any, endIndex: any) => {
const result = Array.from(list);
const [removed]: any = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const handleClose = () => {
setNewGroup(defaultGroup);
closeModal();

View File

@@ -27,7 +27,7 @@ function DebouncedGroupSettingsItem({
bookmarks,
searchTerm,
}: { bookmarkGroup: BookmarkGroup; bookmarks: Bookmark[]; searchTerm: string }) {
const { theme, isMobile, handleDelete } = useCommonHooks();
const { theme, isMobile, handleDelete, reorder } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [popoverOpened, setPopoverOpened] = useState(false);
const { mutateAsync } = useMutationAPI();
@@ -44,12 +44,6 @@ function DebouncedGroupSettingsItem({
const [bookmarkList, setBookmarkList] = useState<Bookmark[]>(bookmarks);
const [isOpen, setIsOpen] = useState(false);
const reorder = (list: any, startIndex: any, endIndex: any) => {
const result = Array.from(list);
const [removed]: any = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
function onDragEnd(result: any) {
if (!result.destination) {
return;

View File

@@ -62,7 +62,10 @@ const PersonalizationSettings = () => {
setSearchValue={() => {}}
searchPlaceholder={undefined}
/>
<Stack mt={'6rem'} mb={'1rem'} w={'100%'} align='center'>
<Stack
align='center'
style={{ marginTop: '6rem', marginBottom: '1rem', width: '100%' }}
>
<Grid w={'75%'} pos={'relative'}>
{personalIsFetching && (
<Overlay