mirror of
https://github.com/ajnart/homarr.git
synced 2026-06-14 15:21:23 +02:00
✨ Add client side removing of items, Add item position modal
This commit is contained in:
24
src/components/Board/gridstack/useResizeGridItem.ts
Normal file
24
src/components/Board/gridstack/useResizeGridItem.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useGridItemRef } from '../item/context';
|
||||
import { useGridstackRef } from './context';
|
||||
|
||||
type ResizeGridItemProps = {
|
||||
height: number;
|
||||
width: number;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export const useResizeGridItem = () => {
|
||||
const itemRef = useGridItemRef();
|
||||
const gridstackRef = useGridstackRef();
|
||||
|
||||
return ({ height, width, ...options }: ResizeGridItemProps) => {
|
||||
gridstackRef.current?.batchUpdate();
|
||||
gridstackRef.current?.update(itemRef.current!, {
|
||||
...options,
|
||||
h: height,
|
||||
w: width,
|
||||
});
|
||||
gridstackRef.current?.batchUpdate(false);
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Text, Title } from '@mantine/core';
|
||||
import { openConfirmModal } from '@mantine/modals';
|
||||
import { useCallback } from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { api } from '~/utils/api';
|
||||
|
||||
type MoveAndResizeItem = {
|
||||
@@ -16,6 +19,9 @@ type MoveItemToSection = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
type RemoveItem = {
|
||||
itemId: string;
|
||||
};
|
||||
|
||||
export const useItemActions = ({ boardName }: { boardName: string }) => {
|
||||
const utils = api.useContext();
|
||||
@@ -33,7 +39,6 @@ export const useItemActions = ({ boardName }: { boardName: string }) => {
|
||||
items: section.items.map((item) => {
|
||||
// Return same item if item is not the one we're moving
|
||||
if (item.id !== itemId) return item;
|
||||
console.log(positionProps);
|
||||
return {
|
||||
...item,
|
||||
...positionProps,
|
||||
@@ -87,12 +92,60 @@ export const useItemActions = ({ boardName }: { boardName: string }) => {
|
||||
[boardName, utils]
|
||||
);
|
||||
|
||||
const removeItem = useCallback(
|
||||
({ itemId }: RemoveItem) => {
|
||||
utils.boards.byName.setData({ boardName }, (prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
// Filter removed item out of items array
|
||||
sections: prev.sections.map((section) => ({
|
||||
...section,
|
||||
items: section.items.filter((item) => item.id !== itemId),
|
||||
})),
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
);
|
||||
|
||||
return {
|
||||
moveAndResizeItem,
|
||||
moveItemToSection,
|
||||
removeItem,
|
||||
};
|
||||
};
|
||||
|
||||
type OpenRemoveItemModalProps = {
|
||||
name: string;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
export const openRemoveItemModal = ({ name, onConfirm }: OpenRemoveItemModalProps) => {
|
||||
openConfirmModal({
|
||||
title: (
|
||||
<Title order={4}>
|
||||
<Trans i18nKey="common:remove" />
|
||||
</Title>
|
||||
),
|
||||
children: (
|
||||
<Trans
|
||||
i18nKey="common:removeConfirm"
|
||||
components={[<Text weight={500} />]}
|
||||
values={{ item: name }}
|
||||
/>
|
||||
),
|
||||
labels: {
|
||||
cancel: <Trans i18nKey="common:cancel" />,
|
||||
confirm: <Trans i18nKey="common:ok" />,
|
||||
},
|
||||
cancelProps: {
|
||||
variant: 'light',
|
||||
},
|
||||
onConfirm,
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
- Add category (on top, below, above)
|
||||
- Rename category
|
||||
@@ -1,14 +1,16 @@
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { ContextModalProps, closeModal } from '@mantine/modals';
|
||||
import { type SelectItem } from '@mantine/core';
|
||||
import { type ContextModalProps, closeModal } from '@mantine/modals';
|
||||
import { type AppItem } from '~/components/Board/context';
|
||||
import { type useResizeGridItem } from '~/components/Board/gridstack/useResizeGridItem';
|
||||
import { useItemActions } from '~/components/Board/item-actions';
|
||||
|
||||
import { useConfigContext } from '~/config/provider';
|
||||
import { useConfigStore } from '~/config/store';
|
||||
import { AppType } from '~/types/app';
|
||||
import { useGridstackStore, useWrapperColumnCount } from '../../Wrappers/gridstack/store';
|
||||
import { ChangePositionModal } from './ChangePositionModal';
|
||||
|
||||
type ChangeAppPositionModalInnerProps = {
|
||||
app: AppType;
|
||||
app: AppItem;
|
||||
boardName: string;
|
||||
resizeGridItem: ReturnType<typeof useResizeGridItem>;
|
||||
};
|
||||
|
||||
export const ChangeAppPositionModal = ({
|
||||
@@ -16,34 +18,17 @@ export const ChangeAppPositionModal = ({
|
||||
context,
|
||||
innerProps,
|
||||
}: ContextModalProps<ChangeAppPositionModalInnerProps>) => {
|
||||
const { name: configName } = useConfigContext();
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
const shapeSize = useGridstackStore((x) => x.currentShapeSize);
|
||||
|
||||
if (!shapeSize) return null;
|
||||
const { moveAndResizeItem } = useItemActions({ boardName: innerProps.boardName });
|
||||
|
||||
const handleSubmit = (x: number, y: number, width: number, height: number) => {
|
||||
if (!configName) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateConfig(
|
||||
configName,
|
||||
(previousConfig) => ({
|
||||
...previousConfig,
|
||||
apps: [
|
||||
...previousConfig.apps.filter((x) => x.id !== innerProps.app.id),
|
||||
{
|
||||
...innerProps.app,
|
||||
shape: {
|
||||
...innerProps.app.shape,
|
||||
[shapeSize]: { location: { x, y }, size: { width, height } },
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
moveAndResizeItem({
|
||||
itemId: innerProps.app.id,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
innerProps.resizeGridItem({ x, y, width, height });
|
||||
context.closeModal(id);
|
||||
};
|
||||
|
||||
@@ -60,10 +45,10 @@ export const ChangeAppPositionModal = ({
|
||||
onCancel={handleCancel}
|
||||
widthData={widthData}
|
||||
heightData={heightData}
|
||||
initialX={innerProps.app.shape[shapeSize]?.location.x}
|
||||
initialY={innerProps.app.shape[shapeSize]?.location.y}
|
||||
initialWidth={innerProps.app.shape[shapeSize]?.size.width}
|
||||
initialHeight={innerProps.app.shape[shapeSize]?.size.height}
|
||||
initialX={innerProps.app.x}
|
||||
initialY={innerProps.app.y}
|
||||
initialWidth={innerProps.app.width}
|
||||
initialHeight={innerProps.app.height}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Button, Flex, Grid, NumberInput, Select, SelectItem } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfigContext } from '~/config/provider';
|
||||
|
||||
interface ChangePositionModalProps {
|
||||
initialX?: number;
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { ContextModalProps, closeModal } from '@mantine/modals';
|
||||
import { type SelectItem } from '@mantine/core';
|
||||
import { type ContextModalProps, closeModal } from '@mantine/modals';
|
||||
import { type WidgetItem } from '~/components/Board/context';
|
||||
import { type useResizeGridItem } from '~/components/Board/gridstack/useResizeGridItem';
|
||||
import { useItemActions } from '~/components/Board/item-actions';
|
||||
|
||||
import widgets from '../../../../widgets';
|
||||
import { WidgetChangePositionModalInnerProps } from '../../Tiles/Widgets/WidgetsMenu';
|
||||
import { useGridstackStore, useWrapperColumnCount } from '../../Wrappers/gridstack/store';
|
||||
import { ChangePositionModal } from './ChangePositionModal';
|
||||
|
||||
export type WidgetChangePositionModalInnerProps = {
|
||||
widget: WidgetItem;
|
||||
boardName: string;
|
||||
wrapperColumnCount: number;
|
||||
resizeGridItem: ReturnType<typeof useResizeGridItem>;
|
||||
};
|
||||
|
||||
export const ChangeWidgetPositionModal = ({
|
||||
context,
|
||||
id,
|
||||
@@ -22,7 +30,7 @@ export const ChangeWidgetPositionModal = ({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
innerProps.resizeGridItem({ x: x, y: y, w: width, h: height });
|
||||
innerProps.resizeGridItem({ x, y, width, height });
|
||||
|
||||
context.closeModal(id);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { AppItem } from '~/components/Board/context';
|
||||
import { useConfigContext } from '~/config/provider';
|
||||
import { useConfigStore } from '~/config/store';
|
||||
import { type AppItem, useRequiredBoard } from '~/components/Board/context';
|
||||
import { useResizeGridItem } from '~/components/Board/gridstack/useResizeGridItem';
|
||||
import { openRemoveItemModal, useItemActions } from '~/components/Board/item-actions';
|
||||
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
|
||||
import { AppType } from '~/types/app';
|
||||
|
||||
import { GenericTileMenu } from '../GenericTileMenu';
|
||||
|
||||
@@ -11,8 +10,9 @@ interface TileMenuProps {
|
||||
}
|
||||
|
||||
export const AppMenu = ({ app }: TileMenuProps) => {
|
||||
const { config, name: configName } = useConfigContext();
|
||||
const { updateConfig } = useConfigStore();
|
||||
const board = useRequiredBoard();
|
||||
const { removeItem } = useItemActions({ boardName: board.name });
|
||||
const resizeGridItem = useResizeGridItem();
|
||||
|
||||
const handleClickEdit = () => {
|
||||
openContextModalGeneric<{ app: AppItem; allowAppNamePropagation: boolean }>({
|
||||
@@ -35,6 +35,8 @@ export const AppMenu = ({ app }: TileMenuProps) => {
|
||||
modal: 'changeAppPositionModal',
|
||||
innerProps: {
|
||||
app,
|
||||
boardName: board.name,
|
||||
resizeGridItem,
|
||||
},
|
||||
styles: {
|
||||
root: {
|
||||
@@ -45,14 +47,14 @@ export const AppMenu = ({ app }: TileMenuProps) => {
|
||||
};
|
||||
|
||||
const handleClickDelete = () => {
|
||||
if (configName === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateConfig(configName, (previousConfig) => ({
|
||||
...previousConfig,
|
||||
apps: previousConfig.apps.filter((a) => a.id !== app.id),
|
||||
}));
|
||||
openRemoveItemModal({
|
||||
name: app.name,
|
||||
onConfirm() {
|
||||
removeItem({
|
||||
itemId: app.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { Title } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { WidgetItem, useRequiredBoard } from '~/components/Board/context';
|
||||
import { useGridstackRef } from '~/components/Board/gridstack/context';
|
||||
import { useGridItemRef } from '~/components/Board/item/context';
|
||||
import { type WidgetItem, useRequiredBoard } from '~/components/Board/context';
|
||||
import { useResizeGridItem } from '~/components/Board/gridstack/useResizeGridItem';
|
||||
import { openRemoveItemModal, useItemActions } from '~/components/Board/item-actions';
|
||||
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
|
||||
|
||||
import WidgetsDefinitions from '../../../../widgets';
|
||||
import { type WidgetChangePositionModalInnerProps } from '../../Modals/ChangePosition/ChangeWidgetPositionModal';
|
||||
import { useWrapperColumnCount } from '../../Wrappers/gridstack/store';
|
||||
import { GenericTileMenu } from '../GenericTileMenu';
|
||||
import { WidgetEditModalInnerProps } from './WidgetsEditModal';
|
||||
import { WidgetsRemoveModalInnerProps } from './WidgetsRemoveModal';
|
||||
|
||||
export type WidgetChangePositionModalInnerProps = {
|
||||
widget: WidgetItem;
|
||||
boardName: string;
|
||||
wrapperColumnCount: number;
|
||||
resizeGridItem: (options: { h: number; w: number; x: number; y: number }) => void;
|
||||
};
|
||||
import { type WidgetEditModalInnerProps } from './WidgetsEditModal';
|
||||
|
||||
interface WidgetsMenuProps {
|
||||
type: string;
|
||||
@@ -27,33 +20,27 @@ export const WidgetsMenu = ({ type, widget }: WidgetsMenuProps) => {
|
||||
const { t } = useTranslation(`modules/${type}`);
|
||||
const board = useRequiredBoard();
|
||||
const wrapperColumnCount = useWrapperColumnCount();
|
||||
const itemRef = useGridItemRef();
|
||||
const gridstackRef = useGridstackRef();
|
||||
|
||||
const resizeGridItem = (options: { h: number; w: number; x: number; y: number }) => {
|
||||
gridstackRef.current?.batchUpdate();
|
||||
gridstackRef.current?.update(itemRef.current!, options);
|
||||
gridstackRef.current?.batchUpdate(false);
|
||||
};
|
||||
const resizeGridItem = useResizeGridItem();
|
||||
const { removeItem } = useItemActions({ boardName: board.name });
|
||||
|
||||
if (!widget || !wrapperColumnCount) return null;
|
||||
// Then get the widget definition
|
||||
const widgetDefinitionObject = WidgetsDefinitions[widget.sort as keyof typeof WidgetsDefinitions];
|
||||
|
||||
const handleDeleteClick = () => {
|
||||
openContextModalGeneric<WidgetsRemoveModalInnerProps>({
|
||||
modal: 'integrationRemove',
|
||||
title: <Title order={4}>{t('common:remove')}</Title>,
|
||||
innerProps: {
|
||||
widgetId: widget.id,
|
||||
widgetType: type,
|
||||
openRemoveItemModal({
|
||||
name: widget.sort,
|
||||
onConfirm() {
|
||||
removeItem({
|
||||
itemId: widget.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeSizeClick = () => {
|
||||
openContextModalGeneric<WidgetChangePositionModalInnerProps>({
|
||||
modal: 'changeIntegrationPositionModal',
|
||||
modal: 'changeWidgetPositionModal',
|
||||
size: 'xl',
|
||||
title: null,
|
||||
innerProps: {
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Button, Group, Stack, Text } from '@mantine/core';
|
||||
import { ContextModalProps } from '@mantine/modals';
|
||||
import { Trans, useTranslation } from 'next-i18next';
|
||||
|
||||
import { useConfigContext } from '~/config/provider';
|
||||
import { useConfigStore } from '~/config/store';
|
||||
|
||||
export type WidgetsRemoveModalInnerProps = {
|
||||
widgetId: string;
|
||||
widgetType: string;
|
||||
};
|
||||
|
||||
export const WidgetsRemoveModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<WidgetsRemoveModalInnerProps>) => {
|
||||
const { t } = useTranslation([`modules/${innerProps.widgetType}`, 'common']);
|
||||
const { name: configName } = useConfigContext();
|
||||
if (!configName) return null;
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
const handleDeletion = () => {
|
||||
updateConfig(
|
||||
configName,
|
||||
(prev) => ({
|
||||
...prev,
|
||||
widgets: prev.widgets.filter((w) => w.id !== innerProps.widgetId),
|
||||
}),
|
||||
true
|
||||
);
|
||||
context.closeModal(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Trans
|
||||
i18nKey="common:removeConfirm"
|
||||
components={[<Text weight={500} />]}
|
||||
values={{ item: innerProps.widgetType }}
|
||||
/>
|
||||
<Group position="right">
|
||||
<Button onClick={() => context.closeModal(id)} variant="light">
|
||||
{t('common:cancel')}
|
||||
</Button>
|
||||
<Button onClick={() => handleDeletion()}>{t('common:ok')}</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import { ChangeWidgetPositionModal } from '~/components/Dashboard/Modals/ChangeP
|
||||
import { EditAppModal } from '~/components/Dashboard/Modals/EditAppModal/EditAppModal';
|
||||
import { SelectElementModal } from '~/components/Dashboard/Modals/SelectElement/SelectElementModal';
|
||||
import { WidgetsEditModal } from '~/components/Dashboard/Tiles/Widgets/WidgetsEditModal';
|
||||
import { WidgetsRemoveModal } from '~/components/Dashboard/Tiles/Widgets/WidgetsRemoveModal';
|
||||
import { CategoryEditModal } from '~/components/Dashboard/Wrappers/Category/CategoryEditModal';
|
||||
|
||||
import { CreateBoardModal } from './components/Manage/Board/create-board.modal';
|
||||
@@ -19,10 +18,9 @@ export const modals = {
|
||||
editApp: EditAppModal,
|
||||
selectElement: SelectElementModal,
|
||||
integrationOptions: WidgetsEditModal,
|
||||
integrationRemove: WidgetsRemoveModal,
|
||||
categoryEditModal: CategoryEditModal,
|
||||
changeAppPositionModal: ChangeAppPositionModal,
|
||||
changeIntegrationPositionModal: ChangeWidgetPositionModal,
|
||||
changeWidgetPositionModal: ChangeWidgetPositionModal,
|
||||
deleteUserModal: DeleteUserModal,
|
||||
createInviteModal: CreateInviteModal,
|
||||
deleteInviteModal: DeleteInviteModal,
|
||||
|
||||
Reference in New Issue
Block a user