mirror of
https://github.com/ajnart/homarr.git
synced 2026-01-24 08:19:14 +01:00
♻️ Improve updating of client side board state
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { openRemoveItemModal, useItemActions } from '~/components/Board/Items/item-actions';
|
||||
import { type AppItem, useRequiredBoard } from '~/components/Board/context';
|
||||
import { type AppItem } from '~/components/Board/context';
|
||||
import { useResizeGridItem } from '~/components/Board/gridstack/useResizeGridItem';
|
||||
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
|
||||
|
||||
@@ -11,8 +11,7 @@ interface AppMenuProps {
|
||||
}
|
||||
|
||||
export const AppMenu = ({ app }: AppMenuProps) => {
|
||||
const board = useRequiredBoard();
|
||||
const { removeItem } = useItemActions({ boardName: board.name });
|
||||
const { removeItem } = useItemActions();
|
||||
const resizeGridItem = useResizeGridItem();
|
||||
|
||||
const handleClickEdit = () => {
|
||||
@@ -21,7 +20,6 @@ export const AppMenu = ({ app }: AppMenuProps) => {
|
||||
size: 'xl',
|
||||
innerProps: {
|
||||
app,
|
||||
board,
|
||||
allowAppNamePropagation: false,
|
||||
},
|
||||
styles: {
|
||||
@@ -37,7 +35,6 @@ export const AppMenu = ({ app }: AppMenuProps) => {
|
||||
modal: 'changeAppPositionModal',
|
||||
innerProps: {
|
||||
app,
|
||||
boardName: board.name,
|
||||
resizeGridItem,
|
||||
},
|
||||
styles: {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { CommonChangePositionModal } from '../CommonChangePositionModal';
|
||||
|
||||
type ChangeAppPositionModalInnerProps = {
|
||||
app: AppItem;
|
||||
boardName: string;
|
||||
resizeGridItem: ReturnType<typeof useResizeGridItem>;
|
||||
};
|
||||
|
||||
@@ -18,7 +17,7 @@ export const ChangeAppPositionModal = ({
|
||||
context,
|
||||
innerProps,
|
||||
}: ContextModalProps<ChangeAppPositionModalInnerProps>) => {
|
||||
const { moveAndResizeItem } = useItemActions({ boardName: innerProps.boardName });
|
||||
const { moveAndResizeItem } = useItemActions();
|
||||
|
||||
const handleSubmit = (x: number, y: number, width: number, height: number) => {
|
||||
moveAndResizeItem({
|
||||
|
||||
@@ -14,7 +14,6 @@ import { removeTrailingSlash } from 'next/dist/shared/lib/router/utils/remove-tr
|
||||
import { useState } from 'react';
|
||||
import { z } from 'zod';
|
||||
import { objectKeys } from '~/tools/object';
|
||||
import { RouterOutputs } from '~/utils/api';
|
||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||
import { appFormSchema } from '~/validations/app';
|
||||
|
||||
@@ -28,7 +27,6 @@ import { useAppActions } from './app-actions';
|
||||
|
||||
export type EditAppModalInnerProps = {
|
||||
app: AppItem;
|
||||
board: RouterOutputs['boards']['byName'];
|
||||
allowAppNamePropagation: boolean;
|
||||
};
|
||||
|
||||
@@ -39,7 +37,7 @@ export const EditAppModal = ({
|
||||
}: ContextModalProps<EditAppModalInnerProps>) => {
|
||||
const { t } = useTranslation(['layout/modals/add-app', 'common']);
|
||||
const [activeTab, setActiveTab] = useState<EditAppModalTab>('general');
|
||||
const { createOrUpdateApp } = useAppActions({ boardName: innerProps.board.name });
|
||||
const { createOrUpdateApp } = useAppActions();
|
||||
// TODO: change to ref
|
||||
const [allowAppNamePropagation, setAllowAppNamePropagation] = useState<boolean>(
|
||||
innerProps.allowAppNamePropagation
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { useCallback } from 'react';
|
||||
import { z } from 'zod';
|
||||
import { api } from '~/utils/api';
|
||||
import { appFormSchema } from '~/validations/app';
|
||||
|
||||
import { useUpdateBoard } from '../../board-actions';
|
||||
import { type AppItem, type EmptySection } from '../../context';
|
||||
|
||||
type CreateOrUpdateApp = {
|
||||
app: z.infer<typeof appFormSchema>;
|
||||
};
|
||||
|
||||
export const useAppActions = ({ boardName }: { boardName: string }) => {
|
||||
const utils = api.useContext();
|
||||
export const useAppActions = () => {
|
||||
const updateBoard = useUpdateBoard();
|
||||
const createOrUpdateApp = useCallback(
|
||||
({ app }: CreateOrUpdateApp) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
|
||||
let sectionId = prev.sections.find((section) =>
|
||||
@@ -40,7 +40,7 @@ export const useAppActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -10,7 +10,6 @@ import { CommonChangePositionModal } from '../CommonChangePositionModal';
|
||||
|
||||
export type WidgetChangePositionModalInnerProps = {
|
||||
widget: WidgetItem;
|
||||
boardName: string;
|
||||
wrapperColumnCount: number;
|
||||
resizeGridItem: ReturnType<typeof useResizeGridItem>;
|
||||
};
|
||||
@@ -20,7 +19,7 @@ export const ChangeWidgetPositionModal = ({
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<WidgetChangePositionModalInnerProps>) => {
|
||||
const { moveAndResizeItem } = useItemActions({ boardName: innerProps.boardName });
|
||||
const { moveAndResizeItem } = useItemActions();
|
||||
|
||||
const handleSubmit = (x: number, y: number, width: number, height: number) => {
|
||||
moveAndResizeItem({
|
||||
|
||||
@@ -38,7 +38,6 @@ export type WidgetEditModalInnerProps = {
|
||||
widgetType: string;
|
||||
options: Record<string, unknown>;
|
||||
widgetOptions: IWidgetDefinition['options'];
|
||||
boardName: string;
|
||||
};
|
||||
|
||||
export const WidgetsEditModal = ({
|
||||
@@ -52,7 +51,7 @@ export const WidgetsEditModal = ({
|
||||
|
||||
// Find the Key in the "Widgets" Object that matches the widgetId
|
||||
const currentWidgetDefinition = Widgets[innerProps.widgetType as keyof typeof Widgets];
|
||||
const { updateWidgetOptions } = useWidgetActions({ boardName: innerProps.boardName });
|
||||
const { updateWidgetOptions } = useWidgetActions();
|
||||
|
||||
if (!innerProps.options) return null;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Title } from '@mantine/core';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { openRemoveItemModal, useItemActions } from '~/components/Board/Items/item-actions';
|
||||
import { type WidgetItem, useRequiredBoard } from '~/components/Board/context';
|
||||
import { type WidgetItem } from '~/components/Board/context';
|
||||
import { useResizeGridItem } from '~/components/Board/gridstack/useResizeGridItem';
|
||||
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
|
||||
|
||||
@@ -17,10 +17,9 @@ interface WidgetsMenuProps {
|
||||
|
||||
export const WidgetsMenu = ({ widget }: WidgetsMenuProps) => {
|
||||
const { t } = useTranslation(`modules/${widget.sort}`);
|
||||
const board = useRequiredBoard();
|
||||
const wrapperColumnCount = useWrapperColumnCount();
|
||||
const resizeGridItem = useResizeGridItem();
|
||||
const { removeItem } = useItemActions({ boardName: board.name });
|
||||
const { removeItem } = useItemActions();
|
||||
|
||||
if (!widget || !wrapperColumnCount) return null;
|
||||
// Then get the widget definition
|
||||
@@ -44,7 +43,6 @@ export const WidgetsMenu = ({ widget }: WidgetsMenuProps) => {
|
||||
title: null,
|
||||
innerProps: {
|
||||
widget,
|
||||
boardName: board.name,
|
||||
wrapperColumnCount,
|
||||
resizeGridItem,
|
||||
},
|
||||
@@ -59,7 +57,6 @@ export const WidgetsMenu = ({ widget }: WidgetsMenuProps) => {
|
||||
widgetId: widget.id,
|
||||
widgetType: widget.sort,
|
||||
options: widget.options,
|
||||
boardName: board.name,
|
||||
widgetOptions: widgetDefinitionObject.options,
|
||||
},
|
||||
zIndex: 250,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useCallback } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
import { api } from '~/utils/api';
|
||||
import { widgetCreationSchema, widgetSortSchema } from '~/validations/widget';
|
||||
import { IWidgetDefinition } from '~/widgets/widgets';
|
||||
|
||||
import { useUpdateBoard } from '../../board-actions';
|
||||
import { EmptySection, WidgetItem } from '../../context';
|
||||
|
||||
type UpdateWidgetOptions = {
|
||||
@@ -17,11 +17,11 @@ type CreateWidget = {
|
||||
definition: IWidgetDefinition;
|
||||
};
|
||||
|
||||
export const useWidgetActions = ({ boardName }: { boardName: string }) => {
|
||||
const utils = api.useContext();
|
||||
export const useWidgetActions = () => {
|
||||
const updateBoard = useUpdateBoard();
|
||||
const updateWidgetOptions = useCallback(
|
||||
({ itemId, newOptions }: UpdateWidgetOptions) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
@@ -43,12 +43,12 @@ export const useWidgetActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
const createWidget = useCallback(
|
||||
({ sort, definition }: CreateWidget) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
|
||||
let lastSection = prev.sections
|
||||
@@ -82,7 +82,7 @@ export const useWidgetActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,8 @@ 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';
|
||||
|
||||
import { useUpdateBoard } from '../board-actions';
|
||||
|
||||
type MoveAndResizeItem = {
|
||||
itemId: string;
|
||||
@@ -23,11 +24,11 @@ type RemoveItem = {
|
||||
itemId: string;
|
||||
};
|
||||
|
||||
export const useItemActions = ({ boardName }: { boardName: string }) => {
|
||||
const utils = api.useContext();
|
||||
export const useItemActions = () => {
|
||||
const updateBoard = useUpdateBoard();
|
||||
const moveAndResizeItem = useCallback(
|
||||
({ itemId, ...positionProps }: MoveAndResizeItem) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
@@ -49,12 +50,12 @@ export const useItemActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
const moveItemToSection = useCallback(
|
||||
({ itemId, sectionId, ...positionProps }: MoveItemToSection) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
|
||||
const currentSection = prev.sections.find((section) =>
|
||||
@@ -99,12 +100,12 @@ export const useItemActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
const removeItem = useCallback(
|
||||
({ itemId }: RemoveItem) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
@@ -116,7 +117,7 @@ export const useItemActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -155,16 +156,3 @@ export const openRemoveItemModal = ({ name, onConfirm }: OpenRemoveItemModalProp
|
||||
onConfirm,
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
- Add category (on top, below, above)
|
||||
- Rename category
|
||||
- Move category (down & up)
|
||||
- Remove category
|
||||
- Add widget
|
||||
- Edit widget
|
||||
- Remove widget
|
||||
- Add app
|
||||
- Edit app
|
||||
- Remove app
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Consola from 'consola';
|
||||
import { useCallback } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
import { api } from '~/utils/api';
|
||||
import { useUpdateBoard } from '~/components/Board/board-actions';
|
||||
|
||||
import { type CategorySection, type EmptySection } from '../../../context';
|
||||
|
||||
@@ -24,8 +24,8 @@ type RemoveCategory = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const useCategoryActions = ({ boardName }: { boardName: string }) => {
|
||||
const utils = api.useContext();
|
||||
export const useCategoryActions = () => {
|
||||
const updateBoard = useUpdateBoard();
|
||||
|
||||
const addCategory = useCallback(
|
||||
({ name, position }: AddCategory) => {
|
||||
@@ -33,7 +33,7 @@ export const useCategoryActions = ({ boardName }: { boardName: string }) => {
|
||||
Consola.error('Cannot add category before first section');
|
||||
return;
|
||||
}
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
@@ -76,12 +76,58 @@ export const useCategoryActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
const addCategoryToEnd = useCallback(
|
||||
({ name }: { name: string }) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
|
||||
const lastSection = prev.sections
|
||||
.filter(
|
||||
(x): x is CategorySection | EmptySection => x.kind === 'empty' || x.kind === 'category'
|
||||
)
|
||||
.sort((a, b) => b.position - a.position)
|
||||
.at(0);
|
||||
|
||||
if (!lastSection) return prev;
|
||||
const lastPosition = lastSection.position;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
sections: [
|
||||
// Ignore sidebar and hidden sections
|
||||
...prev.sections.filter(
|
||||
(section) => section.kind === 'sidebar' || section.kind === 'hidden'
|
||||
),
|
||||
// Place sections before the new category
|
||||
...prev.sections.filter((section) => section.kind === 'category'),
|
||||
{
|
||||
id: v4(),
|
||||
name,
|
||||
kind: 'category',
|
||||
position: lastPosition + 1,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
id: v4(),
|
||||
kind: 'empty',
|
||||
position: lastPosition + 2,
|
||||
items: [],
|
||||
},
|
||||
// Place sections after the new category
|
||||
...prev.sections.filter((section) => section.kind === 'empty'),
|
||||
],
|
||||
};
|
||||
});
|
||||
},
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
const renameCategory = useCallback(
|
||||
({ id: categoryId, name }: RenameCategory) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
@@ -96,12 +142,12 @@ export const useCategoryActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
const moveCategory = useCallback(
|
||||
({ id, direction }: MoveCategory) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
|
||||
const currentCategory = prev.sections.find(
|
||||
@@ -155,12 +201,12 @@ export const useCategoryActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
const removeCategory = useCallback(
|
||||
({ id: categoryId }: RemoveCategory) => {
|
||||
utils.boards.byName.setData({ boardName, userAgent: navigator.userAgent }, (prev) => {
|
||||
updateBoard((prev) => {
|
||||
if (!prev) return prev;
|
||||
|
||||
const currentCategory = prev.sections.find(
|
||||
@@ -194,11 +240,12 @@ export const useCategoryActions = ({ boardName }: { boardName: string }) => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[boardName, utils]
|
||||
[updateBoard]
|
||||
);
|
||||
|
||||
return {
|
||||
addCategory,
|
||||
addCategoryToEnd,
|
||||
renameCategory,
|
||||
moveCategory,
|
||||
removeCategory,
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useCategoryActions } from '~/components/Board/Sections/Category/Actions/category-actions';
|
||||
import { useRequiredBoard } from '~/components/Board/context';
|
||||
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
|
||||
import { CategoryType } from '~/types/category';
|
||||
|
||||
import { CategoryEditModalInnerProps } from '../CategoryEditModal';
|
||||
|
||||
export const useCategoryMenuActions = (category: CategoryType) => {
|
||||
const boardName = useRequiredBoard().name;
|
||||
const { addCategory, moveCategory, removeCategory, renameCategory } = useCategoryActions({
|
||||
boardName,
|
||||
});
|
||||
const { addCategory, moveCategory, removeCategory, renameCategory } = useCategoryActions();
|
||||
|
||||
// creates a new category above the current
|
||||
const addCategoryAbove = () => {
|
||||
|
||||
@@ -9,33 +9,25 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { CategoryEditModalInnerProps } from '~/components/Board/Sections/Category/CategoryEditModal';
|
||||
import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions';
|
||||
import { generateDefaultApp2 } from '~/tools/shared/app';
|
||||
import { RouterOutputs } from '~/utils/api';
|
||||
|
||||
import { EditAppModalInnerProps } from '../../Items/App/EditAppModal';
|
||||
import { useCategoryActions } from '../../Sections/Category/Actions/category-actions';
|
||||
import { AppItem, CategorySection, EmptySection } from '../../context';
|
||||
import { AppItem } from '../../context';
|
||||
import { useStyles } from '../Shared/styles';
|
||||
|
||||
interface AvailableElementTypesProps {
|
||||
modalId: string;
|
||||
board: RouterOutputs['boards']['byName'];
|
||||
onOpenIntegrations: () => void;
|
||||
}
|
||||
|
||||
export const AvailableElementTypes = ({
|
||||
modalId,
|
||||
board,
|
||||
onOpenIntegrations: onOpenWidgets,
|
||||
}: AvailableElementTypesProps) => {
|
||||
const { t } = useTranslation('layout/element-selector/selector');
|
||||
const { addCategory } = useCategoryActions({ boardName: board.name });
|
||||
const lastSection = board.sections
|
||||
.filter((x): x is CategorySection | EmptySection => x.kind === 'empty' || x.kind === 'category')
|
||||
.sort((a, b) => b.position - a.position)
|
||||
.at(0);
|
||||
const { addCategoryToEnd } = useCategoryActions();
|
||||
|
||||
const onClickCreateCategory = async () => {
|
||||
if (!lastSection) return;
|
||||
openContextModalGeneric<CategoryEditModalInnerProps>({
|
||||
modal: 'categoryEditModal',
|
||||
title: t('category.newName'),
|
||||
@@ -47,7 +39,7 @@ export const AvailableElementTypes = ({
|
||||
position: 0, // doesn't matter, is being overwritten
|
||||
},
|
||||
onSuccess: async (category) => {
|
||||
addCategory({ name: category.name, position: lastSection.position + 1 });
|
||||
addCategoryToEnd({ name: category.name });
|
||||
closeModal(modalId);
|
||||
showNotification({
|
||||
title: t('category.created.title'),
|
||||
@@ -72,7 +64,6 @@ export const AvailableElementTypes = ({
|
||||
modal: 'editApp',
|
||||
innerProps: {
|
||||
app: generateDefaultApp2() as AppItem,
|
||||
board: board,
|
||||
allowAppNamePropagation: true,
|
||||
},
|
||||
size: 'xl',
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
import { ContextModalProps } from '@mantine/modals';
|
||||
import { useState } from 'react';
|
||||
import { RouterOutputs } from '~/utils/api';
|
||||
|
||||
import { AvailableElementTypes } from './Overview/AvailableElementsOverview';
|
||||
import { AvailableIntegrationElements } from './WidgetsTab/AvailableWidgetsTab';
|
||||
|
||||
type InnerProps = {
|
||||
board: RouterOutputs['boards']['byName'];
|
||||
};
|
||||
type InnerProps = {};
|
||||
|
||||
export const SelectElementModal = ({ id, innerProps }: ContextModalProps<InnerProps>) => {
|
||||
const [activeTab, setActiveTab] = useState<undefined | 'integrations'>();
|
||||
|
||||
if (activeTab === 'integrations') {
|
||||
return (
|
||||
<AvailableIntegrationElements
|
||||
modalId={id}
|
||||
boardName={innerProps.board.name}
|
||||
onClickBack={() => setActiveTab(undefined)}
|
||||
/>
|
||||
<AvailableIntegrationElements modalId={id} onClickBack={() => setActiveTab(undefined)} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AvailableElementTypes
|
||||
modalId={id}
|
||||
board={innerProps.board}
|
||||
onOpenIntegrations={() => setActiveTab('integrations')}
|
||||
/>
|
||||
<AvailableElementTypes modalId={id} onOpenIntegrations={() => setActiveTab('integrations')} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useStyles } from './styles';
|
||||
|
||||
interface GenericAvailableElementTypeProps {
|
||||
name: string;
|
||||
id: string;
|
||||
handleAddition: () => Promise<void>;
|
||||
description?: string;
|
||||
image: string | Icon;
|
||||
@@ -16,7 +15,6 @@ interface GenericAvailableElementTypeProps {
|
||||
|
||||
export const GenericAvailableElementType = ({
|
||||
name,
|
||||
id,
|
||||
description,
|
||||
image,
|
||||
disabled,
|
||||
|
||||
@@ -8,13 +8,11 @@ import { WidgetElementType } from './WidgetElementType';
|
||||
|
||||
interface AvailableIntegrationElementsProps {
|
||||
modalId: string;
|
||||
boardName: string;
|
||||
onClickBack: () => void;
|
||||
}
|
||||
|
||||
export const AvailableIntegrationElements = ({
|
||||
modalId,
|
||||
boardName,
|
||||
onClickBack,
|
||||
}: AvailableIntegrationElementsProps) => {
|
||||
const { t } = useTranslation('layout/element-selector/selector');
|
||||
@@ -28,14 +26,7 @@ export const AvailableIntegrationElements = ({
|
||||
|
||||
<Grid>
|
||||
{objectEntries(widgets).map(([k, v]) => (
|
||||
<WidgetElementType
|
||||
key={k}
|
||||
sort={k}
|
||||
boardName={boardName}
|
||||
image={v.icon}
|
||||
widget={v}
|
||||
modalId={modalId}
|
||||
/>
|
||||
<WidgetElementType key={k} sort={k} image={v.icon} widget={v} modalId={modalId} />
|
||||
))}
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
@@ -10,7 +10,6 @@ import { GenericAvailableElementType } from '../Shared/GenericElementType';
|
||||
|
||||
interface WidgetElementTypeProps {
|
||||
sort: WidgetSort;
|
||||
boardName: string;
|
||||
image: string | Icon;
|
||||
disabled?: boolean;
|
||||
widget: IWidgetDefinition;
|
||||
@@ -22,12 +21,11 @@ export const WidgetElementType = ({
|
||||
image,
|
||||
disabled,
|
||||
widget,
|
||||
boardName,
|
||||
modalId,
|
||||
}: WidgetElementTypeProps) => {
|
||||
const { closeModal } = useModals();
|
||||
const { t } = useTranslation(`modules/${sort}`);
|
||||
const { createWidget } = useWidgetActions({ boardName });
|
||||
const { createWidget } = useWidgetActions();
|
||||
|
||||
const handleAddition = async () => {
|
||||
createWidget({
|
||||
|
||||
19
src/components/Board/board-actions.ts
Normal file
19
src/components/Board/board-actions.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useCallback } from 'react';
|
||||
import { api } from '~/utils/api';
|
||||
|
||||
import { useRequiredBoardProps } from './outer-context';
|
||||
|
||||
export const useUpdateBoard = () => {
|
||||
const utils = api.useUtils();
|
||||
const { boardName, layoutId } = useRequiredBoardProps();
|
||||
|
||||
return useCallback(
|
||||
(callback: Parameters<(typeof utils)['boards']['byName']['setData']>[1]) => {
|
||||
utils.boards.byName.setData(
|
||||
{ boardName, layoutId, userAgent: navigator.userAgent },
|
||||
callback
|
||||
);
|
||||
},
|
||||
[boardName, layoutId, utils]
|
||||
);
|
||||
};
|
||||
@@ -3,10 +3,10 @@ import Router, { useRouter } from 'next/router';
|
||||
import { createContext, useContext, useEffect } from 'react';
|
||||
import { RouterOutputs, api } from '~/utils/api';
|
||||
|
||||
import { useRequiredBoardProps } from './outer-context';
|
||||
import { useEditModeStore } from './useEditModeStore';
|
||||
|
||||
type BoardContextType = {
|
||||
layout?: string;
|
||||
board: RouterOutputs['boards']['byName'];
|
||||
};
|
||||
const BoardContext = createContext<BoardContextType | null>(null);
|
||||
@@ -16,6 +16,7 @@ type BoardProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
export const BoardProvider = ({ children, userAgent, ...props }: BoardProviderProps) => {
|
||||
const { setProps } = useRequiredBoardProps();
|
||||
const { enabled } = useEditModeStore();
|
||||
const router = useRouter();
|
||||
const { layout } = router.query;
|
||||
@@ -32,6 +33,16 @@ export const BoardProvider = ({ children, userAgent, ...props }: BoardProviderPr
|
||||
}
|
||||
);
|
||||
|
||||
// Setting props for the outer context so they can be used within modals.
|
||||
useEffect(() => {
|
||||
if (!queryBoard) return;
|
||||
setProps({
|
||||
boardName: queryBoard.name,
|
||||
boardId: queryBoard.id,
|
||||
layoutId: layout as string | undefined,
|
||||
});
|
||||
}, [layout, queryBoard?.name, queryBoard?.id, setProps]);
|
||||
|
||||
const board = queryBoard ?? props.initialBoard; // Initialdata property is not working because it somehow ignores the enabled property.
|
||||
|
||||
useConfirmLeavePage(enabled);
|
||||
@@ -39,7 +50,6 @@ export const BoardProvider = ({ children, userAgent, ...props }: BoardProviderPr
|
||||
return (
|
||||
<BoardContext.Provider
|
||||
value={{
|
||||
...props,
|
||||
board: board!,
|
||||
}}
|
||||
>
|
||||
@@ -49,9 +59,9 @@ export const BoardProvider = ({ children, userAgent, ...props }: BoardProviderPr
|
||||
};
|
||||
|
||||
export const useRequiredBoard = () => {
|
||||
const ctx = useContext(BoardContext);
|
||||
if (!ctx) throw new Error('useBoard must be used within a BoardProvider');
|
||||
return ctx.board;
|
||||
const optionalBoard = useOptionalBoard();
|
||||
if (!optionalBoard) throw new Error('useBoard must be used within a BoardProvider');
|
||||
return optionalBoard;
|
||||
};
|
||||
|
||||
export const useOptionalBoard = () => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { useItemActions } from '~/components/Board/Items/item-actions';
|
||||
import { type Section, useRequiredBoard } from '~/components/Board/context';
|
||||
import { type Section } from '~/components/Board/context';
|
||||
|
||||
import { useEditModeStore } from '../useEditModeStore';
|
||||
import { initializeGridstack } from './init-gridstack';
|
||||
@@ -28,8 +28,7 @@ type UseGridstackProps = {
|
||||
|
||||
export const useGridstack = ({ section }: UseGridstackProps): UseGristackReturnType => {
|
||||
const isEditMode = useEditModeStore((x) => x.enabled);
|
||||
const board = useRequiredBoard();
|
||||
const { moveAndResizeItem, moveItemToSection } = useItemActions({ boardName: board.name });
|
||||
const { moveAndResizeItem, moveItemToSection } = useItemActions();
|
||||
// define reference for wrapper - is used to calculate the width of the wrapper
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
// references to the diffrent items contained in the gridstack
|
||||
|
||||
43
src/components/Board/outer-context.tsx
Normal file
43
src/components/Board/outer-context.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Dispatch, ReactNode, SetStateAction, createContext, useContext, useState } from 'react';
|
||||
|
||||
type BoardProps = {
|
||||
boardName: string;
|
||||
boardId: string;
|
||||
layoutId?: string;
|
||||
};
|
||||
|
||||
type OuterBoardContextType = Partial<BoardProps> & {
|
||||
setProps: Dispatch<SetStateAction<BoardProps | undefined>>;
|
||||
};
|
||||
|
||||
const OuterBoardContext = createContext<OuterBoardContextType | null>(null);
|
||||
|
||||
export const OuterBoardProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [props, setProps] = useState<BoardProps>();
|
||||
|
||||
return (
|
||||
<OuterBoardContext.Provider
|
||||
value={{
|
||||
...props,
|
||||
setProps,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</OuterBoardContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useRequiredBoardProps = () => {
|
||||
const optionalBoard = useOptionalBoardProps();
|
||||
if (!optionalBoard) throw new Error('useBoard must be used within a BoardProvider');
|
||||
return {
|
||||
boardName: optionalBoard.boardName!,
|
||||
boardId: optionalBoard.boardId!,
|
||||
...optionalBoard,
|
||||
};
|
||||
};
|
||||
|
||||
export const useOptionalBoardProps = () => {
|
||||
const ctx = useContext(OuterBoardContext);
|
||||
return ctx;
|
||||
};
|
||||
@@ -20,6 +20,7 @@ import { type AppProps } from 'next/app';
|
||||
import Script from 'next/script';
|
||||
import { useEffect, useState } from 'react';
|
||||
import 'video.js/dist/video-js.css';
|
||||
import { OuterBoardProvider } from '~/components/Board/outer-context';
|
||||
import { CommonHead } from '~/components/layout/Meta/CommonHead';
|
||||
import { env } from '~/env.js';
|
||||
import { ColorSchemeProvider } from '~/hooks/use-colorscheme';
|
||||
@@ -148,9 +149,11 @@ function App(
|
||||
withCSSVariables
|
||||
>
|
||||
<Notifications limit={4} position="bottom-left" />
|
||||
<ModalsProvider modals={modals}>
|
||||
<Component {...pageProps} />
|
||||
</ModalsProvider>
|
||||
<OuterBoardProvider>
|
||||
<ModalsProvider modals={modals}>
|
||||
<Component {...pageProps} />
|
||||
</ModalsProvider>
|
||||
</OuterBoardProvider>
|
||||
</MantineProvider>
|
||||
</ColorTheme.Provider>
|
||||
)}
|
||||
|
||||
@@ -52,6 +52,7 @@ import StarterKit from '@tiptap/starter-kit';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Dispatch, SetStateAction, useState } from 'react';
|
||||
import { useUpdateBoard } from '~/components/Board/board-actions';
|
||||
import { useRequiredBoard } from '~/components/Board/context';
|
||||
import { useEditModeStore } from '~/components/Board/useEditModeStore';
|
||||
import { useColorTheme } from '~/tools/color';
|
||||
@@ -70,7 +71,7 @@ export function Editor({ widget }: { widget: INotebookWidget }) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const board = useRequiredBoard();
|
||||
const utils = api.useContext();
|
||||
const updateBoard = useUpdateBoard();
|
||||
const { primaryColor } = useColorTheme();
|
||||
|
||||
const { mutateAsync } = api.notebook.update.useMutation();
|
||||
@@ -169,32 +170,29 @@ export function Editor({ widget }: { widget: INotebookWidget }) {
|
||||
editor.setEditable(current);
|
||||
if (current) return current;
|
||||
|
||||
utils.boards.byName.setData(
|
||||
{ boardName: board.name, userAgent: navigator.userAgent },
|
||||
(previous) => {
|
||||
if (!previous) return previous;
|
||||
return {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
if (!section.items.some((item) => item.id === widget.id)) return section;
|
||||
return {
|
||||
...section,
|
||||
items: section.items.map((item) => {
|
||||
if (item.id !== widget.id) return item;
|
||||
const notebookEditor = item as INotebookWidget;
|
||||
return {
|
||||
...notebookEditor,
|
||||
options: {
|
||||
...notebookEditor.options,
|
||||
content: debouncedContent,
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
);
|
||||
updateBoard((previous) => {
|
||||
if (!previous) return previous;
|
||||
return {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
if (!section.items.some((item) => item.id === widget.id)) return section;
|
||||
return {
|
||||
...section,
|
||||
items: section.items.map((item) => {
|
||||
if (item.id !== widget.id) return item;
|
||||
const notebookEditor = item as INotebookWidget;
|
||||
return {
|
||||
...notebookEditor,
|
||||
options: {
|
||||
...notebookEditor.options,
|
||||
content: debouncedContent,
|
||||
},
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
setToSaveContent(contentUpdate);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user