Skip to content

Commit e754349

Browse files
authored
fix(edit-content): Reset UI state when navigating between portlets (dotCMS#31410)
Introduced a utility function to initialize the content edit session storage with default values while preserving the sidebar open state. This ensures a clean UI state when switching between different portlets and prevents potential state-related issues. This pull request introduces several changes aimed at improving the handling of UI state in the `edit-content` module by switching from localStorage to sessionStorage and adding a new utility function. The most important changes include importing and using the `initContentEditSessionStorage` function, modifying how the UI state is managed, and adding tests for the new functionality. Improvements to UI state management: * [`core-web/apps/dotcms-ui/src/app/view/components/_common/iframe/iframe-porlet-legacy/iframe-porlet-legacy.component.ts`](diffhunk://#diff-5159a82fd083797bc8483174e232b49f0d5a0edc3d2fbda5c37213b3e6e5590cR12): Added import for `initContentEditSessionStorage` and called it in the `ngOnInit` method to initialize the UI state. [[1]](diffhunk://#diff-5159a82fd083797bc8483174e232b49f0d5a0edc3d2fbda5c37213b3e6e5590cR12) [[2]](diffhunk://#diff-5159a82fd083797bc8483174e232b49f0d5a0edc3d2fbda5c37213b3e6e5590cR67-R69) * [`core-web/libs/edit-content/src/index.ts`](diffhunk://#diff-8e4ebf6e3bd0e536ddd7567da45f2092c49a46801b2e8cfa22531cfd312d137fR3): Exported the `initContentEditSessionStorage` function. * [`core-web/libs/edit-content/src/lib/store/features/ui.feature.ts`](diffhunk://#diff-b48dd48cbc0230fe3f75c2f1ef1f26972cc446fc4bbc43a55adc22f74b27f888L59-R64): Modified the `activeSidebarTab` computed property to return 0 if the initial state is 'new'. * [`core-web/libs/edit-content/src/lib/utils/functions.util.ts`](diffhunk://#diff-99ac6732d97f8e73c67b5464dfb645a4ae4ec51a80005cf22d7f83a27dc43c74L316-R325): Replaced localStorage with sessionStorage for storing UI state and added the `initContentEditSessionStorage` function to initialize the UI state with default values while preserving the `isSidebarOpen` value. [[1]](diffhunk://#diff-99ac6732d97f8e73c67b5464dfb645a4ae4ec51a80005cf22d7f83a27dc43c74L316-R325) [[2]](diffhunk://#diff-99ac6732d97f8e73c67b5464dfb645a4ae4ec51a80005cf22d7f83a27dc43c74L336-R356) Testing enhancements: * [`core-web/libs/edit-content/src/lib/utils/functions.util.spec.ts`](diffhunk://#diff-001fcca242290eaaace5b2b311d3fe695f710dc2c0dca7738785c21fef92e1adR28-R29): Added tests for the `initContentEditSessionStorage` function to ensure it initializes the UI state correctly under various conditions. [[1]](diffhunk://#diff-001fcca242290eaaace5b2b311d3fe695f710dc2c0dca7738785c21fef92e1adR28-R29) [[2]](diffhunk://#diff-001fcca242290eaaace5b2b311d3fe695f710dc2c0dca7738785c21fef92e1adR807-R894) ### Checklist - [x] Tests - [ ] Translations - [ ] Security Implications Contemplated (add notes if applicable) ### Additional Info ** any additional useful context or info ** ### Screenshots Original | Updated :-------------------------:|:-------------------------: ** original screenshot ** | ** updated screenshot **
1 parent 4f65bac commit e754349

File tree

11 files changed

+812
-89
lines changed

11 files changed

+812
-89
lines changed

.cursor/rules/dotcms-angular-rules.mdc

Lines changed: 643 additions & 0 deletions
Large diffs are not rendered by default.

.cursor/settings.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"workspaceIndexing": {
3+
"ignorePatterns": [
4+
"**/node_modules/**",
5+
"**/dist/**",
6+
"**/.nx/**",
7+
"**/*.timestamp-*",
8+
"**/.vite/**"
9+
]
10+
}
11+
}

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,3 @@ dotCMS/dependencies.gradle
182182
.cursorrules
183183
.cursorignore
184184
**/vite.config.mts.timestamp-*
185-
.cursor/

core-web/apps/dotcms-ui/src/app/view/components/_common/iframe/iframe-porlet-legacy/iframe-porlet-legacy.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { DotCustomEventHandlerService } from '@dotcms/app/api/services/dot-custo
99
import { DotMenuService } from '@dotcms/app/api/services/dot-menu.service';
1010
import { DotContentTypeService, DotIframeService, DotRouterService } from '@dotcms/data-access';
1111
import { DotcmsEventsService, LoggerService, SiteService } from '@dotcms/dotcms-js';
12+
import { UI_STORAGE_KEY } from '@dotcms/dotcms-models';
1213
import { DotLoadingIndicatorService } from '@dotcms/utils';
1314

1415
@Component({
@@ -63,6 +64,12 @@ export class IframePortletLegacyComponent implements OnInit, OnDestroy {
6364
});
6465

6566
this.subscribeToAIGeneration();
67+
68+
// Workaroud to remove edit-content ui state
69+
this.#initContentEditSessionStorage();
70+
}
71+
#initContentEditSessionStorage() {
72+
sessionStorage.removeItem(UI_STORAGE_KEY);
6673
}
6774

6875
ngOnDestroy(): void {
Lines changed: 58 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,80 @@
1+
export * from './lib/content-type-view.model';
2+
export * from './lib/dot-action-bulk-request-options.model';
3+
export * from './lib/dot-action-bulk-result.model';
4+
export * from './lib/dot-action-menu-item.model';
5+
export * from './lib/dot-ai.model';
6+
export * from './lib/dot-ajax-action-response';
7+
export * from './lib/dot-alert-confirm.model';
8+
export * from './lib/dot-apps.model';
9+
export * from './lib/dot-asset-create-options.model';
10+
export * from './lib/dot-block-editor.model';
11+
export * from './lib/dot-bundle';
12+
export * from './lib/dot-categories.model';
13+
export * from './lib/dot-container.model';
14+
export * from './lib/dot-content-analytics.model';
15+
export * from './lib/dot-content-compare.model';
116
export * from './lib/dot-content-state.model';
217
export * from './lib/dot-content-types.model';
318
export * from './lib/dot-contentlet.model';
4-
export * from './lib/dot-tree-node.model';
5-
export * from './lib/dot-block-editor.model';
6-
export * from './lib/dot-push-publish-dialog-data.model';
7-
export * from './lib/dot-temp-file.model';
8-
export * from './lib/dot-workflow-action.model';
9-
export * from './lib/dot-workflow.model';
10-
export * from './lib/dot-asset-create-options.model';
11-
export * from './lib/dot-file-metadata.model';
19+
export * from './lib/dot-contentlets-events.model';
20+
export * from './lib/dot-current-user';
21+
export * from './lib/dot-device.model';
22+
export * from './lib/dot-dialog.model';
23+
export * from './lib/dot-dynamic-field-component.model';
24+
export * from './lib/dot-edit-content.model';
25+
export * from './lib/dot-edit-page-view-as.model';
1226
export * from './lib/dot-environment.model';
27+
export * from './lib/dot-es-content.model';
28+
export * from './lib/dot-event';
29+
export * from './lib/dot-experiments-constants';
30+
export * from './lib/dot-experiments.model';
31+
export * from './lib/dot-field-variable.model';
32+
export * from './lib/dot-file-metadata.model';
1333
export * from './lib/dot-function-info.model';
1434
export * from './lib/dot-global-message.model';
1535
export * from './lib/dot-http-error-response.model';
1636
export * from './lib/dot-http-request-options.model';
17-
export * from './lib/dot-login.model';
18-
export * from './lib/dot-license.model';
19-
export * from './lib/dot-dynamic-field-component.model';
2037
export * from './lib/dot-iframe-edit-event.model';
21-
export * from './lib/dot-persona.model';
22-
export * from './lib/dot-action-bulk-request-options.model';
23-
export * from './lib/dot-action-bulk-result.model';
24-
export * from './lib/dot-page-mode.enum';
25-
export * from './lib/dot-page.model';
26-
export * from './lib/dot-layout.model';
27-
export * from './lib/dot-container.model';
28-
export * from './lib/dot-device.model';
2938
export * from './lib/dot-language.model';
3039
export * from './lib/dot-layout-body.model';
31-
export * from './lib/dot-layout-sidebar.model';
32-
export * from './lib/dot-layout-row.model';
3340
export * from './lib/dot-layout-column.model';
41+
export * from './lib/dot-layout-row.model';
42+
export * from './lib/dot-layout-sidebar.model';
43+
export * from './lib/dot-layout.model';
44+
export * from './lib/dot-license.model';
45+
export * from './lib/dot-locale-options.model';
46+
export * from './lib/dot-login.model';
47+
export * from './lib/dot-message-severity.model';
48+
export * from './lib/dot-message-type.model';
49+
export * from './lib/dot-message.model';
3450
export * from './lib/dot-page-container.model';
35-
export * from './lib/dot-template.model';
36-
export * from './lib/dot-site.model';
51+
export * from './lib/dot-page-content.model';
52+
export * from './lib/dot-page-mode.enum';
53+
export * from './lib/dot-page-render.model';
54+
export * from './lib/dot-page-tool.model';
55+
export * from './lib/dot-page.model';
56+
export * from './lib/dot-persona.model';
57+
export * from './lib/dot-personalize.model';
58+
export * from './lib/dot-push-publish-data.model';
59+
export * from './lib/dot-push-publish-dialog-data.model';
60+
export * from './lib/dot-rendered-page-state.model';
3761
export * from './lib/dot-rendered-page.model';
38-
export * from './lib/dot-current-user';
39-
export * from './lib/dot-bundle';
40-
export * from './lib/dot-ajax-action-response';
41-
export * from './lib/dot-alert-confirm.model';
42-
export * from './lib/dot-apps.model';
43-
export * from './lib/content-type-view.model';
44-
export * from './lib/structure-type.model';
45-
export * from './lib/structure-type-view.model';
4662
export * from './lib/dot-role.model';
63+
export * from './lib/dot-site.model';
4764
export * from './lib/dot-tag.model';
65+
export * from './lib/dot-temp-file.model';
66+
export * from './lib/dot-template.model';
4867
export * from './lib/dot-theme.model';
49-
export * from './lib/dot-edit-page-view-as.model';
50-
export * from './lib/dot-environment.model';
51-
export * from './lib/dot-locale-options.model';
68+
export * from './lib/dot-tree-node.model';
69+
export * from './lib/dot-vanity-url.model';
5270
export * from './lib/dot-what-changed.model';
5371
export * from './lib/dot-wizard.model';
54-
export * from './lib/dot-es-content.model';
55-
export * from './lib/dot-event';
56-
export * from './lib/dot-page-render.model';
57-
export * from './lib/dot-rendered-page-state.model';
58-
export * from './lib/dot-personalize.model';
59-
export * from './lib/dot-push-publish-data.model';
60-
export * from './lib/dot-field-variable.model';
61-
export * from './lib/dot-field-variable.model';
62-
export * from './lib/dot-experiments-constants';
63-
export * from './lib/dot-experiments.model';
64-
export * from './lib/shared-models';
65-
export * from './lib/dot-page-tool.model';
66-
export * from './lib/navigation';
67-
export * from './lib/dot-contentlets-events.model';
72+
export * from './lib/dot-workflow-action.model';
73+
export * from './lib/dot-workflow.model';
6874
export * from './lib/meta-tags-model';
75+
export * from './lib/navigation';
6976
export * from './lib/page-model-change-event';
7077
export * from './lib/page-model-change-event.type';
71-
export * from './lib/dot-page-content.model';
72-
export * from './lib/dot-message.model';
73-
export * from './lib/dot-message-severity.model';
74-
export * from './lib/dot-message-type.model';
75-
export * from './lib/dot-dialog.model';
76-
export * from './lib/dot-content-compare.model';
77-
export * from './lib/dot-action-menu-item.model';
78-
export * from './lib/dot-vanity-url.model';
79-
export * from './lib/dot-categories.model';
80-
export * from './lib/dot-ai.model';
81-
export * from './lib/dot-content-analytics.model';
78+
export * from './lib/shared-models';
79+
export * from './lib/structure-type-view.model';
80+
export * from './lib/structure-type.model';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './lib/edit-content.routes';
22
export * from './lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component';
3+
export * from './lib/utils/functions.util';

core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.spec.ts

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { MockComponent } from 'ng-mocks';
99
import { of } from 'rxjs';
1010

11+
import { fakeAsync, tick } from '@angular/core/testing';
1112
import { ActivatedRoute, Router } from '@angular/router';
1213

1314
import { MessageService } from 'primeng/api';
@@ -24,6 +25,7 @@ import {
2425
DotWorkflowsActionsService,
2526
DotWorkflowService
2627
} from '@dotcms/data-access';
28+
import { MOCK_SINGLE_WORKFLOW_ACTIONS } from '@dotcms/utils-testing';
2729

2830
import { DotEditContentSidebarInformationComponent } from './components/dot-edit-content-sidebar-information/dot-edit-content-sidebar-information.component';
2931
import { DotEditContentSidebarWorkflowComponent } from './components/dot-edit-content-sidebar-workflow/dot-edit-content-sidebar-workflow.component';
@@ -33,7 +35,7 @@ import { DotEditContentService } from '../../services/dot-edit-content.service';
3335
import { DotEditContentStore } from '../../store/edit-content.store';
3436
import { MOCK_WORKFLOW_STATUS } from '../../utils/edit-content.mock';
3537
import * as utils from '../../utils/functions.util';
36-
import { MockResizeObserver } from '../../utils/mocks';
38+
import { CONTENT_TYPE_MOCK, MockResizeObserver } from '../../utils/mocks';
3739

3840
describe('DotEditContentSidebarComponent', () => {
3941
let spectator: Spectator<DotEditContentSidebarComponent>;
@@ -134,20 +136,76 @@ describe('DotEditContentSidebarComponent', () => {
134136
});
135137

136138
describe('UI State', () => {
137-
it('should initialize with correct UI state', () => {
139+
beforeEach(fakeAsync(() => {
140+
// Mock the services needed for initializeExistingContent
141+
const dotContentTypeService = spectator.inject(DotContentTypeService);
142+
const workflowActionsService = spectator.inject(DotWorkflowsActionsService);
143+
const dotWorkflowService = spectator.inject(DotWorkflowService);
144+
const dotEditContentService = spectator.inject(DotEditContentService);
145+
146+
// Mock contentlet response with all required DotCMSContentlet properties
147+
const mockContentlet = {
148+
inode: '123',
149+
contentType: 'testContentType',
150+
archived: false,
151+
baseType: 'CONTENT',
152+
folder: 'SYSTEM_FOLDER',
153+
hasTitleImage: false,
154+
host: 'demo.dotcms.com',
155+
hostName: 'demo.dotcms.com',
156+
identifier: '123-456',
157+
languageId: 1,
158+
live: true,
159+
locked: false,
160+
modDate: new Date().toISOString(),
161+
modUser: 'admin',
162+
modUserName: 'Admin User',
163+
owner: 'admin',
164+
permissionId: '123',
165+
permissionType: 'CONTENT',
166+
title: 'Test Content',
167+
working: true,
168+
URL_MAP_FOR_CONTENT: '/test',
169+
sortOrder: 0,
170+
stInode: '123-stInode',
171+
structure: {
172+
name: 'Test Structure',
173+
inode: '456'
174+
},
175+
titleImage: '',
176+
url: '/test-content'
177+
};
178+
179+
dotEditContentService.getContentById.mockReturnValue(of(mockContentlet));
180+
dotContentTypeService.getContentType.mockReturnValue(of(CONTENT_TYPE_MOCK));
181+
workflowActionsService.getByInode.mockReturnValue(of([]));
182+
workflowActionsService.getWorkFlowActions.mockReturnValue(
183+
of(MOCK_SINGLE_WORKFLOW_ACTIONS)
184+
);
185+
dotWorkflowService.getWorkflowStatus.mockReturnValue(of(MOCK_WORKFLOW_STATUS));
186+
187+
// Initialize existing content
188+
store.initializeExistingContent('123');
189+
tick();
190+
spectator.detectChanges();
191+
}));
192+
193+
it('should initialize with correct UI state', fakeAsync(() => {
138194
expect(store.isSidebarOpen()).toBe(true);
139195
expect(store.activeSidebarTab()).toBe(0);
140-
});
196+
}));
141197

142-
it('should update active tab when changed', () => {
198+
it('should update active tab when changed', fakeAsync(() => {
143199
store.setActiveSidebarTab(1);
200+
tick();
144201
expect(store.activeSidebarTab()).toBe(1);
145-
});
202+
}));
146203

147-
it('should toggle sidebar visibility', () => {
204+
it('should toggle sidebar visibility', fakeAsync(() => {
148205
const initialState = store.isSidebarOpen();
149206
store.toggleSidebar();
207+
tick();
150208
expect(store.isSidebarOpen()).toBe(!initialState);
151-
});
209+
}));
152210
});
153211
});

core-web/libs/edit-content/src/lib/store/features/ui.feature.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface UIState {
2626

2727
export const uiInitialState: UIState = {
2828
activeTab: 0,
29-
isSidebarOpen: false,
29+
isSidebarOpen: true,
3030
activeSidebarTab: 0
3131
};
3232

@@ -56,7 +56,12 @@ export function withUI() {
5656
/**
5757
* Computed property that returns the active sidebar tab
5858
*/
59-
activeSidebarTab: computed(() => store.uiState().activeSidebarTab)
59+
activeSidebarTab: computed(() => {
60+
const initialState = store.initialContentletState();
61+
const uiState = store.uiState();
62+
63+
return initialState === 'new' ? 0 : uiState.activeSidebarTab;
64+
})
6065
})),
6166
withMethods((store) => ({
6267
/**

0 commit comments

Comments
 (0)