11import classNames from "classnames" ;
22import _cloneDeep from "lodash/cloneDeep" ;
3+ import _find from "lodash/find" ;
34import _isEmpty from "lodash/isEmpty" ;
5+ import _isEqual from "lodash/isEqual" ;
46import _map from "lodash/map" ;
7+ import _omit from "lodash/omit" ;
58import PropTypes from "prop-types" ;
69import { Component , Fragment } from "react" ;
710import { FormattedMessage , injectIntl } from "react-intl" ;
811import AppErrors from "../../services/Error/AppErrors" ;
9- import { importRecommendedConfiguration } from "../../services/Widget/Widget" ;
12+ import {
13+ generateWidgetId ,
14+ importRecommendedConfiguration ,
15+ nextAvailableConfigurationLabel ,
16+ } from "../../services/Widget/Widget" ;
1017import BusySpinner from "../BusySpinner/BusySpinner" ;
1118import Button from "../Button/Button" ;
1219import ConfirmAction from "../ConfirmAction/ConfirmAction" ;
1320import Dropdown from "../Dropdown/Dropdown" ;
21+ import External from "../External/External" ;
1422import WithErrors from "../HOCs/WithErrors/WithErrors" ;
1523import Header from "../Header/Header" ;
1624import ImportFileModal from "../ImportFileModal/ImportFileModal" ;
25+ import Modal from "../Modal/Modal" ;
1726import QuickTextBox from "../QuickTextBox/QuickTextBox" ;
1827import SvgSymbol from "../SvgSymbol/SvgSymbol" ;
1928import WidgetGrid from "../WidgetGrid/WidgetGrid" ;
@@ -33,6 +42,7 @@ export class WidgetWorkspace extends Component {
3342 isEditingId : null ,
3443 isExportingLayout : false ,
3544 isImportingLayout : false ,
45+ showRecommendedModal : false ,
3646 workspaceContext : { } ,
3747 activeRecommendedLayout : false ,
3848 } ;
@@ -150,6 +160,100 @@ export class WidgetWorkspace extends Component {
150160 closeDropdown ( ) ;
151161 } ;
152162
163+ /**
164+ * Returns the widget keys for a given configuration, used for comparing layouts.
165+ */
166+ configWidgetKeys = ( conf ) => {
167+ if ( ! conf ?. widgets ) return [ ] ;
168+ return conf . widgets . map ( ( w ) => w ?. widgetKey ) . filter ( Boolean ) ;
169+ } ;
170+
171+ /**
172+ * Checks if a saved (non-recommended) configuration matches the recommended
173+ * layout by comparing widget keys.
174+ */
175+ findMatchingSavedLayout = ( ) => {
176+ const recommended = this . props . workspaceConfigurations ?. recommendedLayout ;
177+ if ( ! recommended ) return null ;
178+
179+ const recommendedKeys = this . configWidgetKeys ( recommended ) ;
180+ const savedConfigs = _omit ( this . props . workspaceConfigurations , [ "recommendedLayout" ] ) ;
181+
182+ return _find ( savedConfigs , ( conf ) =>
183+ _isEqual ( this . configWidgetKeys ( conf ) , recommendedKeys ) ,
184+ ) ;
185+ } ;
186+
187+ /**
188+ * Returns true if the current configuration already matches the recommended
189+ * layout (either it IS the transient recommended layout, or it's a saved
190+ * layout with the same widget keys).
191+ */
192+ isUsingRecommended = ( ) => {
193+ const { currentConfiguration, workspaceConfigurations } = this . props ;
194+ if ( ! workspaceConfigurations ?. recommendedLayout || ! currentConfiguration ) return false ;
195+
196+ if ( currentConfiguration . id === "recommendedLayout" ) return true ;
197+
198+ const recommendedKeys = this . configWidgetKeys ( workspaceConfigurations . recommendedLayout ) ;
199+ const currentKeys = this . configWidgetKeys ( currentConfiguration ) ;
200+ return _isEqual ( currentKeys , recommendedKeys ) ;
201+ } ;
202+
203+ showRecommendedLayoutModal = ( closeDropdown ) => {
204+ if ( this . isUsingRecommended ( ) ) {
205+ closeDropdown ( ) ;
206+ return ;
207+ }
208+
209+ // If there's already a saved layout matching the recommended one, just switch to it
210+ const matchingSaved = this . findMatchingSavedLayout ( ) ;
211+ if ( matchingSaved ) {
212+ this . props . switchWorkspaceConfiguration ( matchingSaved . id , this . props . currentConfiguration ) ;
213+ closeDropdown ( ) ;
214+ return ;
215+ }
216+
217+ this . setState ( { showRecommendedModal : true } ) ;
218+ closeDropdown ( ) ;
219+ } ;
220+
221+ useRecommendedTemporarily = ( ) => {
222+ this . setState ( { showRecommendedModal : false } ) ;
223+ const recommended = this . props . workspaceConfigurations ?. recommendedLayout ;
224+ if ( recommended ) {
225+ this . props . switchWorkspaceConfiguration ( recommended . id , this . props . currentConfiguration ) ;
226+ }
227+ } ;
228+
229+ saveRecommendedAsMyLayout = ( ) => {
230+ this . setState ( { showRecommendedModal : false } ) ;
231+ const recommended = this . props . workspaceConfigurations ?. recommendedLayout ;
232+ if ( ! recommended ) return ;
233+
234+ // Check if there's already a saved layout matching the recommended one
235+ const matchingSaved = this . findMatchingSavedLayout ( ) ;
236+ if ( matchingSaved ) {
237+ this . props . switchWorkspaceConfiguration ( matchingSaved . id , this . props . currentConfiguration ) ;
238+ return ;
239+ }
240+
241+ const existingLabels = Object . values (
242+ _omit ( this . props . workspaceConfigurations , [ "recommendedLayout" ] ) ,
243+ ) . map ( ( conf ) => conf . label ) ;
244+ const newLayout = _cloneDeep ( recommended ) ;
245+ newLayout . id = generateWidgetId ( ) ;
246+ newLayout . label = nextAvailableConfigurationLabel (
247+ "Recommended Layout" ,
248+ existingLabels ,
249+ ) ;
250+ newLayout . active = true ;
251+ this . props . saveWorkspaceConfiguration ( newLayout ) ;
252+ setTimeout ( ( ) => {
253+ this . props . switchWorkspaceConfiguration ( newLayout . id , this . props . currentConfiguration ) ;
254+ } , 500 ) ;
255+ } ;
256+
153257 setupWorkspaceAlt = ( closeDropdown ) => {
154258 this . props . setupWorkspaceAlt ( this . props . currentConfiguration ) ;
155259 closeDropdown ( ) ;
@@ -192,8 +296,10 @@ export class WidgetWorkspace extends Component {
192296 dropdownButton = { ( dropdown ) => (
193297 < LayoutButton
194298 { ...this . props }
299+ isUsingRecommended = { this . isUsingRecommended ( ) }
195300 switchConfiguration = { this . switchConfiguration }
196301 switchAltConfiguration = { this . switchAltConfiguration }
302+ showRecommendedLayoutModal = { this . showRecommendedLayoutModal }
197303 closeDropdown = { dropdown . closeDropdown }
198304 toggleDropdownVisible = { dropdown . toggleDropdownVisible }
199305 />
@@ -202,8 +308,10 @@ export class WidgetWorkspace extends Component {
202308 < ListLayoutItems
203309 workspaceConfigurations = { this . props . workspaceConfigurations }
204310 currentConfiguration = { this . props . currentConfiguration }
311+ isUsingRecommended = { this . isUsingRecommended ( ) }
205312 switchConfiguration = { this . switchConfiguration }
206313 switchAltConfiguration = { this . switchAltConfiguration }
314+ showRecommendedLayoutModal = { this . showRecommendedLayoutModal }
207315 startEditingLayout = { this . startEditingLayout }
208316 addConfiguration = { this . addConfiguration }
209317 resetConfiguration = { this . resetConfiguration }
@@ -291,6 +399,43 @@ export class WidgetWorkspace extends Component {
291399 }
292400 />
293401 ) }
402+ { this . state . showRecommendedModal && (
403+ < External >
404+ < Modal
405+ narrow
406+ isActive
407+ onClose = { ( ) => this . setState ( { showRecommendedModal : false } ) }
408+ >
409+ < div className = "mr-flex mr-flex-col mr-items-center mr-px-8 mr-pt-12" >
410+ < SvgSymbol
411+ className = "mr-fill-green-lighter mr-h-10 mr-mb-4"
412+ viewBox = "0 0 20 20"
413+ sym = "cog-icon"
414+ />
415+ < h2 className = "mr-text-white mr-text-3xl mr-mb-4" >
416+ < FormattedMessage { ...messages . recommendedLayoutLabel } />
417+ </ h2 >
418+ < p className = "mr-text-white mr-font-medium mr-text-center" >
419+ < FormattedMessage { ...messages . saveRecommendedPrompt } />
420+ </ p >
421+ </ div >
422+ < div className = "mr-mt-8 mr-bg-blue-cloudburst mr-p-8 mr-flex mr-justify-center mr-items-center" >
423+ < button
424+ className = "mr-button mr-button--white mr-mr-4"
425+ onClick = { this . useRecommendedTemporarily }
426+ >
427+ < FormattedMessage { ...messages . useTemporarilyLabel } />
428+ </ button >
429+ < button
430+ className = "mr-button mr-button--green-lighter"
431+ onClick = { this . saveRecommendedAsMyLayout }
432+ >
433+ < FormattedMessage { ...messages . saveAsDefaultLabel } />
434+ </ button >
435+ </ div >
436+ </ Modal >
437+ </ External >
438+ ) }
294439 </ div >
295440 ) ;
296441 }
@@ -301,15 +446,11 @@ const LayoutButton = function (props) {
301446 < div className = "mr-normal-case mr-flex" >
302447 { props . workspaceConfigurations . recommendedLayout ? (
303448 < h3 className = "mr-text-base mr-font-bold mr-mr-2" >
304- { props . workspaceConfigurations . recommendedLayout . id === props . currentConfiguration . id &&
305- "✓" }
449+ { props . isUsingRecommended && "✓" }
306450 < a
307451 className = "mr-ml-2"
308452 onClick = { ( ) =>
309- props . switchConfiguration (
310- props . workspaceConfigurations . recommendedLayout . id ,
311- props . closeDropdown ,
312- )
453+ props . showRecommendedLayoutModal ( props . closeDropdown )
313454 }
314455 >
315456 < FormattedMessage { ...messages . useRecommendedLayoutLabel } />
@@ -383,7 +524,7 @@ const ListLayoutItems = function (props) {
383524 < ol className = "mr-list-dropdown" > { configurationItems } </ ol >
384525 < hr className = "mr-rule-dropdown" />
385526 < ol className = "mr-list-dropdown" >
386- { props . currentConfiguration . id === " recommendedLayout" ? (
527+ { props . isUsingRecommended && props . workspaceConfigurations . recommendedLayout ? (
387528 < li className = "mr-normal-case mr-flex" >
388529 < div className = "mr-text-white mr-w-4" > { "✓" } </ div >
389530 < FormattedMessage { ...messages . recommendedLayoutLabel } />
@@ -393,10 +534,7 @@ const ListLayoutItems = function (props) {
393534 < li className = "mr-normal-case mr-flex" >
394535 < a
395536 onClick = { ( ) =>
396- props . switchConfiguration (
397- props . workspaceConfigurations . recommendedLayout . id ,
398- props . closeDropdown ,
399- )
537+ props . showRecommendedLayoutModal ( props . closeDropdown )
400538 }
401539 >
402540 < FormattedMessage { ...messages . recommendedLayoutLabel } />
0 commit comments