Skip to content

Commit 48f1610

Browse files
authored
[Subitems] Add extensibility point to add new views (#2735)
* Before prefix change * Prefixes * Screenshot * Adjusted example to v5 * Vale suggestion * Added mention about replacing a view * Improvements * Added mention of composer run post-install-cmd
1 parent 54d6e0b commit 48f1610

File tree

9 files changed

+275
-25
lines changed

9 files changed

+275
-25
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const containerNode = document.querySelector('#sub-items-container');
2+
3+
ReactDOM.render(
4+
React.createElement(ibexa.modules.SubItems, {
5+
parentLocationId: { Number },
6+
restInfo: {
7+
token: { String },
8+
siteaccess: { String },
9+
},
10+
}),
11+
containerNode,
12+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const attrs = {
2+
parentLocationId: {Number},
3+
restInfo: {
4+
token: {String},
5+
siteaccess: {String}
6+
}
7+
};
8+
9+
<SubItemsModule {...attrs}/>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import TimelineViewComponent from './timeline.view.component.js';
2+
import { registerView } from '@ibexa-admin-ui-modules/sub-items/services/view.registry';
3+
4+
// Use the existing constants to replace a view
5+
import { VIEW_MODE_GRID, VIEW_MODE_TABLE } from '@ibexa-admin-ui-modules/sub-items/constants';
6+
7+
registerView('timeline', {
8+
component: TimelineViewComponent,
9+
iconName: 'timeline',
10+
label: 'Timeline view',
11+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import TimelineViewItemComponent from './timeline.view.item.component';
4+
5+
const TimelineViewComponent = ({ items, generateLink }) => {
6+
const groupByDate = (items) => {
7+
return items.reduce((groups, item) => {
8+
const date = new Date(item.content._info.modificationDate.timestamp * 1000);
9+
const dateKey = date.toISOString().split('T')[0];
10+
11+
if (!groups[dateKey]) {
12+
groups[dateKey] = [];
13+
}
14+
15+
groups[dateKey].push(item);
16+
return groups;
17+
}, {});
18+
};
19+
20+
const groupedItems = groupByDate(items);
21+
22+
return (
23+
<div className="app-timeline-view">
24+
{Object.entries(groupedItems).map(([date, dateItems]) => (
25+
<div key={date} className="app-timeline-view__group">
26+
<div className="app-timeline-view__date">
27+
<div className="app-timeline-view__date-marker" />
28+
<h3>{new Date(date).toLocaleDateString()}</h3>
29+
</div>
30+
<div className="app-timeline-view__items">
31+
{dateItems.map((item) => (
32+
<TimelineViewItemComponent key={item.id} item={item} generateLink={generateLink} />
33+
))}
34+
</div>
35+
</div>
36+
))}
37+
</div>
38+
);
39+
};
40+
41+
TimelineViewComponent.propTypes = {
42+
items: PropTypes.array.isRequired,
43+
generateLink: PropTypes.func.isRequired,
44+
};
45+
46+
export default TimelineViewComponent;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import Icon from '@ibexa-admin-ui-modules/common/icon/icon';
4+
5+
const { ibexa } = window;
6+
7+
const TimelineViewItemComponent = ({ item, generateLink }) => {
8+
const { content } = item;
9+
const contentTypeIdentifier = content._info.contentType.identifier;
10+
const contentTypeIconUrl = ibexa.helpers.contentType.getContentTypeIconUrl(contentTypeIdentifier);
11+
const time = new Date(content._info.modificationDate.timestamp * 1000).toLocaleTimeString();
12+
13+
return (
14+
<a className="app-timeline-view-item" href={generateLink(item.id, content._info.id)}>
15+
<div className="app-timeline-view-item__time">{time}</div>
16+
<div className="app-timeline-view-item__content">
17+
<div className="app-timeline-view-item__info">
18+
<div className="app-timeline-view-item__name">{content._name}</div>
19+
<div className="app-timeline-view-item__type">
20+
<Icon customPath={contentTypeIconUrl} extraClasses="ibexa-icon--small" />
21+
<span className="app-timeline-view-item__type-name">{content._info.contentType.name}</span>
22+
</div>
23+
</div>
24+
</div>
25+
</a>
26+
);
27+
};
28+
29+
TimelineViewItemComponent.propTypes = {
30+
item: PropTypes.object.isRequired,
31+
generateLink: PropTypes.func.isRequired,
32+
};
33+
34+
export default TimelineViewItemComponent;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *;
2+
3+
.app-timeline-view {
4+
padding: calculateRem(16px);
5+
6+
&__group {
7+
position: relative;
8+
margin-bottom: calculateRem(32px);
9+
}
10+
11+
&__date {
12+
display: flex;
13+
align-items: center;
14+
margin-bottom: calculateRem(16px);
15+
16+
h3 {
17+
margin: 0;
18+
font-size: $ibexa-text-font-size-large;
19+
color: $ibexa-color-dark;
20+
}
21+
}
22+
23+
&__date-marker {
24+
width: calculateRem(12px);
25+
height: calculateRem(12px);
26+
border-radius: 50%;
27+
background: $ibexa-color-primary;
28+
margin-right: calculateRem(16px);
29+
}
30+
31+
&__items {
32+
margin-left: calculateRem(6px);
33+
padding-left: calculateRem(32px);
34+
border-left: calculateRem(2px) solid $ibexa-color-light;
35+
}
36+
}
37+
38+
.app-timeline-view-item {
39+
display: flex;
40+
align-items: flex-start;
41+
padding: calculateRem(16px);
42+
margin-bottom: calculateRem(8px);
43+
text-decoration: none;
44+
color: inherit;
45+
background: $ibexa-color-light-300;
46+
border-radius: $ibexa-border-radius;
47+
transition: background-color 0.2s $ibexa-admin-transition;
48+
49+
&:hover {
50+
background: $ibexa-color-light-400;
51+
}
52+
53+
&__time {
54+
color: $ibexa-color-dark-400;
55+
margin-right: calculateRem(16px);
56+
min-width: calculateRem(80px);
57+
}
58+
59+
&__content {
60+
display: flex;
61+
align-items: center;
62+
}
63+
64+
&__icon {
65+
margin-right: calculateRem(16px);
66+
}
67+
68+
&__name {
69+
font-weight: $ibexa-font-weight-bold;
70+
margin-bottom: calculateRem(4px);
71+
}
72+
73+
&__type {
74+
font-size: $ibexa-text-font-size-small;
75+
color: $ibexa-color-dark-400;
76+
display: flex;
77+
align-items: center;
78+
gap: calculateRem(8px);
79+
}
80+
81+
&__type-name {
82+
line-height: calculateRem(16px);
83+
}
84+
}

docs/administration/back_office/back_office_elements/importing_assets_from_bundle.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,15 @@ To add a new configuration under your own namespace and with its own dependencie
103103

104104
## Configuration from main project files
105105

106-
If you prefer to include the asset configuration in the main project files, add it in [`webpack.config.js`](https://github.com/ibexa/recipes/blob/master/ibexa/oss/4.0/encore/webpack.config.js#L31).
106+
If you prefer to include the asset configuration in the main project files, add it in [`webpack.config.js`](https://github.com/ibexa/recipes/blob/master/ibexa/oss/4.6/encore/webpack.config.js#L26).
107107

108108
To overwrite the built-in assets, use the following configuration to replace, remove, or add asset files in `webpack.config.js`:
109109

110110
``` js
111+
const ibexaConfigManager = require('./ibexa.webpack.config.manager.js');
112+
113+
//...
114+
111115
ibexaConfigManager.replace({
112116
ibexaConfig,
113117
entryName: '<entry-name>',
92.5 KB
Loading

docs/administration/back_office/subitems_list.md

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,97 @@
11
---
2-
description: Inject a sub-items list into your back office customizations.
2+
description: Inject a sub-items list into your back office customizations or customize the view.
33
---
44

55
# Sub-items list
66

77
The Sub-items List module is meant to be used as a part of the editorial interface of [[= product_name =]].
88
It provides an interface for listing the sub-items of any location.
99

10-
!!! caution
10+
## Create custom sub-items list view
11+
12+
You can extend the Sub-items List module to replace an existing view or add your own.
13+
The example below adds a new timeline view to highlight the modification date.
14+
15+
![Sub-items List module using the new Timeline view](img/subitems/timeline_view.png "Sub-items List module using the new Timeline view")
16+
17+
To recreate it, start by creating the components responsible for rendering the new view.
18+
You can create two files:
19+
20+
- `assets/js/timeline.view.component.js` responsible for rendering the whole view
21+
22+
``` js
23+
[[= include_file('code_samples/back_office/subitems/timeline_view/timeline.view.component.js') =]]
24+
```
25+
26+
- `assets/js/timeline.view.item.component.js` responsible for rendering a single item
27+
28+
``` js
29+
[[= include_file('code_samples/back_office/subitems/timeline_view/timeline.view.item.component.js') =]]
30+
```
31+
32+
Provide the necessary styling in `assets/scss/timeline.view.scss`. The example below uses [[= product_name =]]'s SCSS variables for consistency with the rest of the back office interface.
33+
34+
``` scss
35+
[[= include_file('code_samples/back_office/subitems/timeline_view/timeline.view.scss') =]]
36+
```
1137

12-
If you want to load the Sub-items module, you need to load the JS code for it in your view, as it's not available by default.
38+
The last step is adding the view module to the list of available views in the system, by using the provided `registerView` function.
39+
40+
You can create a new view by providing an unique identifier, or replace an existing one by reusing its identifier.
41+
The existing view identifiers are defined as JavaScript constants in the `@ibexa-admin-ui-modules/sub-items/constants` module:
42+
43+
- Grid view: `VIEW_MODE_GRID` constant
44+
- Table view: `VIEW_MODE_TABLE` constant
45+
46+
Create a file called `assets/js/registerTimelineView.js`:
47+
48+
``` js
49+
[[= include_file('code_samples/back_office/subitems/timeline_view/registerTimelineView.js') =]]
50+
```
51+
52+
And include it into the back office using Webpack Encore, together with your custom styles.
53+
See [configuring assets from main project files](importing_assets_from_bundle.md#configuration-from-main-project-files) to learn more about this mechanism.
54+
55+
``` js
56+
const ibexaConfigManager = require('./ibexa.webpack.config.manager.js');
57+
58+
//...
59+
60+
ibexaConfigManager.add({
61+
ibexaConfig,
62+
entryName: 'ibexa-admin-ui-layout-js',
63+
newItems: [
64+
path.resolve(__dirname, './assets/js/registerTimelineView.js')
65+
],
66+
});
67+
68+
ibexaConfigManager.add({
69+
ibexaConfig,
70+
entryName: 'ibexa-admin-ui-layout-css',
71+
newItems: [
72+
path.resolve(__dirname, './assets/scss/timeline.view.scss'),
73+
],
74+
});
75+
```
76+
77+
Complete the task by running `composer run post-install-cmd`.
1378

1479
## Use sub-items list
1580

81+
!!! caution
82+
83+
If you want to load the Sub-items module from your custom code, you need to load the JS code for it in your view, as it's not available by default.
84+
1685
With plain JS:
1786

1887
``` js
19-
const containerNode = document.querySelector('#sub-items-container');
20-
21-
ReactDOM.render(
22-
React.createElement(ibexa.modules.SubItems, {
23-
parentLocationId: { Number },
24-
restInfo: {
25-
token: { String },
26-
siteaccess: { String }
27-
}
28-
}),
29-
containerNode
30-
);
88+
[[= include_file('code_samples/back_office/subitems/render_subitems.js') =]]
3189
```
3290

3391
With JSX:
3492

3593
``` jsx
36-
const attrs = {
37-
parentLocationId: {Number},
38-
restInfo: {
39-
token: {String},
40-
siteaccess: {String}
41-
}
42-
};
43-
44-
<SubItemsModule {...attrs}/>
94+
[[= include_file('code_samples/back_office/subitems/render_subitems.jsx') =]]
4595
```
4696

4797
## Properties list

0 commit comments

Comments
 (0)