IFrame widget improvements (#15483)
changes - - make iframe side panel to match others -- ie use SidePanelHeader for title - make url optional in configuration to match that of the other widgets (allow partial saves) - render No data status when error or no url - split widget sizes into two -- graph widget sizes and widget sizes (graph widgets are a subset of chart widgets)
This commit is contained in:
@@ -1519,7 +1519,7 @@ export enum IdentityProviderType {
|
||||
|
||||
export type IframeConfiguration = {
|
||||
__typename?: 'IframeConfiguration';
|
||||
url: Scalars['String'];
|
||||
url?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ImapSmtpCaldavConnectionParameters = {
|
||||
|
||||
@@ -1476,7 +1476,7 @@ export enum IdentityProviderType {
|
||||
|
||||
export type IframeConfiguration = {
|
||||
__typename?: 'IframeConfiguration';
|
||||
url: Scalars['String'];
|
||||
url?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ImapSmtpCaldavConnectionParameters = {
|
||||
@@ -4705,7 +4705,7 @@ export type SearchQueryVariables = Exact<{
|
||||
|
||||
export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } };
|
||||
|
||||
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url: string } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null };
|
||||
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null };
|
||||
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -4713,7 +4713,7 @@ export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url: string } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } };
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } };
|
||||
|
||||
export type OnDbEventSubscriptionVariables = Exact<{
|
||||
input: OnDbEventInput;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CommandMenuCalendarEventPage } from '@/command-menu/pages/calendar-even
|
||||
import { CommandMenuMessageThreadPage } from '@/command-menu/pages/message-thread/components/CommandMenuMessageThreadPage';
|
||||
import { CommandMenuPageLayoutGraphFilter } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter';
|
||||
import { CommandMenuPageLayoutGraphTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect';
|
||||
import { CommandMenuPageLayoutIframeConfig } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig';
|
||||
import { CommandMenuPageLayoutIframeSettings } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeSettings';
|
||||
import { CommandMenuPageLayoutWidgetTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect';
|
||||
import { CommandMenuPageLayoutTabSettings } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings';
|
||||
import { CommandMenuMergeRecordPage } from '@/command-menu/pages/record-page/components/CommandMenuMergeRecordPage';
|
||||
@@ -55,8 +55,8 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
|
||||
<CommandMenuPageLayoutGraphFilter />,
|
||||
],
|
||||
[
|
||||
CommandMenuPages.PageLayoutIframeConfig,
|
||||
<CommandMenuPageLayoutIframeConfig />,
|
||||
CommandMenuPages.PageLayoutIframeSettings,
|
||||
<CommandMenuPageLayoutIframeSettings />,
|
||||
],
|
||||
[
|
||||
CommandMenuPages.PageLayoutTabSettings,
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
const isPageLayoutEditingPage =
|
||||
currentPage === CommandMenuPages.PageLayoutWidgetTypeSelect ||
|
||||
currentPage === CommandMenuPages.PageLayoutGraphTypeSelect ||
|
||||
currentPage === CommandMenuPages.PageLayoutIframeConfig ||
|
||||
currentPage === CommandMenuPages.PageLayoutIframeSettings ||
|
||||
currentPage === CommandMenuPages.PageLayoutTabSettings;
|
||||
|
||||
if (isPageLayoutEditingPage) {
|
||||
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
|
||||
import { useCreatePageLayoutIframeWidget } from '@/page-layout/hooks/useCreatePageLayoutIframeWidget';
|
||||
import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { isValidUrl } from 'twenty-shared/utils';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledSectionTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
padding-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const CommandMenuPageLayoutIframeConfig = () => {
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
|
||||
const { createPageLayoutIframeWidget } =
|
||||
useCreatePageLayoutIframeWidget(pageLayoutId);
|
||||
|
||||
const { updatePageLayoutWidget } = useUpdatePageLayoutWidget(pageLayoutId);
|
||||
|
||||
const [pageLayoutEditingWidgetId, setPageLayoutEditingWidgetId] =
|
||||
useRecoilComponentState(
|
||||
pageLayoutEditingWidgetIdComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
const pageLayoutDraft = useRecoilComponentValue(
|
||||
pageLayoutDraftComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const allWidgets = pageLayoutDraft.tabs.flatMap((tab) => tab.widgets);
|
||||
|
||||
const editingWidget = allWidgets.find(
|
||||
(w) => w.id === pageLayoutEditingWidgetId,
|
||||
);
|
||||
const isEditMode = !!editingWidget;
|
||||
|
||||
const [title, setTitle] = useState(editingWidget?.title || '');
|
||||
|
||||
const configUrl =
|
||||
editingWidget?.configuration && 'url' in editingWidget.configuration
|
||||
? editingWidget.configuration.url
|
||||
: undefined;
|
||||
|
||||
const [url, setUrl] = useState(isString(configUrl) ? configUrl : '');
|
||||
|
||||
const [urlError, setUrlError] = useState('');
|
||||
|
||||
const validateUrl = (urlString: string): boolean => {
|
||||
const trimmedUrl = urlString.trim();
|
||||
|
||||
if (!isValidUrl(trimmedUrl)) {
|
||||
setUrlError('Please enter a valid URL');
|
||||
return false;
|
||||
}
|
||||
|
||||
setUrlError('');
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleUrlChange = (value: string) => {
|
||||
setUrl(value);
|
||||
validateUrl(value);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!title.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateUrl(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEditMode && pageLayoutEditingWidgetId !== null) {
|
||||
updatePageLayoutWidget(pageLayoutEditingWidgetId, {
|
||||
title: title.trim(),
|
||||
configuration: {
|
||||
...editingWidget?.configuration,
|
||||
url: url.trim(),
|
||||
},
|
||||
});
|
||||
|
||||
setPageLayoutEditingWidgetId(null);
|
||||
} else {
|
||||
createPageLayoutIframeWidget(title.trim(), url.trim());
|
||||
}
|
||||
|
||||
closeCommandMenu();
|
||||
};
|
||||
|
||||
const isFormValid = title.trim() && url.trim() && !urlError;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledSectionTitle>
|
||||
{isEditMode ? t`Edit iFrame Widget` : t`Configure iFrame Widget`}
|
||||
</StyledSectionTitle>
|
||||
|
||||
<FormTextFieldInput
|
||||
label={t`Widget Title`}
|
||||
placeholder={t`e.g., Analytics Dashboard`}
|
||||
defaultValue={title}
|
||||
onChange={setTitle}
|
||||
/>
|
||||
|
||||
<FormTextFieldInput
|
||||
label="URL to Embed"
|
||||
placeholder="https://example.com/embed"
|
||||
defaultValue={url}
|
||||
onChange={handleUrlChange}
|
||||
error={urlError}
|
||||
/>
|
||||
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
title={isEditMode ? 'Save Changes' : 'Create Widget'}
|
||||
onClick={handleSubmit}
|
||||
disabled={!isFormValid}
|
||||
variant="primary"
|
||||
size="small"
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
|
||||
import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString, isString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { isDefined, isValidUrl } from 'twenty-shared/utils';
|
||||
import { IconFrame } from 'twenty-ui/display';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const CommandMenuPageLayoutIframeSettings = () => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
const { updatePageLayoutWidget } = useUpdatePageLayoutWidget(pageLayoutId);
|
||||
|
||||
if (!isDefined(widgetInEditMode)) {
|
||||
throw new Error('Widget ID must be present while editing the widget');
|
||||
}
|
||||
|
||||
const configUrl =
|
||||
widgetInEditMode.configuration && 'url' in widgetInEditMode.configuration
|
||||
? widgetInEditMode.configuration.url
|
||||
: null;
|
||||
|
||||
const [url, setUrl] = useState<string | null>(
|
||||
isString(configUrl) ? configUrl : null,
|
||||
);
|
||||
const [urlError, setUrlError] = useState('');
|
||||
|
||||
const validateUrl = (urlString: string): boolean => {
|
||||
const trimmedUrl = urlString.trim();
|
||||
|
||||
if (!isNonEmptyString(trimmedUrl)) {
|
||||
setUrlError('');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isValidUrl(trimmedUrl)) {
|
||||
setUrlError(t`Please enter a valid URL`);
|
||||
return false;
|
||||
}
|
||||
|
||||
setUrlError('');
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleUrlChange = (value: string) => {
|
||||
setUrl(value);
|
||||
|
||||
if (validateUrl(value)) {
|
||||
const trimmedValue = value.trim();
|
||||
updatePageLayoutWidget(widgetInEditMode.id, {
|
||||
configuration: {
|
||||
...widgetInEditMode.configuration,
|
||||
url: trimmedValue || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidePanelHeader
|
||||
Icon={IconFrame}
|
||||
iconColor={theme.font.color.tertiary}
|
||||
initialTitle={widgetInEditMode.title}
|
||||
headerType={t`iFrame Widget`}
|
||||
onTitleChange={(newTitle) => {
|
||||
if (!isNonEmptyString(newTitle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updatePageLayoutWidget(widgetInEditMode.id, {
|
||||
title: newTitle,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<StyledContainer>
|
||||
<FormTextFieldInput
|
||||
label={t`URL to Embed`}
|
||||
placeholder="https://example.com/embed"
|
||||
defaultValue={url}
|
||||
onChange={handleUrlChange}
|
||||
error={urlError}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+15
-7
@@ -6,6 +6,7 @@ import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pa
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { useCompanyDefaultChartConfig } from '@/page-layout/hooks/useCompanyDefaultChartConfig';
|
||||
import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget';
|
||||
import { useCreatePageLayoutIframeWidget } from '@/page-layout/hooks/useCreatePageLayoutIframeWidget';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
@@ -24,6 +25,9 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => {
|
||||
const { createPageLayoutGraphWidget } =
|
||||
useCreatePageLayoutGraphWidget(pageLayoutId);
|
||||
|
||||
const { createPageLayoutIframeWidget } =
|
||||
useCreatePageLayoutIframeWidget(pageLayoutId);
|
||||
|
||||
const [pageLayoutEditingWidgetId, setPageLayoutEditingWidgetId] =
|
||||
useRecoilComponentState(
|
||||
pageLayoutEditingWidgetIdComponentState,
|
||||
@@ -46,9 +50,15 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleNavigateToIframeConfig = () => {
|
||||
const handleNavigateToIframeSettings = () => {
|
||||
if (!isDefined(pageLayoutEditingWidgetId)) {
|
||||
const newWidget = createPageLayoutIframeWidget('Untitled iFrame', null);
|
||||
|
||||
setPageLayoutEditingWidgetId(newWidget.id);
|
||||
}
|
||||
|
||||
navigatePageLayoutCommandMenu({
|
||||
commandMenuPage: CommandMenuPages.PageLayoutIframeConfig,
|
||||
commandMenuPage: CommandMenuPages.PageLayoutIframeSettings,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -68,15 +78,13 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => {
|
||||
</SelectableListItem>
|
||||
<SelectableListItem
|
||||
itemId="iframe"
|
||||
onEnter={() => {
|
||||
handleNavigateToIframeConfig();
|
||||
}}
|
||||
onEnter={handleNavigateToIframeSettings}
|
||||
>
|
||||
<CommandMenuItem
|
||||
Icon={IconFrame}
|
||||
label="iFrame"
|
||||
label={t`iFrame`}
|
||||
id="iframe"
|
||||
onClick={handleNavigateToIframeConfig}
|
||||
onClick={handleNavigateToIframeSettings}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</CommandGroup>
|
||||
|
||||
+1
-1
@@ -3,6 +3,6 @@ import { type CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
export type PageLayoutCommandMenuPage =
|
||||
| CommandMenuPages.PageLayoutWidgetTypeSelect
|
||||
| CommandMenuPages.PageLayoutGraphTypeSelect
|
||||
| CommandMenuPages.PageLayoutIframeConfig
|
||||
| CommandMenuPages.PageLayoutIframeSettings
|
||||
| CommandMenuPages.PageLayoutGraphFilter
|
||||
| CommandMenuPages.PageLayoutTabSettings;
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export const getPageLayoutIcon = (page: PageLayoutCommandMenuPage) => {
|
||||
return IconAppWindow;
|
||||
case CommandMenuPages.PageLayoutGraphTypeSelect:
|
||||
return IconChartPie;
|
||||
case CommandMenuPages.PageLayoutIframeConfig:
|
||||
case CommandMenuPages.PageLayoutIframeSettings:
|
||||
return IconFrame;
|
||||
case CommandMenuPages.PageLayoutGraphFilter:
|
||||
return IconFilter;
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ export const getPageLayoutPageTitle = (page: PageLayoutCommandMenuPage) => {
|
||||
return t`Add Widget`;
|
||||
case CommandMenuPages.PageLayoutGraphTypeSelect:
|
||||
return t`Select Graph Type`;
|
||||
case CommandMenuPages.PageLayoutIframeConfig:
|
||||
return t`Configure iFrame`;
|
||||
case CommandMenuPages.PageLayoutIframeSettings:
|
||||
return t`iFrame Settings`;
|
||||
case CommandMenuPages.PageLayoutGraphFilter:
|
||||
return t`Configure filters`;
|
||||
case CommandMenuPages.PageLayoutTabSettings:
|
||||
|
||||
@@ -18,6 +18,6 @@ export enum CommandMenuPages {
|
||||
PageLayoutWidgetTypeSelect = 'page-layout-widget-type-select',
|
||||
PageLayoutGraphTypeSelect = 'page-layout-graph-type-select',
|
||||
PageLayoutGraphFilter = 'page-layout-graph-filter',
|
||||
PageLayoutIframeConfig = 'page-layout-iframe-config',
|
||||
PageLayoutIframeSettings = 'page-layout-iframe-settings',
|
||||
PageLayoutTabSettings = 'page-layout-tab-settings',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { type WidgetSizeConfig } from '@/page-layout/types/WidgetSizeConfig';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const GRAPH_WIDGET_SIZES: Record<GraphType, WidgetSizeConfig> = {
|
||||
[GraphType.AGGREGATE]: {
|
||||
default: { w: 2, h: 2 },
|
||||
minimum: { w: 2, h: 2 },
|
||||
},
|
||||
[GraphType.GAUGE]: {
|
||||
default: { w: 3, h: 4 },
|
||||
minimum: { w: 3, h: 4 },
|
||||
},
|
||||
[GraphType.PIE]: {
|
||||
default: { w: 4, h: 4 },
|
||||
minimum: { w: 3, h: 4 },
|
||||
},
|
||||
[GraphType.VERTICAL_BAR]: {
|
||||
default: { w: 6, h: 6 },
|
||||
minimum: { w: 4, h: 4 },
|
||||
},
|
||||
[GraphType.HORIZONTAL_BAR]: {
|
||||
default: { w: 6, h: 6 },
|
||||
minimum: { w: 4, h: 4 },
|
||||
},
|
||||
[GraphType.LINE]: {
|
||||
default: { w: 6, h: 10 },
|
||||
minimum: { w: 4, h: 4 },
|
||||
},
|
||||
};
|
||||
@@ -1,33 +1,9 @@
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
import { type WidgetSizeConfig } from '@/page-layout/types/WidgetSizeConfig';
|
||||
import { WidgetType } from '~/generated/graphql';
|
||||
|
||||
type WidgetSizeConfig = {
|
||||
default: { w: number; h: number };
|
||||
minimum: { w: number; h: number };
|
||||
};
|
||||
|
||||
export const WIDGET_SIZES: Record<GraphType, WidgetSizeConfig> = {
|
||||
[GraphType.AGGREGATE]: {
|
||||
default: { w: 2, h: 2 },
|
||||
minimum: { w: 2, h: 2 },
|
||||
},
|
||||
[GraphType.GAUGE]: {
|
||||
default: { w: 3, h: 4 },
|
||||
minimum: { w: 3, h: 4 },
|
||||
},
|
||||
[GraphType.PIE]: {
|
||||
default: { w: 4, h: 4 },
|
||||
minimum: { w: 3, h: 4 },
|
||||
},
|
||||
[GraphType.VERTICAL_BAR]: {
|
||||
export const WIDGET_SIZES: Partial<Record<WidgetType, WidgetSizeConfig>> = {
|
||||
[WidgetType.IFRAME]: {
|
||||
default: { w: 6, h: 6 },
|
||||
minimum: { w: 4, h: 4 },
|
||||
},
|
||||
[GraphType.HORIZONTAL_BAR]: {
|
||||
default: { w: 6, h: 6 },
|
||||
minimum: { w: 4, h: 4 },
|
||||
},
|
||||
[GraphType.LINE]: {
|
||||
default: { w: 6, h: 10 },
|
||||
minimum: { w: 4, h: 4 },
|
||||
minimum: { w: 4, h: 5 },
|
||||
},
|
||||
};
|
||||
|
||||
+13
-6
@@ -1,4 +1,4 @@
|
||||
import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
|
||||
import { WIDGET_SIZES } from '@/page-layout/constants/WidgetSizes';
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
@@ -13,7 +13,9 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { type PageLayoutWidget, WidgetType } from '~/generated/graphql';
|
||||
|
||||
export const useCreatePageLayoutIframeWidget = (
|
||||
pageLayoutIdFromProps?: string,
|
||||
@@ -45,7 +47,7 @@ export const useCreatePageLayoutIframeWidget = (
|
||||
|
||||
const createPageLayoutIframeWidget = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(title: string, url: string) => {
|
||||
(title: string, url: string | null): PageLayoutWidget => {
|
||||
const allTabLayouts = snapshot
|
||||
.getLoadable(pageLayoutCurrentLayoutsState)
|
||||
.getValue();
|
||||
@@ -54,13 +56,16 @@ export const useCreatePageLayoutIframeWidget = (
|
||||
.getLoadable(pageLayoutDraggedAreaState)
|
||||
.getValue();
|
||||
|
||||
if (!activeTabId) {
|
||||
return;
|
||||
if (!isDefined(activeTabId)) {
|
||||
throw new Error(
|
||||
'A tab must be selected to create a new iframe widget',
|
||||
);
|
||||
}
|
||||
|
||||
const widgetId = uuidv4();
|
||||
const defaultIframeSize = { w: 6, h: 6 };
|
||||
const minimumSize = DEFAULT_WIDGET_SIZE.minimum;
|
||||
const iframeSize = WIDGET_SIZES[WidgetType.IFRAME]!;
|
||||
const defaultIframeSize = iframeSize.default;
|
||||
const minimumSize = iframeSize.minimum;
|
||||
const position = getDefaultWidgetPosition(
|
||||
pageLayoutDraggedArea,
|
||||
defaultIframeSize,
|
||||
@@ -104,6 +109,8 @@ export const useCreatePageLayoutIframeWidget = (
|
||||
}));
|
||||
|
||||
set(pageLayoutDraggedAreaState, null);
|
||||
|
||||
return newWidget;
|
||||
},
|
||||
[
|
||||
activeTabId,
|
||||
|
||||
@@ -35,7 +35,7 @@ export const useEditPageLayoutWidget = (pageLayoutIdFromProps?: string) => {
|
||||
|
||||
if (widgetType === WidgetType.IFRAME) {
|
||||
navigatePageLayoutCommandMenu({
|
||||
commandMenuPage: CommandMenuPages.PageLayoutIframeConfig,
|
||||
commandMenuPage: CommandMenuPages.PageLayoutIframeSettings,
|
||||
pageTitle: t`Edit iFrame`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export type WidgetSizeConfig = {
|
||||
default: { w: number; h: number };
|
||||
minimum: { w: number; h: number };
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
|
||||
import { WIDGET_SIZES } from '@/page-layout/constants/WidgetSizes';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
import { type TabLayouts } from '@/page-layout/types/tab-layouts';
|
||||
import { getWidgetSize } from '@/page-layout/utils/getWidgetSize';
|
||||
@@ -32,6 +33,12 @@ export const convertPageLayoutToTabLayouts = (
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.type === WidgetType.IFRAME) {
|
||||
const iframeMinimumSize = WIDGET_SIZES[WidgetType.IFRAME]!.minimum;
|
||||
minW = iframeMinimumSize.w;
|
||||
minH = iframeMinimumSize.h;
|
||||
}
|
||||
|
||||
return {
|
||||
i: widget.id,
|
||||
x: widget.gridPosition.column,
|
||||
|
||||
@@ -8,7 +8,7 @@ export const createDefaultIframeWidget = (
|
||||
id: string,
|
||||
pageLayoutTabId: string,
|
||||
title: string,
|
||||
url: string,
|
||||
url: string | null,
|
||||
gridPosition: GridPosition,
|
||||
objectMetadataId?: string | null,
|
||||
): PageLayoutWidget => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { DEFAULT_WIDGET_SIZE } from '@/page-layout/constants/DefaultWidgetSize';
|
||||
import { WIDGET_SIZES } from '@/page-layout/constants/WidgetSizes';
|
||||
import { GRAPH_WIDGET_SIZES } from '@/page-layout/constants/GraphWidgetSizes';
|
||||
import { type GraphType } from '~/generated/graphql';
|
||||
|
||||
export const getWidgetSize = (
|
||||
graphType: GraphType,
|
||||
type: 'default' | 'minimum',
|
||||
): { w: number; h: number } => {
|
||||
const sizeConfig = WIDGET_SIZES[graphType] ?? DEFAULT_WIDGET_SIZE;
|
||||
const sizeConfig = GRAPH_WIDGET_SIZES[graphType] ?? DEFAULT_WIDGET_SIZE;
|
||||
return sizeConfig[type];
|
||||
};
|
||||
|
||||
+20
-25
@@ -1,24 +1,30 @@
|
||||
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
|
||||
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { type PageLayoutWidget } from '~/generated-metadata/graphql';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type PageLayoutWidget } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
background: ${({ theme }) => theme.background.transparent.lighter};
|
||||
const StyledContainer = styled.div<{ $isEditMode: boolean }>`
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
pointer-events: ${({ $isEditMode }) => ($isEditMode ? 'none' : 'auto')};
|
||||
`;
|
||||
|
||||
const StyledIframe = styled.iframe`
|
||||
const StyledIframe = styled.iframe<{ $isEditMode: boolean }>`
|
||||
border: none;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: ${({ $isEditMode }) => ($isEditMode ? 'none' : 'auto')};
|
||||
`;
|
||||
|
||||
const StyledLoadingContainer = styled.div`
|
||||
@@ -44,30 +50,19 @@ const StyledErrorContainer = styled.div`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const StyledErrorMessage = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledErrorUrl = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||
word-break: break-all;
|
||||
`;
|
||||
|
||||
export type IframeWidgetProps = {
|
||||
widget: PageLayoutWidget;
|
||||
};
|
||||
|
||||
export const IframeWidget = ({ widget }: IframeWidgetProps) => {
|
||||
const isPageLayoutInEditMode = useRecoilComponentValue(
|
||||
isPageLayoutInEditModeComponentState,
|
||||
);
|
||||
|
||||
const configuration = widget.configuration;
|
||||
|
||||
if (!configuration || !('url' in configuration)) {
|
||||
throw new Error(
|
||||
`Invalid configuration for widget ${widget.id}: missing url`,
|
||||
);
|
||||
throw new Error(`Invalid configuration for widget ${widget.id}`);
|
||||
}
|
||||
|
||||
const url = configuration.url;
|
||||
@@ -85,25 +80,25 @@ export const IframeWidget = ({ widget }: IframeWidgetProps) => {
|
||||
setHasError(true);
|
||||
};
|
||||
|
||||
if (hasError) {
|
||||
if (hasError || !isDefined(url)) {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledContainer $isEditMode={isPageLayoutInEditMode}>
|
||||
<StyledErrorContainer>
|
||||
<StyledErrorMessage>Failed to load content</StyledErrorMessage>
|
||||
<StyledErrorUrl>{url}</StyledErrorUrl>
|
||||
<PageLayoutWidgetNoDataDisplay widgetId={widget.id} />
|
||||
</StyledErrorContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledContainer $isEditMode={isPageLayoutInEditMode}>
|
||||
{isLoading && (
|
||||
<StyledLoadingContainer>
|
||||
<ChartSkeletonLoader />
|
||||
</StyledLoadingContainer>
|
||||
)}
|
||||
<StyledIframe
|
||||
$isEditMode={isPageLayoutInEditMode}
|
||||
src={url}
|
||||
title={title}
|
||||
onLoad={handleIframeLoad}
|
||||
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsString, IsUrl } from 'class-validator';
|
||||
import { IsOptional, IsString, IsUrl } from 'class-validator';
|
||||
|
||||
@ObjectType('IframeConfiguration')
|
||||
export class IframeConfigurationDTO {
|
||||
@Field(() => String)
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
url: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
+5
-15
@@ -1,17 +1,14 @@
|
||||
import {
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
INVALID_IFRAME_CONFIG_MISSING_URL,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
TEST_GAUGE_CHART_CONFIG,
|
||||
TEST_GAUGE_CHART_CONFIG_MINIMAL,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_IFRAME_CONFIG,
|
||||
TEST_LINE_CHART_CONFIG,
|
||||
TEST_LINE_CHART_CONFIG_MINIMAL,
|
||||
@@ -19,6 +16,8 @@ import {
|
||||
TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
TEST_PIE_CHART_CONFIG,
|
||||
TEST_PIE_CHART_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
} from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
|
||||
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
|
||||
@@ -44,15 +43,6 @@ describe('validateAndTransformWidgetConfiguration', () => {
|
||||
).toThrow(/url must be a URL address/);
|
||||
});
|
||||
|
||||
it('should throw error for missing URL', () => {
|
||||
expect(() =>
|
||||
validateAndTransformWidgetConfiguration(
|
||||
WidgetType.IFRAME,
|
||||
INVALID_IFRAME_CONFIG_MISSING_URL,
|
||||
),
|
||||
).toThrow(/url must be a URL address/);
|
||||
});
|
||||
|
||||
it('should throw error for empty URL', () => {
|
||||
expect(() =>
|
||||
validateAndTransformWidgetConfiguration(
|
||||
|
||||
-3
@@ -167,8 +167,6 @@ export const INVALID_IFRAME_CONFIG_BAD_URL = {
|
||||
url: 'not-a-valid-url',
|
||||
};
|
||||
|
||||
export const INVALID_IFRAME_CONFIG_MISSING_URL = {};
|
||||
|
||||
export const INVALID_IFRAME_CONFIG_EMPTY_URL = {
|
||||
url: '',
|
||||
};
|
||||
@@ -246,7 +244,6 @@ export const ALL_MINIMAL_GRAPH_CONFIGS = [
|
||||
|
||||
export const ALL_INVALID_CONFIGS = [
|
||||
INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
INVALID_IFRAME_CONFIG_MISSING_URL,
|
||||
INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
|
||||
Reference in New Issue
Block a user