Skip to content

Commit 425c5e5

Browse files
committed
[UI] fixes context sidebar display
1 parent 4064d4b commit 425c5e5

12 files changed

Lines changed: 283 additions & 212 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import React, {useState} from 'react'
2+
import {useSelector} from 'react-redux'
3+
import classes from 'classnames'
4+
import get from 'lodash/get'
5+
6+
import {trans} from '#/main/app/intl'
7+
import {url} from '#/main/app/api'
8+
import {Html} from '#/main/app/components/html'
9+
import {Button, pickAction, Toolbar} from '#/main/app/action'
10+
import {CALLBACK_BUTTON, URL_BUTTON} from '#/main/app/buttons'
11+
import {route} from '#/main/app/context/routing'
12+
13+
import {selectors} from '#/main/app/context/store'
14+
15+
const Callout = ({
16+
className,
17+
title,
18+
content,
19+
type = 'primary',
20+
dismissible = true,
21+
actions = []
22+
}) => {
23+
const [dismissed, setDismissed] = useState(false)
24+
25+
if (dismissed) {
26+
return null
27+
}
28+
29+
return (
30+
<div className={classes('rounded-2 p-3', `text-${type}-emphasis bg-${type}-subtle`, className)}>
31+
<div className="d-flex flex-row flex-nowrap align-items-baseline gap-1">
32+
{title &&
33+
<h6 className="mb-3 fs-sm flex-fill">
34+
{title}
35+
</h6>
36+
}
37+
38+
{dismissible &&
39+
<Button
40+
className="btn btn-link p-1 text-reset mt-n1 me-n1"
41+
{...{
42+
name: 'close-callout',
43+
type: CALLBACK_BUTTON,
44+
icon: 'fa fa-times',
45+
label: trans('hide', {}, 'actions'),
46+
tooltip: 'bottom',
47+
callback: () => setDismissed(true),
48+
displayed: dismissible
49+
}}
50+
/>
51+
}
52+
</div>
53+
54+
<Html as="p" className="fs-sm">{content}</Html>
55+
56+
<Toolbar
57+
className="d-flex flex-column align-items-start gap-2"
58+
buttonName="fs-sm fw-bolder alert-link"
59+
actions={actions}
60+
/>
61+
</div>
62+
)
63+
}
64+
65+
const ContextCallout = ({className, actions}) => {
66+
const contextType = useSelector(selectors.type)
67+
const contextId = useSelector(selectors.id)
68+
const contextData = useSelector(selectors.data)
69+
const impersonated = useSelector(selectors.impersonated)
70+
const roles = useSelector(selectors.roles)
71+
72+
if (impersonated) {
73+
return (
74+
<Callout
75+
className={className}
76+
type="warning"
77+
content={trans('workspace_impersonation', {role: roles[0] ? trans(roles[0].translationKey) : ''}, 'workspace')}
78+
dismissible={false}
79+
actions={[
80+
/*{
81+
name: 'change-role',
82+
type: CALLBACK_BUTTON,
83+
label: trans('Changer de rôle', {}, 'actions'),
84+
callback: () => true
85+
}, */{
86+
name: 'exit',
87+
type: URL_BUTTON,
88+
label: trans('exit', {}, 'actions'),
89+
target: url(['claro_index', {}], {view_as: 'exit'}) + '#' + route(contextType, contextId)
90+
}
91+
]}
92+
/>
93+
)
94+
}
95+
96+
if (get(contextData, 'meta.archived')) {
97+
return (
98+
<Callout
99+
className={className}
100+
type="danger"
101+
title={trans('workspace_archive', {}, 'workspace')}
102+
content={trans('workspace_archive_desc', {}, 'workspace')}
103+
actions={Promise.all([
104+
pickAction('restore', actions),
105+
pickAction('delete', actions)
106+
]).then(loadedActions => loadedActions
107+
.filter(a => !!a)
108+
.map(a => ({
109+
...a,
110+
icon: undefined,
111+
label: <>{a.label} <span className="fa ms-1 fa-arrow-right" aria-hidden={true}/></>
112+
}))
113+
)}
114+
/>
115+
)
116+
}
117+
118+
if (get(contextData, 'meta.model', false)) {
119+
return (
120+
<Callout
121+
className={className}
122+
type="primary"
123+
title={trans('workspace_model', {}, 'workspace')}
124+
content={trans('workspace_model_desc', {}, 'workspace')}
125+
actions={Promise.all([
126+
pickAction('create-from-model', actions)
127+
]).then(loadedActions => loadedActions
128+
.filter(a => !!a)
129+
.map(a => ({
130+
...a,
131+
icon: undefined,
132+
label: <>{a.label} <span className="fa ms-1 fa-arrow-right" aria-hidden={true}/></>
133+
}))
134+
)}
135+
/>
136+
)
137+
}
138+
}
139+
140+
export {
141+
ContextCallout
142+
}

src/main/app/Resources/modules/context/components/favorite.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {selectors as securitySelectors} from '#/main/app/security/store'
1111
import {actions as platformActions, selectors as platformSelectors} from '#/main/app/platform/store'
1212

1313
const ContextFavourite = ({
14-
className
14+
className,
15+
tooltip
1516
}) => {
1617
const dispatch = useDispatch()
1718

@@ -21,19 +22,20 @@ const ContextFavourite = ({
2122
const favourite = useSelector((state) => platformSelectors.isContextFavorite(state, contextData))
2223

2324
if (!isAuthenticated || 'workspace' !== contextType) {
24-
return null;
25+
return null
2526
}
2627

2728
return (
2829
<Button
2930
id="toggle-favorite"
30-
className={classes('btn btn-text-body p-1 focus-ring ms-n1', className)}
31+
className={classes('btn btn-text-body focus-ring', className)}
3132
type={CALLBACK_BUTTON}
3233
label={trans(favourite ? 'remove-favourite' : 'add-favourite', {}, 'actions')}
3334
icon={classes('fa fs-base', {
3435
'fa-star text-warning': favourite,
3536
'far fa-star': !favourite
3637
})}
38+
tooltip={tooltip}
3739
callback={() => favourite ?
3840
dispatch(platformActions.deleteFavorite(contextData)) :
3941
dispatch(platformActions.addFavorite(contextData))
@@ -44,7 +46,8 @@ const ContextFavourite = ({
4446
}
4547

4648
ContextFavourite.propTypes = {
47-
className: T.string
49+
className: T.string,
50+
tooltip: T.string
4851
}
4952

5053
export {

src/main/app/Resources/modules/context/components/menu.jsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import React, {useCallback, useId} from 'react'
1+
import React, {useCallback, useEffect, useId, useState} from 'react'
22
import {PropTypes as T} from 'prop-types'
33
import {useDispatch, useSelector} from 'react-redux'
4+
import {useHistory} from 'react-router-dom'
45
import classes from 'classnames'
56
import omit from 'lodash/omit'
67

78
import {trans} from '#/main/app/intl'
89
import {useLocaleStorage} from '#/main/app/storage'
9-
import {Button} from '#/main/app/action'
10+
import {Button, constants as actionConstants, pickActionSet} from '#/main/app/action'
1011
import {CALLBACK_BUTTON, CallbackButton, MENU_BUTTON, MODAL_BUTTON} from '#/main/app/buttons'
1112
import {DataMicro} from '#/main/app/data/components/micro'
1213
import {Menu} from '#/main/app/overlays/menu'
@@ -16,10 +17,18 @@ import {MODAL_PLATFORM_ORGANIZATIONS} from '#/main/app/platform/modals/organizat
1617

1718
import {ContextFavourite} from '#/main/app/context/components/favorite'
1819
import {actions, selectors} from '#/main/app/context/store'
20+
import {ContextCallout} from '#/main/app/context/components/callout'
21+
import {selectors as securitySelectors} from '#/main/app/security'
22+
import {route} from '#/main/app/context'
23+
import {getActions} from '#/main/app/context/utils'
1924

2025
const ContextFlyout = (props) => {
2126
const dispatch = useDispatch()
27+
const history = useHistory()
2228

29+
const contextPath = useSelector(selectors.path)
30+
const contextType = useSelector(selectors.type)
31+
const contextData = useSelector(selectors.data)
2332
// get context organizations
2433
const organizations = useSelector(selectors.organizations)
2534
// get context tools
@@ -30,6 +39,22 @@ const ContextFlyout = (props) => {
3039
const organizationsTitleId = useId()
3140
const organizationsDescId = useId()
3241

42+
const [contextActions, setContextActions] = useState([])
43+
44+
const currentUser = useSelector(securitySelectors.currentUser)
45+
const refresher = {
46+
add: () => dispatch(actions.reload()),
47+
update: () => dispatch(actions.reload()),
48+
delete: () => {
49+
history.push(route('desktop', null, 'workspaces'))
50+
}
51+
}
52+
useEffect(() => {
53+
getActions(contextType, [contextData], refresher, contextPath, currentUser).then((loadedActions) => {
54+
setContextActions(pickActionSet(actionConstants.ACTION_SET_DETAILS, loadedActions))
55+
})
56+
}, [contextType, contextData ? contextData.id : null])
57+
3358
return (
3459
<Menu
3560
id={props.id}
@@ -52,9 +77,11 @@ const ContextFlyout = (props) => {
5277
<span className="fa fa-thumb-tack fs-base" aria-hidden={true} />
5378
</Button>
5479

55-
<ContextFavourite />
80+
<ContextFavourite className="p-1 ms-n1" />
5681
</div>
5782

83+
<ContextCallout className="mt-4 mx-4" actions={contextActions} />
84+
5885
{1 < toolLinks.length &&
5986
<div role="presentation">
6087
<h2 id={toolsTitleId} className="visually-hidden">{trans('tools')}</h2>

src/main/app/Resources/modules/context/components/page.jsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import React from 'react'
22
import {PropTypes as T} from 'prop-types'
33
import {useSelector} from 'react-redux'
44
import get from 'lodash/get'
5-
import merge from 'lodash/merge'
65

76
import {trans} from '#/main/app/intl'
7+
import {useLocaleStorage} from '#/main/app/storage'
88
import {Action, PromisedAction} from '#/main/app/action/prop-types'
99
import {PageBody, PageSimple} from '#/main/app/page'
1010

@@ -30,6 +30,8 @@ const ContextPage = ({
3030
const contextData = useSelector(selectors.data)
3131
const contextPath = useSelector(selectors.path)
3232

33+
const [pinedMenu] = useLocaleStorage('contextMenuPined', false)
34+
3335
return (
3436
<PageSimple
3537
className={className}
@@ -42,12 +44,12 @@ const ContextPage = ({
4244
<PageMenu
4345
embedded={embedded}
4446
{...menu}
45-
breadcrumb={[
47+
breadcrumb={(!pinedMenu ? [
4648
{
4749
label: get(contextData, 'name') || trans(contextType, {}, 'context'),
4850
target: contextPath
4951
}
50-
].concat(breadcrumb || [])}
52+
] : []).concat(breadcrumb || [])}
5153
affix={ContextMenu}
5254
/>
5355
}
@@ -73,7 +75,29 @@ const ContextPage = ({
7375
)
7476
}
7577

76-
ContextPage.propTypes = merge({}, PageSimple.propTypes, {
78+
ContextPage.propTypes = {
79+
className: T.string,
80+
81+
/**
82+
* Custom data used for document head.
83+
*/
84+
title: T.string,
85+
description: T.string,
86+
87+
/**
88+
* A list of additional styles to add to the page.
89+
*/
90+
styles: T.arrayOf(T.string),
91+
92+
/**
93+
* Is the current page embedded into another one?
94+
*
95+
* @type {bool}
96+
*/
97+
embedded: T.bool,
98+
99+
children: T.node,
100+
77101
/**
78102
* The path of the page inside the context.
79103
*/
@@ -120,7 +144,7 @@ ContextPage.propTypes = merge({}, PageSimple.propTypes, {
120144
]),
121145
children: T.node
122146
})
123-
})
147+
}
124148

125149
export {
126150
ContextPage

0 commit comments

Comments
 (0)