🔀 Merge branch 'dev' into tests/add-tests

This commit is contained in:
Manuel
2023-03-20 23:18:20 +01:00
11 changed files with 224 additions and 42 deletions

View File

@@ -24,11 +24,14 @@ env:
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
jobs:
# Push image to GitHub Packages.
# See also https://docs.docker.com/docker-hub/builds/
yarn_install_and_build:
yarn_install_and_build_dev:
runs-on: ubuntu-latest
permissions:
packages: write
@@ -67,7 +70,7 @@ jobs:
- run: yarn install --immutable
- run: yarn build
- run: yarn turbo build
- name: Docker meta
if: github.event_name != 'pull_request'

1
.gitignore vendored
View File

@@ -32,6 +32,7 @@ yarn-error.log*
# vercel
.vercel
.turbo
*.tsbuildinfo
# storybook

View File

@@ -11,6 +11,7 @@
"dev": "next dev",
"build": "vitest run && next build",
"analyze": "vitest run && ANALYZE=true next build",
"turbo" : "turbo run build",
"start": "next start",
"typecheck": "tsc --noEmit",
"export": "vitest run && next build && next export",
@@ -96,7 +97,7 @@
"jsdom": "^21.1.1",
"prettier": "^2.7.1",
"sass": "^1.56.1",
"turbo": "^1.7.4",
"turbo": "^1.8.3",
"typescript": "^4.7.4",
"video.js": "^8.0.3",
"vitest": "^0.29.3",

View File

@@ -0,0 +1,14 @@
{
"card": {
"title": "Oops, there was an error!",
"buttons": {
"details": "Details",
"tryAgain": "Try again"
}
},
"modal": {
"text": "We're sorry for the inconvinience! This shouln't happen - please report this issue on GitHub.",
"label": "Your error",
"reportButton": "Report this error"
}
}

View File

@@ -1,18 +1,20 @@
import { ActionIcon, Button, Group, Text, Title, Tooltip } from '@mantine/core';
import { useHotkeys, useWindowEvent } from '@mantine/hooks';
import { hideNotification, showNotification } from '@mantine/notifications';
import { IconEditCircle, IconEditCircleOff } from '@tabler/icons';
import axios from 'axios';
import Consola from 'consola';
import { ActionIcon, Button, Group, Text, Title, Tooltip } from '@mantine/core';
import { IconEditCircle, IconEditCircleOff } from '@tabler/icons';
import { getCookie } from 'cookies-next';
import { Trans, useTranslation } from 'next-i18next';
import { useHotkeys } from '@mantine/hooks';
import { hideNotification, showNotification } from '@mantine/notifications';
import { useConfigContext } from '../../../../../config/provider';
import { useScreenSmallerThan } from '../../../../../hooks/useScreenSmallerThan';
import { useEditModeStore } from '../../../../Dashboard/Views/useEditModeStore';
import { AddElementAction } from '../AddElementAction/AddElementAction';
import { useNamedWrapperColumnCount } from '../../../../Dashboard/Wrappers/gridstack/store';
import { useCardStyles } from '../../../useCardStyles';
import { AddElementAction } from '../AddElementAction/AddElementAction';
const beforeUnloadEventText = 'Exit the edit mode to save your changes';
export const ToggleEditModeAction = () => {
const { enabled, toggleEditMode } = useEditModeStore();
@@ -29,6 +31,16 @@ export const ToggleEditModeAction = () => {
useHotkeys([['ctrl+E', toggleEditMode]]);
useWindowEvent('beforeunload', (event: BeforeUnloadEvent) => {
if (enabled) {
// eslint-disable-next-line no-param-reassign
event.returnValue = beforeUnloadEventText;
return beforeUnloadEventText;
}
return undefined;
});
const toggleButtonClicked = () => {
toggleEditMode();
if (enabled || config === undefined || config?.schemaVersion === undefined) {

View File

@@ -38,6 +38,7 @@ function App(
colorScheme: ColorScheme;
packageAttributes: ServerSidePackageAttributesType;
editModeEnabled: boolean;
defaultColorScheme: ColorScheme;
}
) {
const { Component, pageProps } = props;
@@ -55,7 +56,7 @@ function App(
// hook will return either 'dark' or 'light' on client
// and always 'light' during ssr as window.matchMedia is not available
const preferredColorScheme = useColorScheme();
const preferredColorScheme = useColorScheme(props.defaultColorScheme);
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>({
key: 'mantine-color-scheme',
defaultValue: preferredColorScheme,
@@ -144,10 +145,18 @@ App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => {
'EXPERIMENTAL: You have disabled the edit mode. Modifications are no longer possible and any requests on the API will be dropped. If you want to disable this, unset the DISABLE_EDIT_MODE environment variable. This behaviour may be removed in future versions of Homarr'
);
}
if (process.env.DEFAULT_COLOR_SCHEME !== undefined) {
Consola.debug(`Overriding the default color scheme with ${process.env.DEFAULT_COLOR_SCHEME}`);
}
const colorScheme: ColorScheme = process.env.DEFAULT_COLOR_SCHEME as ColorScheme ?? 'light';
return {
colorScheme: getCookie('color-scheme', ctx) || 'light',
packageAttributes: getServiceSidePackageAttributes(),
editModeEnabled: !disableEditMode,
defaultColorScheme: colorScheme,
};
};

View File

@@ -36,6 +36,7 @@ export const dashboardNamespaces = [
'modules/media-server',
'modules/common-media-cards',
'modules/video-stream',
'widgets/error-boundary',
];
export const loginNamespaces = ['authentication/login'];

View File

@@ -2,6 +2,7 @@ import { ComponentType, useMemo } from 'react';
import Widgets from '.';
import { HomarrCardWrapper } from '../components/Dashboard/Tiles/HomarrCardWrapper';
import { WidgetsMenu } from '../components/Dashboard/Tiles/Widgets/WidgetsMenu';
import ErrorBoundary from './boundary';
import { IWidget } from './widgets';
interface WidgetWrapperProps {
@@ -40,9 +41,11 @@ export const WidgetWrapper = ({
const widgetWithDefaultProps = useWidget(widget);
return (
<HomarrCardWrapper className={className}>
<WidgetsMenu integration={widgetId} widget={widgetWithDefaultProps} />
<WidgetComponent widget={widgetWithDefaultProps} />
</HomarrCardWrapper>
<ErrorBoundary>
<HomarrCardWrapper className={className}>
<WidgetsMenu integration={widgetId} widget={widgetWithDefaultProps} />
<WidgetComponent widget={widgetWithDefaultProps} />
</HomarrCardWrapper>
</ErrorBoundary>
);
};

127
src/widgets/boundary.tsx Normal file
View File

@@ -0,0 +1,127 @@
import Consola from 'consola';
import React, { ReactNode } from 'react';
import { openModal } from '@mantine/modals';
import { withTranslation } from 'next-i18next';
import { Button, Card, Center, Code, Group, Stack, Text, Title } from '@mantine/core';
import { IconBrandGithub, IconBug, IconInfoCircle, IconRefresh } from '@tabler/icons';
type ErrorBoundaryState = {
hasError: boolean;
error: Error | undefined;
};
type ErrorBoundaryProps = {
t: (key: string) => string;
children: ReactNode;
};
/**
* A custom error boundary, that catches errors within widgets and renders an error component.
* The error component can be refreshed and shows a modal with error details
*/
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: any) {
super(props);
// Define a state variable to track whether is an error or not
this.state = { hasError: false, error: undefined };
}
static getDerivedStateFromError(error: Error) {
// Update state so the next render will show the fallback UI
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: any) {
Consola.error(`Error while rendering widget, ${error}: ${errorInfo}`);
}
render() {
// Check if the error is thrown
if (this.state.hasError) {
return (
<Card
m={10}
sx={(theme) => ({
backgroundColor: theme.colors.red[5],
})}
radius="lg"
shadow="sm"
withBorder
>
<Center>
<Stack align="center">
<IconBug color="white" />
<Stack spacing={0} align="center">
<Title order={4} color="white" align="center">
{this.props.t('card.title')}
</Title>
{this.state.error && (
<Text color="white" align="center" size="sm">
{this.state.error.toString()}
</Text>
)}
</Stack>
<Group>
<Button
onClick={() =>
openModal({
title: 'Your widget had an error',
children: (
<>
<Text size="sm" mb="sm">
{this.props.t('modal.text')}
</Text>
{this.state.error && (
<>
<Text weight="bold" size="sm">
{this.props.t('modal.label')}
</Text>
<Code block>{this.state.error.toString()}</Code>
</>
)}
<Button
sx={(theme) => ({
backgroundColor: theme.colors.gray[8],
'&:hover': {
backgroundColor: theme.colors.gray[9],
},
})}
leftIcon={<IconBrandGithub />}
component="a"
href="https://github.com/ajnart/homarr/issues/new?assignees=&labels=%F0%9F%90%9B+Bug&template=bug.yml&title=New%20bug"
target="_blank"
mt="md"
fullWidth
>
{(this.props.t('modal.reportButton'))}
</Button>
</>
),
})
}
leftIcon={<IconInfoCircle size={16} />}
variant="light"
>
{this.props.t('card.buttons.details')}
</Button>
<Button
onClick={() => this.setState({ hasError: false })}
leftIcon={<IconRefresh size={16} />}
variant="light"
>
{this.props.t('card.buttons.tryAgain')}
</Button>
</Group>
</Stack>
</Center>
</Card>
);
}
// Return children components in case of no error
return this.props.children;
}
}
export default withTranslation('widgets/error-boundary')(ErrorBoundary);

11
turbo.json Normal file
View File

@@ -0,0 +1,11 @@
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"outputs": [
".next/**",
"!.next/cache/**"
]
}
}
}

View File

@@ -4936,7 +4936,7 @@ __metadata:
rss-parser: ^3.12.0
sabnzbd-api: ^1.5.0
sass: ^1.56.1
turbo: ^1.7.4
turbo: ^1.8.3
typescript: ^4.7.4
uuid: ^8.3.2
video.js: ^8.0.3
@@ -7936,58 +7936,58 @@ __metadata:
languageName: node
linkType: hard
"turbo-darwin-64@npm:1.8.0":
version: 1.8.0
resolution: "turbo-darwin-64@npm:1.8.0"
"turbo-darwin-64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-darwin-64@npm:1.8.3"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"turbo-darwin-arm64@npm:1.8.0":
version: 1.8.0
resolution: "turbo-darwin-arm64@npm:1.8.0"
"turbo-darwin-arm64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-darwin-arm64@npm:1.8.3"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"turbo-linux-64@npm:1.8.0":
version: 1.8.0
resolution: "turbo-linux-64@npm:1.8.0"
"turbo-linux-64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-linux-64@npm:1.8.3"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"turbo-linux-arm64@npm:1.8.0":
version: 1.8.0
resolution: "turbo-linux-arm64@npm:1.8.0"
"turbo-linux-arm64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-linux-arm64@npm:1.8.3"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"turbo-windows-64@npm:1.8.0":
version: 1.8.0
resolution: "turbo-windows-64@npm:1.8.0"
"turbo-windows-64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-windows-64@npm:1.8.3"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"turbo-windows-arm64@npm:1.8.0":
version: 1.8.0
resolution: "turbo-windows-arm64@npm:1.8.0"
"turbo-windows-arm64@npm:1.8.3":
version: 1.8.3
resolution: "turbo-windows-arm64@npm:1.8.3"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"turbo@npm:^1.7.4":
version: 1.8.0
resolution: "turbo@npm:1.8.0"
"turbo@npm:^1.8.3":
version: 1.8.3
resolution: "turbo@npm:1.8.3"
dependencies:
turbo-darwin-64: 1.8.0
turbo-darwin-arm64: 1.8.0
turbo-linux-64: 1.8.0
turbo-linux-arm64: 1.8.0
turbo-windows-64: 1.8.0
turbo-windows-arm64: 1.8.0
turbo-darwin-64: 1.8.3
turbo-darwin-arm64: 1.8.3
turbo-linux-64: 1.8.3
turbo-linux-arm64: 1.8.3
turbo-windows-64: 1.8.3
turbo-windows-arm64: 1.8.3
dependenciesMeta:
turbo-darwin-64:
optional: true
@@ -8003,7 +8003,7 @@ __metadata:
optional: true
bin:
turbo: bin/turbo
checksum: 7f97068d7f9a155e088d3575b1f9922e68fa3015aae0c92625238d44b4e6c275bec2a281907702dedb402fca29a6cd4690499e916cb334d7c24c98099bc3d8b0
checksum: 4a07d120ef8adf6c8e58a48abd02e075ffa215287cc6c3ef843d4fb08aeb0a566fe810ec9bfc376254468a2aa4f29bae154a60804a83af78dfa86d0e8e995476
languageName: node
linkType: hard