Skip to content

Commit d253dc9

Browse files
committed
Add recommended layout functionality to WidgetWorkspace
- Introduced a modal for saving recommended layouts as defaults or using them temporarily. - Added new messages for user prompts and labels related to recommended layouts. - Implemented methods to check if the current configuration matches the recommended layout and to find matching saved layouts. - Enhanced the WidgetWorkspace component to manage recommended layout states and interactions.
1 parent f6241e5 commit d253dc9

2 files changed

Lines changed: 166 additions & 12 deletions

File tree

src/components/WidgetWorkspace/Messages.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,20 @@ export default defineMessages({
9393
id: "WidgetWorkspace.useRecommendedLayoutLabel.label",
9494
defaultMessage: "Use Recommended Layout",
9595
},
96+
97+
saveRecommendedPrompt: {
98+
id: "WidgetWorkspace.recommendedLayout.prompt",
99+
defaultMessage:
100+
"Would you like to save this recommended layout as your default? If not, it will only be applied temporarily for this session.",
101+
},
102+
103+
saveAsDefaultLabel: {
104+
id: "WidgetWorkspace.controls.saveAsDefault.label",
105+
defaultMessage: "Save as My Default",
106+
},
107+
108+
useTemporarilyLabel: {
109+
id: "WidgetWorkspace.controls.useTemporarily.label",
110+
defaultMessage: "Use Temporarily",
111+
},
96112
});

src/components/WidgetWorkspace/WidgetWorkspace.jsx

Lines changed: 150 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import classNames from "classnames";
22
import _cloneDeep from "lodash/cloneDeep";
3+
import _find from "lodash/find";
34
import _isEmpty from "lodash/isEmpty";
5+
import _isEqual from "lodash/isEqual";
46
import _map from "lodash/map";
7+
import _omit from "lodash/omit";
58
import PropTypes from "prop-types";
69
import { Component, Fragment } from "react";
710
import { FormattedMessage, injectIntl } from "react-intl";
811
import 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";
1017
import BusySpinner from "../BusySpinner/BusySpinner";
1118
import Button from "../Button/Button";
1219
import ConfirmAction from "../ConfirmAction/ConfirmAction";
1320
import Dropdown from "../Dropdown/Dropdown";
21+
import External from "../External/External";
1422
import WithErrors from "../HOCs/WithErrors/WithErrors";
1523
import Header from "../Header/Header";
1624
import ImportFileModal from "../ImportFileModal/ImportFileModal";
25+
import Modal from "../Modal/Modal";
1726
import QuickTextBox from "../QuickTextBox/QuickTextBox";
1827
import SvgSymbol from "../SvgSymbol/SvgSymbol";
1928
import 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

Comments
 (0)