diff --git a/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx b/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx index 608000e05..9cd2bcdd8 100644 --- a/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx +++ b/src/components/Dashboard/Modals/EditAppModal/EditAppModal.tsx @@ -14,6 +14,7 @@ import { useState } from 'react'; import { useConfigContext } from '../../../../config/provider'; import { useConfigStore } from '../../../../config/store'; import { AppType } from '../../../../types/app'; +import { useEditModeStore } from '../../Views/useEditModeStore'; import { AppearanceTab } from './Tabs/AppereanceTab/AppereanceTab'; import { BehaviourTab } from './Tabs/BehaviourTab/BehaviourTab'; import { GeneralTab } from './Tabs/GeneralTab/GeneralTab'; @@ -33,6 +34,7 @@ export const EditAppModal = ({ const { t } = useTranslation(['layout/modals/add-app', 'common']); const { name: configName, config } = useConfigContext(); const updateConfig = useConfigStore((store) => store.updateConfig); + const { enabled: isEditMode } = useEditModeStore(); const [allowAppNamePropagation, setAllowAppNamePropagation] = useState( innerProps.allowAppNamePropagation ); @@ -87,9 +89,15 @@ export const EditAppModal = ({ configName, (previousConfig) => ({ ...previousConfig, - apps: [...previousConfig.apps.filter((x) => x.id !== form.values.id), form.values], + apps: [ + ...previousConfig.apps.filter((x) => x.id !== values.id), + { + ...values, + }, + ], }), - true + true, + !isEditMode ); // also close the parent modal diff --git a/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx b/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx index 2d38dfbe4..21345ef41 100644 --- a/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx +++ b/src/components/Dashboard/Modals/SelectElement/Components/Overview/AvailableElementsOverview.tsx @@ -3,8 +3,10 @@ import { IconBox, IconStack } from '@tabler/icons'; import { useTranslation } from 'next-i18next'; import { ReactNode } from 'react'; import { v4 as uuidv4 } from 'uuid'; +import { useConfigContext } from '../../../../../../config/provider'; import { openContextModalGeneric } from '../../../../../../tools/mantineModalManagerExtensions'; import { AppType } from '../../../../../../types/app'; +import { appTileDefinition } from '../../../../Tiles/Apps/AppTile'; import { useStyles } from '../Shared/styles'; interface AvailableElementTypesProps { @@ -17,6 +19,8 @@ export const AvailableElementTypes = ({ onOpenStaticElements, }: AvailableElementTypesProps) => { const { t } = useTranslation('layout/element-selector/selector'); + const { config } = useConfigContext(); + const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0]; return ( <> @@ -45,10 +49,11 @@ export const AvailableElementTypes = ({ isOpeningNewTab: true, externalUrl: '', }, + area: { - type: 'sidebar', + type: 'wrapper', properties: { - location: 'right', + id: getLowestWrapper()?.id ?? '', }, }, shape: { @@ -57,8 +62,8 @@ export const AvailableElementTypes = ({ y: 0, }, size: { - height: 1, - width: 1, + width: appTileDefinition.minWidth, + height: appTileDefinition.minHeight, }, }, integration: { diff --git a/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx index db486cfae..01caf6c8b 100644 --- a/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx +++ b/src/components/Dashboard/Modals/SelectElement/Components/WidgetsTab/WidgetElementType.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'next-i18next'; import { useConfigContext } from '../../../../../../config/provider'; import { useConfigStore } from '../../../../../../config/store'; import { IWidget, IWidgetDefinition } from '../../../../../../widgets/widgets'; +import { useEditModeStore } from '../../../../Views/useEditModeStore'; import { GenericAvailableElementType } from '../Shared/GenericElementType'; interface WidgetElementTypeProps { @@ -18,6 +19,7 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement const { t } = useTranslation(`modules/${id}`); const { name: configName, config } = useConfigContext(); const updateConfig = useConfigStore((x) => x.updateConfig); + const isEditMode = useEditModeStore((x) => x.enabled); if (!configName) return null; @@ -56,9 +58,10 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement }, ], }), - true + true, + !isEditMode ); - // TODO: safe to file system + closeModal('selectElement'); }; diff --git a/src/components/Dashboard/Tiles/Apps/AppTile.tsx b/src/components/Dashboard/Tiles/Apps/AppTile.tsx index 9c5a8c5e3..d577f8c37 100644 --- a/src/components/Dashboard/Tiles/Apps/AppTile.tsx +++ b/src/components/Dashboard/Tiles/Apps/AppTile.tsx @@ -88,3 +88,11 @@ const useStyles = createStyles((theme, _params, getRef) => ({ }, }, })); + +export const appTileDefinition = { + component: AppTile, + minWidth: 2, + maxWidth: 12, + minHeight: 2, + maxHeight: 12, +}; diff --git a/src/components/Dashboard/Tiles/tilesDefinitions.tsx b/src/components/Dashboard/Tiles/tilesDefinitions.tsx deleted file mode 100644 index 1cc23fa03..000000000 --- a/src/components/Dashboard/Tiles/tilesDefinitions.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import calendarDefinition from '../../../widgets/calendar/CalendarTile'; -import clockDefinition from '../../../widgets/date/DateTile'; -import dashDotDefinition from '../../../widgets/dashDot/DashDotTile'; -import useNetDefinition from '../../../widgets/useNet/UseNetTile'; -import weatherDefinition from '../../../widgets/weather/WeatherTile'; -import { EmptyTile } from './EmptyTile'; -import { AppTile } from './Apps/AppTile'; - -// TODO: just remove and use app (later app) directly. For widgets the the definition should contain min/max width/height -type TileDefinitionProps = { - [key in keyof any | 'app']: { - minWidth?: number; - minHeight?: number; - maxWidth?: number; - maxHeight?: number; - component: React.ElementType; - }; -}; - -export const Tiles: TileDefinitionProps = { - app: { - component: AppTile, - minWidth: 2, - maxWidth: 12, - minHeight: 2, - maxHeight: 12, - }, - bitTorrent: { - component: EmptyTile, - minWidth: 4, - maxWidth: 12, - minHeight: 5, - maxHeight: 12, - }, - calendar: { - component: calendarDefinition.component, - minWidth: 4, - maxWidth: 12, - minHeight: 5, - maxHeight: 12, - }, - clock: { - component: clockDefinition.component, - minWidth: 4, - maxWidth: 12, - minHeight: 2, - maxHeight: 12, - }, - dashDot: { - component: dashDotDefinition.component, - minWidth: 4, - maxWidth: 9, - minHeight: 5, - maxHeight: 14, - }, - torrentNetworkTraffic: { - component: EmptyTile, - minWidth: 4, - maxWidth: 12, - minHeight: 5, - maxHeight: 12, - }, - useNet: { - component: useNetDefinition.component, - minWidth: 4, - maxWidth: 12, - minHeight: 5, - maxHeight: 12, - }, - weather: { - component: weatherDefinition.component, - minWidth: 4, - maxWidth: 12, - minHeight: 2, - maxHeight: 12, - }, -}; diff --git a/src/components/Dashboard/Wrappers/WrapperContent.tsx b/src/components/Dashboard/Wrappers/WrapperContent.tsx index 657a8eae9..579bbd7c8 100644 --- a/src/components/Dashboard/Wrappers/WrapperContent.tsx +++ b/src/components/Dashboard/Wrappers/WrapperContent.tsx @@ -4,7 +4,7 @@ import { AppType } from '../../../types/app'; import Widgets from '../../../widgets'; import { IWidget, IWidgetDefinition } from '../../../widgets/widgets'; import { WidgetWrapper } from '../../../widgets/WidgetWrapper'; -import { Tiles } from '../Tiles/tilesDefinitions'; +import { appTileDefinition } from '../Tiles/Apps/AppTile'; import { GridstackTileWrapper } from '../Tiles/TileWrapper'; interface WrapperContentProps { @@ -21,7 +21,7 @@ export function WrapperContent({ apps, refs, widgets }: WrapperContentProps) { return ( <> {apps?.map((app) => { - const { component: TileComponent, ...tile } = Tiles.app; + const { component: TileComponent, ...tile } = appTileDefinition; return ( { + if (!configName || !config) { + return; + } + + updateConfig(configName, (_) => config, false, true); + }; return ( - - - - - - - - - - - + + + + + + + + + + + + + + + ); } diff --git a/src/components/Settings/SettingsDrawer.tsx b/src/components/Settings/SettingsDrawer.tsx index f951ae585..bdd4bc4e8 100644 --- a/src/components/Settings/SettingsDrawer.tsx +++ b/src/components/Settings/SettingsDrawer.tsx @@ -1,9 +1,9 @@ -import { Title, Drawer, Tabs, ScrollArea } from '@mantine/core'; +import { Drawer, ScrollArea, Tabs, Title } from '@mantine/core'; import { useTranslation } from 'next-i18next'; -import CustomizationSettings from './Customization/CustomizationSettings'; import CommonSettings from './Common/CommonSettings'; import Credits from './Common/Credits'; +import CustomizationSettings from './Customization/CustomizationSettings'; function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) { const { t } = useTranslation('settings/common'); @@ -20,9 +20,7 @@ function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) - - - + ); diff --git a/src/config/store.ts b/src/config/store.ts index 6e5acb0e8..a2842214f 100644 --- a/src/config/store.ts +++ b/src/config/store.ts @@ -1,3 +1,4 @@ +import axios from 'axios'; import create from 'zustand'; import { ConfigType } from '../types/config'; @@ -15,7 +16,8 @@ export const useConfigStore = create((set, get) => ({ updateConfig: async ( name, updateCallback: (previous: ConfigType) => ConfigType, - shouldRegenerateGridstack = false + shouldRegenerateGridstack = false, + shouldSaveConfigToFileSystem = false ) => { const { configs } = get(); const currentConfig = configs.find((x) => x.value.configProperties.name === name); @@ -23,7 +25,6 @@ export const useConfigStore = create((set, get) => ({ // copies the value of currentConfig and creates a non reference object named previousConfig const previousConfig: ConfigType = JSON.parse(JSON.stringify(currentConfig.value)); - // TODO: update config on server const updatedConfig = updateCallback(currentConfig.value); set((old) => ({ ...old, @@ -40,6 +41,10 @@ export const useConfigStore = create((set, get) => ({ ) { currentConfig.increaseVersion(); } + + if (shouldSaveConfigToFileSystem) { + axios.put(`/api/configs/${name}`, { ...updatedConfig }); + } }, })); @@ -51,6 +56,7 @@ interface UseConfigStoreType { updateCallback: (previous: ConfigType) => ConfigType, shouldRegenerateGridstace?: | boolean - | ((previousConfig: ConfigType, currentConfig: ConfigType) => boolean) + | ((previousConfig: ConfigType, currentConfig: ConfigType) => boolean), + shouldSaveConfigToFileSystem?: boolean ) => Promise; } diff --git a/src/modules/ModuleTypes.d.ts b/src/modules/ModuleTypes.d.ts index 937afc5a5..23d535b42 100644 --- a/src/modules/ModuleTypes.d.ts +++ b/src/modules/ModuleTypes.d.ts @@ -5,6 +5,7 @@ import { TablerIcon } from '@tabler/icons'; // Note: Maybe use context to keep track of the modules +// TODO: Remove this old component and the entire file export interface IModule { id: string; title: string; diff --git a/src/pages/api/configs/[slug].ts b/src/pages/api/configs/[slug].ts index 6f71ca257..e9f14d3c1 100644 --- a/src/pages/api/configs/[slug].ts +++ b/src/pages/api/configs/[slug].ts @@ -1,22 +1,61 @@ import { NextApiRequest, NextApiResponse } from 'next'; import fs from 'fs'; import path from 'path'; +import { BackendConfigType, ConfigType } from '../../../types/config'; +import { getConfig } from '../../../tools/config/getConfig'; function Put(req: NextApiRequest, res: NextApiResponse) { // Get the slug of the request const { slug } = req.query as { slug: string }; // Get the body of the request - const { body }: { body: string } = req; - if (!slug || !body) { - res.status(400).json({ + const { body: config }: { body: ConfigType } = req; + if (!slug || !config) { + return res.status(400).json({ error: 'Wrong request', }); } - // Save the body in the /data/config folder with the slug as filename + const previousConfig = getConfig(slug); + + const newConfig: BackendConfigType = { + ...config, + apps: [ + ...config.apps.map((app) => ({ + ...app, + integration: { + ...app.integration, + properties: app.integration.properties.map((property) => { + if (property.type === 'public') { + return { + field: property.field, + type: property.type, + value: property.value, + }; + } + + const previousApp = previousConfig.apps.find( + (previousApp) => previousApp.id === app.id + ); + + const previousProperty = previousApp?.integration?.properties.find( + (previousProperty) => previousProperty.field === property.field + ); + + return { + field: property.field, + type: property.type, + value: property.value !== undefined ? property.value : previousProperty?.value, + }; + }), + }, + })), + ], + }; + + // Save the body in the /data/config folder with the slug as filename fs.writeFileSync( path.join('data/configs', `${slug}.json`), - JSON.stringify(body, null, 2), + JSON.stringify(newConfig, null, 2), 'utf8' ); return res.status(200).json({ diff --git a/tsconfig.json b/tsconfig.json index 97fd02c6c..4cc7a24b0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "allowJs": true, "skipLibCheck": true, "strict": true, - "forceConsistentCasingInFileNames": true, + "forceConsistentCasingInFileNames": false, "noEmit": true, "esModuleInterop": true, "module": "esnext",