From 0745921ed1e670732aa4d3835467b6d2ebbb3dd0 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Sun, 1 Oct 2023 15:54:43 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20client=20side=20widget=20modi?= =?UTF-8?q?fication,=20improve=20switching=20between=20edit=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Board/widget-actions.ts | 41 ++++++++++++++++ src/components/Dashboard/Dashboard.tsx | 4 +- .../ChangeWidgetPositionModal.tsx | 8 +-- .../Tiles/Widgets/WidgetsEditModal.tsx | 49 ++++++++----------- .../Dashboard/Tiles/Widgets/WidgetsMenu.tsx | 30 ++++-------- .../Dashboard/Wrappers/WrapperContent.tsx | 2 +- .../Wrappers/gridstack/init-gridstack.ts | 8 +-- .../Wrappers/gridstack/use-gridstack.ts | 15 ++++-- src/widgets/WidgetWrapper.tsx | 2 +- src/widgets/boundary.tsx | 2 +- 10 files changed, 96 insertions(+), 65 deletions(-) create mode 100644 src/components/Board/widget-actions.ts diff --git a/src/components/Board/widget-actions.ts b/src/components/Board/widget-actions.ts new file mode 100644 index 000000000..17aba4dd5 --- /dev/null +++ b/src/components/Board/widget-actions.ts @@ -0,0 +1,41 @@ +import { useCallback } from 'react'; +import { api } from '~/utils/api'; + +type UpdateWidgetOptions = { + itemId: string; + newOptions: Record; +}; + +export const useWidgetActions = ({ boardName }: { boardName: string }) => { + const utils = api.useContext(); + const updateWidgetOptions = useCallback( + ({ itemId, newOptions }: UpdateWidgetOptions) => { + utils.boards.byName.setData({ boardName }, (prev) => { + if (!prev) return prev; + return { + ...prev, + sections: prev.sections.map((section) => { + // Return same section if item is not in it + if (!section.items.some((item) => item.id === itemId)) return section; + return { + ...section, + items: section.items.map((item) => { + // Return same item if item is not the one we're moving + if (item.id !== itemId || item.type !== 'widget') return item; + return { + ...item, + options: newOptions, + }; + }), + }; + }), + }; + }); + }, + [boardName, utils] + ); + + return { + updateWidgetOptions, + }; +}; diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx index 0802b79d4..821e49bb1 100644 --- a/src/components/Dashboard/Dashboard.tsx +++ b/src/components/Dashboard/Dashboard.tsx @@ -1,7 +1,5 @@ import { MobileRibbons } from './Mobile/Ribbon/MobileRibbon'; import { BoardView } from './Views/DashboardView'; -import { DashboardDetailView } from './Views/DetailView'; -import { DashboardEditView } from './Views/EditView'; import { useEditModeStore } from './Views/useEditModeStore'; export const Board = () => { @@ -10,7 +8,7 @@ export const Board = () => { return ( <> {/* The following elemens are splitted because gridstack doesn't reinitialize them when using same item. */} - + ); diff --git a/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx b/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx index efa5b574c..c0092eaa8 100644 --- a/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx +++ b/src/components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal.tsx @@ -29,7 +29,7 @@ export const ChangeWidgetPositionModal = ({ updateConfig( configName, (prev) => { - const currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId); + const currentWidget = prev.widgets.find((x) => x.id === innerProps.widget.id); currentWidget!.shape[shapeSize] = { location: { x, @@ -43,7 +43,7 @@ export const ChangeWidgetPositionModal = ({ return { ...prev, - widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widgetId), currentWidget!], + widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widget.id), currentWidget!], }; }, true @@ -55,8 +55,8 @@ export const ChangeWidgetPositionModal = ({ closeModal(id); }; - const widthData = useWidthData(innerProps.widgetType); - const heightData = useHeightData(innerProps.widgetType); + const widthData = useWidthData(innerProps.widget.sort); + const heightData = useHeightData(innerProps.widget.sort); return ( ['properties']; - widgetOptions: IWidget['properties']; + options: Record; + widgetOptions: IWidgetDefinition['options']; + boardName: string; }; -export type IntegrationOptionsValueType = IWidget['properties'][string]; - export const WidgetsEditModal = ({ context, id, @@ -46,19 +51,15 @@ export const WidgetsEditModal = ({ }: ContextModalProps) => { const { t } = useTranslation([`modules/${innerProps.widgetType}`, 'common']); const [moduleProperties, setModuleProperties] = useState(innerProps.options); - const items = Object.entries(innerProps.widgetOptions ?? {}) as [ - string, - IntegrationOptionsValueType, - ][]; + const items = objectEntries(innerProps.widgetOptions ?? {}); // Find the Key in the "Widgets" Object that matches the widgetId const currentWidgetDefinition = Widgets[innerProps.widgetType as keyof typeof Widgets]; - const { name: configName } = useConfigContext(); - const updateConfig = useConfigStore((x) => x.updateConfig); + const { updateWidgetOptions } = useWidgetActions({ boardName: innerProps.boardName }); - if (!configName || !innerProps.options) return null; + if (!innerProps.options) return null; - const handleChange = (key: string, value: IntegrationOptionsValueType) => { + const handleChange = (key: string, value: unknown) => { setModuleProperties((prev) => { const copyOfPrev: any = { ...prev }; copyOfPrev[key] = value; @@ -67,19 +68,11 @@ export const WidgetsEditModal = ({ }; const handleSave = () => { - updateConfig( - configName, - (prev) => { - const currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId); - currentWidget!.properties = moduleProperties; + updateWidgetOptions({ + itemId: innerProps.widgetId, + newOptions: moduleProperties, + }); - return { - ...prev, - widgets: [...prev.widgets.filter((x) => x.id !== innerProps.widgetId), currentWidget!], - }; - }, - true - ); context.closeModal(id); }; @@ -132,7 +125,7 @@ const WidgetOptionTypeSwitch: FC<{ widgetId: string; propName: string; value: any; - handleChange: (key: string, value: IntegrationOptionsValueType) => void; + handleChange: (key: string, value: unknown) => void; }> = ({ option, widgetId, propName: key, value, handleChange }) => { const { t } = useTranslation([`modules/${widgetId}`, 'common']); const info = option.info ?? false; diff --git a/src/components/Dashboard/Tiles/Widgets/WidgetsMenu.tsx b/src/components/Dashboard/Tiles/Widgets/WidgetsMenu.tsx index 23574876e..6bde0b0df 100644 --- a/src/components/Dashboard/Tiles/Widgets/WidgetsMenu.tsx +++ b/src/components/Dashboard/Tiles/Widgets/WidgetsMenu.tsx @@ -1,8 +1,7 @@ import { Title } from '@mantine/core'; import { useTranslation } from 'next-i18next'; -import { WidgetItem } from '~/components/Board/context'; +import { WidgetItem, useRequiredBoard } from '~/components/Board/context'; import { openContextModalGeneric } from '~/tools/mantineModalManagerExtensions'; -import { IWidget } from '~/widgets/widgets'; import WidgetsDefinitions from '../../../../widgets'; import { useWrapperColumnCount } from '../../Wrappers/gridstack/store'; @@ -11,30 +10,23 @@ import { WidgetEditModalInnerProps } from './WidgetsEditModal'; import { WidgetsRemoveModalInnerProps } from './WidgetsRemoveModal'; export type WidgetChangePositionModalInnerProps = { - widgetId: string; - widgetType: string; widget: WidgetItem; wrapperColumnCount: number; }; interface WidgetsMenuProps { - integration: string; + type: string; widget: WidgetItem | undefined; } -export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => { - const { t } = useTranslation(`modules/${integration}`); +export const WidgetsMenu = ({ type, widget }: WidgetsMenuProps) => { + const { t } = useTranslation(`modules/${type}`); + const board = useRequiredBoard(); const wrapperColumnCount = useWrapperColumnCount(); if (!widget || !wrapperColumnCount) return null; - // Match widget.id with WidgetsDefinitions - // First get the keys - const keys = Object.keys(WidgetsDefinitions); - // Then find the key that matches the widget.type - const widgetDefinition = keys.find((key) => key === widget.type); // Then get the widget definition - const widgetDefinitionObject = - WidgetsDefinitions[widgetDefinition as keyof typeof WidgetsDefinitions]; + const widgetDefinitionObject = WidgetsDefinitions[widget.sort as keyof typeof WidgetsDefinitions]; const handleDeleteClick = () => { openContextModalGeneric({ @@ -42,7 +34,7 @@ export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => { title: {t('common:remove')}, innerProps: { widgetId: widget.id, - widgetType: integration, + widgetType: type, }, }); }; @@ -53,8 +45,6 @@ export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => { size: 'xl', title: null, innerProps: { - widgetId: widget.id, - widgetType: integration, widget, wrapperColumnCount, }, @@ -67,10 +57,10 @@ export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => { title: {t('descriptor.settings.title')}, innerProps: { widgetId: widget.id, - widgetType: integration, + widgetType: type, options: widget.options, - // Cast as the right type for the correct widget - widgetOptions: widgetDefinitionObject.options as any, + boardName: board.name, + widgetOptions: widgetDefinitionObject.options, }, zIndex: 250, }); diff --git a/src/components/Dashboard/Wrappers/WrapperContent.tsx b/src/components/Dashboard/Wrappers/WrapperContent.tsx index 733e847c0..dd68d4606 100644 --- a/src/components/Dashboard/Wrappers/WrapperContent.tsx +++ b/src/components/Dashboard/Wrappers/WrapperContent.tsx @@ -62,7 +62,7 @@ export function WrapperContent({ items, refs }: WrapperContentProps) { diff --git a/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts index f144f1c81..3745812a1 100644 --- a/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts +++ b/src/components/Dashboard/Wrappers/gridstack/init-gridstack.ts @@ -1,4 +1,4 @@ -import { GridStack, GridStackNode } from 'fily-publish-gridstack'; +import { GridItemHTMLElement, GridStack, GridStackNode } from 'fily-publish-gridstack'; import { MutableRefObject, RefObject } from 'react'; import { Item, Section } from '~/components/Board/context'; @@ -6,7 +6,7 @@ type InitializeGridstackProps = { section: Section; refs: { wrapper: RefObject; - items: MutableRefObject>>; + items: MutableRefObject>>; gridstack: MutableRefObject; }; isEditMode: boolean; @@ -73,12 +73,12 @@ export const initializeGridstack = ({ section.items.forEach((item) => { const ref = refs.items.current[item.id]?.current; setAttributesFromShape(ref, item); - ref && grid.makeWidget(ref as HTMLDivElement); + ref && grid.makeWidget(ref); }); grid.batchUpdate(false); }; -function setAttributesFromShape(ref: HTMLDivElement | null, item: Item) { +function setAttributesFromShape(ref: GridItemHTMLElement | null, item: Item) { if (!item || !ref) return; ref.setAttribute('gs-x', item.x.toString()); ref.setAttribute('gs-y', item.y.toString()); diff --git a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts index 9b486a55a..cc1d6fc75 100644 --- a/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts +++ b/src/components/Dashboard/Wrappers/gridstack/use-gridstack.ts @@ -1,4 +1,9 @@ -import { GridStack, GridStackNode } from 'fily-publish-gridstack'; +import { + GridItemHTMLElement, + GridStack, + GridStackElement, + GridStackNode, +} from 'fily-publish-gridstack'; import { MutableRefObject, RefObject, @@ -18,7 +23,7 @@ import { useGridstackStore, useWrapperColumnCount } from './store'; interface UseGristackReturnType { refs: { wrapper: RefObject; - items: MutableRefObject>>; + items: MutableRefObject>>; gridstack: MutableRefObject; }; } @@ -33,7 +38,7 @@ export const useGridstack = ({ section }: UseGridstackProps): UseGristackReturnT // define reference for wrapper - is used to calculate the width of the wrapper const wrapperRef = useRef(null); // references to the diffrent items contained in the gridstack - const itemRefs = useRef>>({}); + const itemRefs = useRef>>({}); // reference of the gridstack object for modifications after initialization const gridRef = useRef(); const sectionColumnCount = useWrapperColumnCount(); @@ -70,6 +75,10 @@ export const useGridstack = ({ section }: UseGridstackProps): UseGristackReturnT root?.style.setProperty('--gridstack-column-count', sectionColumnCount.toString()); }, [sectionColumnCount]); + useEffect(() => { + gridRef.current?.setStatic(!isEditMode); + }, [isEditMode]); + const onChange = useCallback( (changedNode: GridStackNode) => { if (!isEditMode) return; diff --git a/src/widgets/WidgetWrapper.tsx b/src/widgets/WidgetWrapper.tsx index b94cfd2cc..ddb9c45ef 100644 --- a/src/widgets/WidgetWrapper.tsx +++ b/src/widgets/WidgetWrapper.tsx @@ -43,7 +43,7 @@ export const WidgetWrapper = ({ return ( - + diff --git a/src/widgets/boundary.tsx b/src/widgets/boundary.tsx index cd55f0323..ad0b7da58 100644 --- a/src/widgets/boundary.tsx +++ b/src/widgets/boundary.tsx @@ -56,7 +56,7 @@ class ErrorBoundary extends React.Component - +