@eeacms/volto-clms-theme 1.1.130 → 1.1.132

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,11 +4,11 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
- ### [1.1.130](https://github.com/eea/volto-clms-theme/compare/1.1.129...1.1.130) - 26 March 2024
7
+ ### [1.1.132](https://github.com/eea/volto-clms-theme/compare/1.1.131...1.1.132) - 4 April 2024
8
8
 
9
- #### :bug: Bug Fixes
9
+ ### [1.1.131](https://github.com/eea/volto-clms-theme/compare/1.1.130...1.1.131) - 27 March 2024
10
10
 
11
- - fix: CLMS-3102 [Unai Etxaburu - [`c939a1f`](https://github.com/eea/volto-clms-theme/commit/c939a1fca1340b8d9fe5513d0ece8a7f31022d56)]
11
+ ### [1.1.130](https://github.com/eea/volto-clms-theme/compare/1.1.129...1.1.130) - 27 March 2024
12
12
 
13
13
  ### [1.1.129](https://github.com/eea/volto-clms-theme/compare/1.1.128...1.1.129) - 25 March 2024
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.1.130",
3
+ "version": "1.1.132",
4
4
  "description": "volto-clms-theme: Volto theme for CLMS site",
5
5
  "main": "src/index.js",
6
6
  "author": "CodeSyntax for the European Environment Agency",
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import SidebarPortal from '@plone/volto/components/manage/Sidebar/SidebarPortal';
3
+ import { InlineForm } from '@plone/volto/components';
4
+ import schema from './schema';
5
+ import CclHelpdeskDocView from './CclHelpdeskDocBlockView';
6
+
7
+ const CclHelpdeskDocEdit = (props) => {
8
+ const { block, data, onChangeBlock, selected } = props;
9
+ return (
10
+ <>
11
+ <CclHelpdeskDocView data={data} />
12
+ <SidebarPortal selected={selected}>
13
+ <InlineForm
14
+ schema={schema}
15
+ title={schema.title}
16
+ onChangeField={(id, value) => {
17
+ onChangeBlock(block, {
18
+ ...data,
19
+ [id]: value,
20
+ });
21
+ }}
22
+ formData={data}
23
+ />
24
+ </SidebarPortal>
25
+ </>
26
+ );
27
+ };
28
+
29
+ export default CclHelpdeskDocEdit;
@@ -0,0 +1,97 @@
1
+ import React from 'react';
2
+ import { Segment, Icon } from 'semantic-ui-react';
3
+ import { HelpdeskForm } from './HelpdeskForm';
4
+ import { toast } from 'react-toastify';
5
+ import { Toast } from '@plone/volto/components';
6
+ const CclHelpdeskDocView = (props) => {
7
+ const { data } = props;
8
+ const [file, setFile] = React.useState('');
9
+ const [endpoint, setEndpoint] = React.useState('');
10
+ const [result, setResult] = React.useState('');
11
+ const [fields, setFields] = React.useState({});
12
+
13
+ const reEndpoint = /\/api\/(@.*)\?/;
14
+ const reParams = /(?=&?)(\w*=[a-zA-Z0-9-]*)/gm;
15
+ React.useEffect(() => {
16
+ data.url &&
17
+ fetch(data.url)
18
+ .then((response) => response.text())
19
+ .then((data) => setFile(data));
20
+ return () => {};
21
+ }, [data.url]);
22
+
23
+ React.useEffect(() => {
24
+ if (file) {
25
+ const endpoint = file.match(reEndpoint);
26
+ if (endpoint[1]) {
27
+ setEndpoint(endpoint[1]);
28
+ }
29
+
30
+ const params = file.match(reParams);
31
+ if (params) {
32
+ const fields = params.map((p) => {
33
+ const s = p.split('=');
34
+ return {
35
+ key: s[0],
36
+ value: s[1],
37
+ };
38
+ });
39
+ setFields(fields);
40
+ }
41
+ }
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
43
+ }, [file]);
44
+
45
+ return (
46
+ <div>
47
+ {data.title && <h2>{data.title}</h2>}
48
+ <h2>Endpoint:</h2>
49
+ <h3>{endpoint}</h3>
50
+ <Segment color="teal" padded>
51
+ {file.split('\n').map((line, key) => (
52
+ <React.Fragment key={key}>
53
+ {line}
54
+ <br />
55
+ </React.Fragment>
56
+ ))}
57
+ </Segment>
58
+
59
+ <Segment color="teal" padded>
60
+ <HelpdeskForm
61
+ fields={fields}
62
+ endpoint={endpoint}
63
+ setResult={setResult}
64
+ ></HelpdeskForm>
65
+ </Segment>
66
+ {result && (
67
+ <>
68
+ <Segment color="teal" padded>
69
+ <h2>Endpoint result:</h2>
70
+ <Icon
71
+ color={'olive'}
72
+ name="copy"
73
+ size="large"
74
+ style={{ cursor: 'pointer' }}
75
+ onClick={() => {
76
+ navigator.clipboard.writeText(result);
77
+ toast.success(
78
+ <Toast
79
+ success
80
+ autoClose={5000}
81
+ title={'URL copied to clipboard'}
82
+ content={`The result URL has been successfully copied to clipboard`}
83
+ />,
84
+ );
85
+ }}
86
+ ></Icon>
87
+ <br />
88
+ <br />
89
+ {JSON.stringify(result)}
90
+ </Segment>
91
+ </>
92
+ )}
93
+ </div>
94
+ );
95
+ };
96
+
97
+ export default CclHelpdeskDocView;
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { Form } from 'semantic-ui-react';
3
+
4
+ import CclButton from '@eeacms/volto-clms-theme/components/CclButton/CclButton';
5
+
6
+ import { getWidget } from './utils';
7
+
8
+ export const HelpdeskForm = ({ fields, endpoint, setResult }) => {
9
+ const [datasetInfo, setDatasetInfo] = React.useState(null);
10
+ const [formData, setFormData] = React.useState({});
11
+ return fields.length > 0 ? (
12
+ <>
13
+ <h2>Form to test the endpoint:</h2>
14
+ <Form>
15
+ {fields.map((f, key) => {
16
+ const Field = getWidget(f.key);
17
+ return (
18
+ <Field
19
+ key={key}
20
+ label={f.key}
21
+ placeholder={f.value}
22
+ dependant={datasetInfo}
23
+ setDependant={setDatasetInfo}
24
+ formData={formData}
25
+ setFormData={setFormData}
26
+ />
27
+ );
28
+ })}
29
+ <CclButton
30
+ onClick={() => {
31
+ fetch(
32
+ `/++api++/${endpoint}?${Object.keys(formData)
33
+ .map((k) => `${k}=${formData[k]}`)
34
+ .join('&')}`,
35
+ )
36
+ .then((response) => response.json())
37
+ .then((data) => setResult(data));
38
+ }}
39
+ >
40
+ Send request
41
+ </CclButton>
42
+ </Form>
43
+ </>
44
+ ) : (
45
+ <></>
46
+ );
47
+ };
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import Select from 'react-select';
4
+ import { Segment } from 'semantic-ui-react';
5
+
6
+ import { searchContent } from '@plone/volto/actions';
7
+ import { FormFieldWrapper } from '@plone/volto/components';
8
+
9
+ export const DatasetField = ({
10
+ label,
11
+ setDependant,
12
+ formData,
13
+ setFormData,
14
+ }) => {
15
+ const id = 'helpdesk-block';
16
+ const separator = '===';
17
+ const search = useSelector((state) => state.search.subrequests[id]);
18
+ const dispatch = useDispatch();
19
+ React.useEffect(() => {
20
+ dispatch(
21
+ searchContent(
22
+ '/',
23
+ {
24
+ portal_type: 'DataSet',
25
+ metadata_fields: ['dataset_download_information', 'UID'],
26
+ b_size: 9999,
27
+ },
28
+ id,
29
+ ),
30
+ );
31
+ return () => {};
32
+ // eslint-disable-next-line react-hooks/exhaustive-deps
33
+ }, []);
34
+ return (
35
+ <FormFieldWrapper title={`${label}:`} id={label} className="text">
36
+ <Segment basic loading={search?.loading}>
37
+ <Select
38
+ type="text"
39
+ onChange={(e) => {
40
+ setDependant(e.value);
41
+ setFormData({ ...formData, [label]: e.value.split(separator)[0] });
42
+ }}
43
+ options={
44
+ search?.items
45
+ ? search?.items.map((i) => {
46
+ return {
47
+ value: `${i.UID}${separator}${JSON.stringify(
48
+ i.dataset_download_information?.items,
49
+ )}`,
50
+ label: i.title,
51
+ };
52
+ })
53
+ : []
54
+ }
55
+ />
56
+ </Segment>
57
+ </FormFieldWrapper>
58
+ );
59
+ };
@@ -0,0 +1,39 @@
1
+ import { useSelector } from 'react-redux';
2
+ import Select from 'react-select';
3
+ import { Segment } from 'semantic-ui-react';
4
+
5
+ import { FormFieldWrapper } from '@plone/volto/components';
6
+
7
+ export const DatasetInfoField = ({
8
+ label,
9
+ formData,
10
+ dependant,
11
+ setFormData,
12
+ }) => {
13
+ const id = 'helpdesk-block';
14
+ const search = useSelector((state) => state.search.subrequests[id]);
15
+ const valueItems = dependant ? JSON.parse(dependant.split('===')[1]) : [];
16
+ const options = valueItems.map((v) => {
17
+ return {
18
+ value: v['@id'],
19
+ label: `${v.name ?? ''} ${v.collection ?? ''} ${v.full_source ?? ''} ${
20
+ typeof v.full_format === 'object'
21
+ ? v.full_format.title ?? ''
22
+ : v.full_format ?? ''
23
+ }`,
24
+ };
25
+ });
26
+ return (
27
+ <FormFieldWrapper title={`${label}:`} id={label} className="text">
28
+ <Segment basic loading={search?.loading}>
29
+ <Select
30
+ type="text"
31
+ options={options}
32
+ onChange={(e) => {
33
+ setFormData({ ...formData, [label]: e.value });
34
+ }}
35
+ />
36
+ </Segment>
37
+ </FormFieldWrapper>
38
+ );
39
+ };
@@ -0,0 +1,17 @@
1
+ import { Input } from 'semantic-ui-react';
2
+
3
+ import { FormFieldWrapper } from '@plone/volto/components';
4
+
5
+ export const StringField = ({ label, placeholder, formData, setFormData }) => {
6
+ return (
7
+ <FormFieldWrapper title={`${label}:`} id={label} className="text">
8
+ <Input
9
+ type="text"
10
+ placeholder={placeholder}
11
+ onChange={(e) => {
12
+ setFormData({ ...formData, [label]: e.target.value });
13
+ }}
14
+ />
15
+ </FormFieldWrapper>
16
+ );
17
+ };
@@ -0,0 +1,26 @@
1
+ const schema = {
2
+ title: 'Documentation block',
3
+ fieldsets: [
4
+ {
5
+ id: 'default',
6
+ title: 'Default',
7
+ fields: ['title', 'url'],
8
+ },
9
+ ],
10
+ properties: {
11
+ title: {
12
+ title: 'Title',
13
+ description: 'Documentation block title',
14
+ type: 'string',
15
+ },
16
+ url: {
17
+ title: 'URL',
18
+ description: 'GitHub documentation .req file raw url',
19
+ mode: 'link',
20
+ allowExternals: true,
21
+ },
22
+ },
23
+ required: ['title', 'url'],
24
+ };
25
+
26
+ export default schema;
@@ -0,0 +1,17 @@
1
+ import { StringField } from './fields/StringField';
2
+ import { DatasetField } from './fields/DatasetField';
3
+ import { DatasetInfoField } from './fields/DatasetInfoField';
4
+ const widgets = {
5
+ default: StringField,
6
+ dataset_uid: DatasetField,
7
+ uid: DatasetField,
8
+ download_information_id: DatasetInfoField,
9
+ };
10
+
11
+ export const getWidget = (id) => {
12
+ if (widgets[id]) {
13
+ return widgets[id];
14
+ } else {
15
+ return widgets['default'];
16
+ }
17
+ };
@@ -18,6 +18,14 @@ const handleClick = (e, tab, activeTab, setActiveTab, location, tabHash) => {
18
18
  location.hash = `#${tabHash}`;
19
19
  setActiveTab(tab);
20
20
  }
21
+ if (tab === 'd7706c16-7c4a-4c0e-9471-90765a302c1c') {
22
+ document.querySelector('#loader').style.display = 'block';
23
+ } else {
24
+ closeSpinner();
25
+ }
26
+ };
27
+ const closeSpinner = () => {
28
+ document.querySelector('#loader').style.display = 'none';
21
29
  };
22
30
 
23
31
  function isSpan(subTab, nextSubTab) {
@@ -34,6 +42,9 @@ const TabsComponent = (props) => {
34
42
  tabs = {},
35
43
  setActiveTab,
36
44
  } = props;
45
+ if (activeTab === 'd7706c16-7c4a-4c0e-9471-90765a302c1c') {
46
+ document.querySelector('#loader').style.display = 'block';
47
+ }
37
48
  const location = useLocation();
38
49
  return (
39
50
  <div className="left-content cont-w-25">
@@ -105,10 +116,50 @@ const PanelsComponent = (props) => {
105
116
  const tabHash = `tab=${slugify(title)}`;
106
117
  return (
107
118
  <Route key={index} to={'#' + tabHash}>
119
+ <div
120
+ id="loader"
121
+ className="loading"
122
+ role="alert"
123
+ aria-busy="true"
124
+ aria-live="polite"
125
+ >
126
+ <div>
127
+ <svg
128
+ width="80"
129
+ height="80"
130
+ viewBox="0 0 24 24"
131
+ xmlns="http://www.w3.org/2000/svg"
132
+ >
133
+ <style>
134
+ {`.spinner_ajPY {
135
+ transform-origin: center;
136
+ animation: spinner_AtaB .75s infinite linear;
137
+ fill: #a0b128; stroke-width: 3px; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
138
+ }
139
+ @keyframes spinner_AtaB {
140
+ 100% {
141
+ transform: rotate(360deg);
142
+ }
143
+ }`}
144
+ </style>
145
+ <path
146
+ d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
147
+ opacity=".25"
148
+ />
149
+ <path
150
+ d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
151
+ className="spinner_ajPY"
152
+ />
153
+ </svg>
154
+ </div>
155
+ </div>
108
156
  <div
109
157
  className={cx('panel', tab === activeTab && 'panel-selected')}
110
- role="tabpanel"
158
+ role="button"
111
159
  aria-hidden="false"
160
+ onLoad={() => {
161
+ closeSpinner();
162
+ }}
112
163
  >
113
164
  <RenderBlocks
114
165
  {...props}
@@ -63,6 +63,8 @@ import SubscriptionBlockView from '@eeacms/volto-clms-theme/components/Blocks/Cc
63
63
  import SubscriptionBlockEdit from '@eeacms/volto-clms-theme/components/Blocks/CclSubscriptionBlock/SubscriptionEdit';
64
64
  import CclFAQBlockEdit from '@eeacms/volto-clms-theme/components/Blocks/CclFAQBlock/CclFAQBlockEdit';
65
65
  import CclFAQBlockView from '@eeacms/volto-clms-theme/components/Blocks/CclFAQBlock/CclFAQBlockView';
66
+ import CclHelpdeskDocBlockEdit from '@eeacms/volto-clms-theme/components/Blocks/CclHelpdeskDocBlock/CclHelpdeskDocBlockEdit';
67
+ import CclHelpdeskDocBlockView from '@eeacms/volto-clms-theme/components/Blocks/CclHelpdeskDocBlock/CclHelpdeskDocBlockView';
66
68
  import containerSVG from '@plone/volto/icons/apps.svg';
67
69
  import {
68
70
  customIdFieldSchema,
@@ -76,6 +78,8 @@ import linkSVG from '@plone/volto/icons/link.svg';
76
78
  import navSVG from '@plone/volto/icons/nav.svg';
77
79
  import codeSVG from '@plone/volto/icons/code.svg';
78
80
  import upSVG from '@plone/volto/icons/up-key.svg';
81
+ import infoSVG from '@plone/volto/icons/info.svg';
82
+
79
83
  import ImageWidget from '@eeacms/volto-clms-theme/components/Widgets/ImageWidget';
80
84
  import AttachmentWithSizeLimit from '@eeacms/volto-clms-theme/components/Widgets/AttachmentWithSizeLimit';
81
85
  import TextareaWithRequestData from '@eeacms/volto-clms-theme/components/Widgets/TextareaWithRequestData';
@@ -644,6 +648,22 @@ const customBlocks = (config) => ({
644
648
  view: [], // Future proof (not implemented yet) view user role(s)
645
649
  },
646
650
  },
651
+ cclHelpdeskDoc: {
652
+ id: 'cclHelpdeskDoc', // The name (id) of the block
653
+ title: 'Helpdesk Documentation block', // The display name of the block
654
+ icon: infoSVG, // The icon used in the block chooser
655
+ group: 'ccl_blocks', // The group (blocks can be grouped, displayed in the chooser)
656
+ view: CclHelpdeskDocBlockView, // The view mode component
657
+ edit: CclHelpdeskDocBlockEdit, // The edit mode component
658
+ restricted: false, // If the block is restricted, it won't show in the chooser
659
+ mostUsed: false, // A meta group `most used`, appearing at the top of the chooser
660
+ blockHasOwnFocusManagement: false, // Set this to true if the block manages its own focus
661
+ sidebarTab: 1, // The sidebar tab you want to be selected when selecting the block
662
+ security: {
663
+ addPermission: [], // Future proof (not implemented yet) add user permission role(s)
664
+ view: [], // Future proof (not implemented yet) view user role(s)
665
+ },
666
+ },
647
667
  teaser: {
648
668
  ...config.blocks.blocksConfig.teaser,
649
669
  blockSchema: (intl) => {