Merge branch 'dev' into feature/add-basic-authentication

This commit is contained in:
Meier Lukas
2023-08-06 15:02:57 +02:00
10 changed files with 1228 additions and 382 deletions

View File

@@ -4,6 +4,7 @@ import {
Button,
Card,
Center,
Flex,
Group,
Loader,
Modal,
@@ -19,6 +20,7 @@ import { useDisclosure } from '@mantine/hooks';
import { IconAlertTriangle, IconClick, IconListSearch } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { InfoCard } from '~/components/InfoCard/InfoCard';
import { City } from '~/server/api/routers/weather';
import { api } from '~/utils/api';
@@ -29,6 +31,8 @@ type LocationSelectionProps = {
propName: string;
value: any;
handleChange: (key: string, value: IntegrationOptionsValueType) => void;
info?: boolean;
infoLink?: string;
};
export const LocationSelection = ({
@@ -36,6 +40,8 @@ export const LocationSelection = ({
propName: key,
value,
handleChange,
info,
infoLink,
}: LocationSelectionProps) => {
const { t } = useTranslation('widgets/location');
const [query, setQuery] = useState(value.name ?? '');
@@ -57,7 +63,15 @@ export const LocationSelection = ({
<>
<Card>
<Stack spacing="xs">
<Title order={5}>{t(`modules/${widgetId}:descriptor.settings.${key}.label`)}</Title>
<Flex direction="row" justify="space-between" wrap="nowrap">
<Title order={5}>{t(`modules/${widgetId}:descriptor.settings.${key}.label`)}</Title>
{info && (
<InfoCard
message={t(`modules/${widgetId}:descriptor.settings.${key}.info`)}
link={infoLink}
/>
)}
</Flex>
<Group noWrap align="end">
<TextInput

View File

@@ -19,10 +19,10 @@ import { IconAlertTriangle, IconPlaylistX, IconPlus } from '@tabler/icons-react'
import { Trans, useTranslation } from 'next-i18next';
import { FC, useState } from 'react';
import { InfoCard } from '../../../InfoCard/InfoCard';
import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../../config/store';
import { mapObject } from '../../../../tools/client/objects';
import { useColorTheme } from '../../../../tools/color';
import Widgets from '../../../../widgets';
import type { IDraggableListInputValue, IWidgetOptionValue } from '../../../../widgets/widgets';
import { IWidget } from '../../../../widgets/widgets';
@@ -135,70 +135,99 @@ const WidgetOptionTypeSwitch: FC<{
handleChange: (key: string, value: IntegrationOptionsValueType) => void;
}> = ({ option, widgetId, propName: key, value, handleChange }) => {
const { t } = useTranslation([`modules/${widgetId}`, 'common']);
const { primaryColor } = useColorTheme();
const info = option.info ?? false;
const link = option.infoLink ?? undefined;
switch (option.type) {
case 'switch':
return (
<Switch
label={t(`descriptor.settings.${key}.label`)}
checked={value as boolean}
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
/>
<Group align="center" spacing="sm">
<Switch
label={t(`descriptor.settings.${key}.label`)}
checked={value as boolean}
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
{...option.inputProps}
/>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
);
case 'text':
return (
<TextInput
color={primaryColor}
label={t(`descriptor.settings.${key}.label`)}
value={value as string}
onChange={(ev) => handleChange(key, ev.currentTarget.value)}
/>
<Stack spacing={0}>
<Group align="center" spacing="sm">
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
<TextInput
value={value as string}
onChange={(ev) => handleChange(key, ev.currentTarget.value)}
{...option.inputProps}
/>
</Stack>
);
case 'multi-select':
return (
<MultiSelect
color={primaryColor}
data={option.data}
label={t(`descriptor.settings.${key}.label`)}
value={value as string[]}
defaultValue={option.defaultValue}
onChange={(v) => handleChange(key, v)}
/>
<Stack spacing={0}>
<Group align="center" spacing="sm">
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
<MultiSelect
data={option.data}
value={value as string[]}
defaultValue={option.defaultValue}
onChange={(v) => handleChange(key, v)}
withinPortal
{...option.inputProps}
/>
</Stack>
);
case 'select':
return (
<Select
color={primaryColor}
defaultValue={option.defaultValue}
data={option.data}
label={t(`descriptor.settings.${key}.label`)}
value={value as string}
onChange={(v) => handleChange(key, v ?? option.defaultValue)}
/>
<Stack spacing={0}>
<Group align="center" spacing="sm">
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
<Select
defaultValue={option.defaultValue}
data={option.data}
value={value as string}
onChange={(v) => handleChange(key, v ?? option.defaultValue)}
withinPortal
{...option.inputProps}
/>
</Stack>
);
case 'number':
return (
<NumberInput
color={primaryColor}
label={t(`descriptor.settings.${key}.label`)}
value={value as number}
onChange={(v) => handleChange(key, v!)}
{...option.inputProps}
/>
<Stack spacing={0}>
<Group align="center" spacing="sm">
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
<NumberInput
value={value as number}
onChange={(v) => handleChange(key, v!)}
{...option.inputProps}
/>
</Stack>
);
case 'slider':
return (
<Stack spacing="xs">
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
<Stack spacing={0}>
<Group align="center" spacing="sm">
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
<Slider
color={primaryColor}
label={value}
value={value as number}
min={option.min}
max={option.max}
step={option.step}
onChange={(v) => handleChange(key, v)}
{...option.inputProps}
/>
</Stack>
);
@@ -209,6 +238,8 @@ const WidgetOptionTypeSwitch: FC<{
value={value}
handleChange={handleChange}
widgetId={widgetId}
info={info}
infoLink={link}
/>
);
@@ -237,7 +268,10 @@ const WidgetOptionTypeSwitch: FC<{
return (
<Stack spacing="xs">
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
<Group align="center" spacing="sm">
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
<StaticDraggableList
value={typedVal}
onChange={(v) => handleChange(key, v)}
@@ -262,28 +296,36 @@ const WidgetOptionTypeSwitch: FC<{
);
case 'multiple-text':
return (
<MultiSelect
data={value.map((name: any) => ({ value: name, label: name }))}
label={t(`descriptor.settings.${key}.label`)}
description={t(`descriptor.settings.${key}.description`)}
defaultValue={value as string[]}
withinPortal
searchable
creatable
getCreateLabel={(query) => t('common:createItem', { item: query })}
onChange={(values) =>
handleChange(
key,
values.map((item: string) => item)
)
}
/>
<Stack spacing={0}>
<Group align="center" spacing="sm">
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
<MultiSelect
data={value.map((name: any) => ({ value: name, label: name }))}
description={t(`descriptor.settings.${key}.description`)}
defaultValue={value as string[]}
withinPortal
searchable
creatable
getCreateLabel={(query) => t('common:createItem', { item: query })}
onChange={(values) =>
handleChange(
key,
values.map((item: string) => item)
)
}
/>
</Stack>
);
case 'draggable-editable-list':
const { t: translateDraggableList } = useTranslation('widgets/draggable-list');
return (
<Stack spacing="xs">
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
<Group align="center" spacing="sm">
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
{info && <InfoCard message={t(`descriptor.settings.${key}.info`)} link={link}/>}
</Group>
<DraggableList
items={Array.from(value).map((v: any) => ({
data: v,

View File

@@ -0,0 +1,51 @@
import {
DefaultMantineColor,
HoverCard,
HoverCardProps,
SystemProp,
useMantineTheme,
} from '@mantine/core';
import { Link, RichTextEditor, RichTextEditorProps } from '@mantine/tiptap';
import { IconInfoCircle } from '@tabler/icons-react';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { useTranslation } from 'next-i18next';
interface InfoCardProps {
bg?: SystemProp<DefaultMantineColor>;
cardProp?: Partial<RichTextEditorProps>;
message: string;
link?: string;
hoverProp?: Partial<HoverCardProps>;
position?: HoverCardProps['position'];
}
export const InfoCard = ({ bg, cardProp, message, link, hoverProp, position }: InfoCardProps) => {
const { colorScheme } = useMantineTheme();
const { t } = useTranslation('common');
const content = link? message + ` <a href=\"${link}\" target=\"_blank\">${t('seeMore')}</a>` : message;
const editor = useEditor({
content,
editable: false,
editorProps: { attributes: { style: 'padding: 0;' } },
extensions: [StarterKit, Link],
});
return (
<HoverCard position={position ?? 'top'} radius="md" withArrow withinPortal {...hoverProp}>
<HoverCard.Target>
<IconInfoCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
</HoverCard.Target>
<HoverCard.Dropdown
bg={bg ?? colorScheme === 'light' ? 'gray.2' : 'dark.8'}
maw={400}
px="10px"
py="5px"
>
<RichTextEditor editor={editor} style={{ border: '0' }} {...cardProp}>
<RichTextEditor.Content bg="transparent" />
</RichTextEditor>
</HoverCard.Dropdown>
</HoverCard>
);
};