volto-aemet 1.0.0-alpha.0

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.
Files changed (91) hide show
  1. package/.changelog.draft +3 -0
  2. package/.release-it.json +34 -0
  3. package/CHANGELOG.md +11 -0
  4. package/LICENSE +21 -0
  5. package/README.md +323 -0
  6. package/babel.config.js +17 -0
  7. package/locales/de/LC_MESSAGES/volto.po +83 -0
  8. package/locales/en/LC_MESSAGES/volto.po +83 -0
  9. package/locales/es/LC_MESSAGES/volto.po +90 -0
  10. package/locales/pt_BR/LC_MESSAGES/volto.po +88 -0
  11. package/locales/volto.pot +85 -0
  12. package/news/.gitkeep +0 -0
  13. package/package.json +47 -0
  14. package/public/.gitkeep +0 -0
  15. package/src/components/Aemet/Weather.js +289 -0
  16. package/src/components/Aemet/Weather.test.js +398 -0
  17. package/src/components/Aemet/WeatherCurrent.jsx +97 -0
  18. package/src/components/Aemet/WeatherCurrent.test.jsx +282 -0
  19. package/src/components/Aemet/index.js +9 -0
  20. package/src/components/Blocks/AemetWeatherCurrent/View.jsx +11 -0
  21. package/src/components/Blocks/AemetWeatherForecast/Edit.jsx +72 -0
  22. package/src/components/Blocks/AemetWeatherForecast/View.jsx +32 -0
  23. package/src/components/Blocks/AemetWeatherForecast/messages.js +36 -0
  24. package/src/components/Blocks/AemetWeatherForecast/schema.js +37 -0
  25. package/src/config/blocks.ts +70 -0
  26. package/src/config/settings.ts +16 -0
  27. package/src/icons/aemet-logo.svg +311 -0
  28. package/src/icons/weather/temps/11.png +0 -0
  29. package/src/icons/weather/temps/11n.png +0 -0
  30. package/src/icons/weather/temps/12.png +0 -0
  31. package/src/icons/weather/temps/12n.png +0 -0
  32. package/src/icons/weather/temps/13.png +0 -0
  33. package/src/icons/weather/temps/13n.png +0 -0
  34. package/src/icons/weather/temps/14.png +0 -0
  35. package/src/icons/weather/temps/14n.png +0 -0
  36. package/src/icons/weather/temps/15.png +0 -0
  37. package/src/icons/weather/temps/15n.png +0 -0
  38. package/src/icons/weather/temps/16.png +0 -0
  39. package/src/icons/weather/temps/16n.png +0 -0
  40. package/src/icons/weather/temps/17.png +0 -0
  41. package/src/icons/weather/temps/17n.png +0 -0
  42. package/src/icons/weather/temps/23.png +0 -0
  43. package/src/icons/weather/temps/23n.png +0 -0
  44. package/src/icons/weather/temps/24.png +0 -0
  45. package/src/icons/weather/temps/24n.png +0 -0
  46. package/src/icons/weather/temps/25.png +0 -0
  47. package/src/icons/weather/temps/25n.png +0 -0
  48. package/src/icons/weather/temps/26.png +0 -0
  49. package/src/icons/weather/temps/26n.png +0 -0
  50. package/src/icons/weather/temps/33.png +0 -0
  51. package/src/icons/weather/temps/33n.png +0 -0
  52. package/src/icons/weather/temps/34.png +0 -0
  53. package/src/icons/weather/temps/34n.png +0 -0
  54. package/src/icons/weather/temps/35.png +0 -0
  55. package/src/icons/weather/temps/35n.png +0 -0
  56. package/src/icons/weather/temps/36.png +0 -0
  57. package/src/icons/weather/temps/36n.png +0 -0
  58. package/src/icons/weather/temps/43.png +0 -0
  59. package/src/icons/weather/temps/43n.png +0 -0
  60. package/src/icons/weather/temps/44.png +0 -0
  61. package/src/icons/weather/temps/44n.png +0 -0
  62. package/src/icons/weather/temps/45.png +0 -0
  63. package/src/icons/weather/temps/45n.png +0 -0
  64. package/src/icons/weather/temps/46.png +0 -0
  65. package/src/icons/weather/temps/46n.png +0 -0
  66. package/src/icons/weather/temps/51.png +0 -0
  67. package/src/icons/weather/temps/51n.png +0 -0
  68. package/src/icons/weather/temps/52.png +0 -0
  69. package/src/icons/weather/temps/52n.png +0 -0
  70. package/src/icons/weather/temps/53.png +0 -0
  71. package/src/icons/weather/temps/54.png +0 -0
  72. package/src/icons/weather/temps/54n.png +0 -0
  73. package/src/icons/weather/temps/61.png +0 -0
  74. package/src/icons/weather/temps/61n.png +0 -0
  75. package/src/icons/weather/temps/62.png +0 -0
  76. package/src/icons/weather/temps/62n.png +0 -0
  77. package/src/icons/weather/temps/63.png +0 -0
  78. package/src/icons/weather/temps/63n.png +0 -0
  79. package/src/icons/weather/temps/64.png +0 -0
  80. package/src/icons/weather/temps/64n.png +0 -0
  81. package/src/icons/weather/temps/71.png +0 -0
  82. package/src/icons/weather/temps/71n.png +0 -0
  83. package/src/icons/weather/temps/72.png +0 -0
  84. package/src/icons/weather/temps/72n.png +0 -0
  85. package/src/icons/weather/temps/73.png +0 -0
  86. package/src/icons/weather/temps/74.png +0 -0
  87. package/src/index.ts +24 -0
  88. package/src/theme/components/_weather_current.scss +69 -0
  89. package/src/theme/main.scss +3 -0
  90. package/towncrier.toml +33 -0
  91. package/tsconfig.json +30 -0
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Unit tests for WeatherCurrent.jsx view component.
3
+ * @module components/Aemet/WeatherCurrent.test
4
+ */
5
+
6
+ import React from 'react';
7
+ import { render, screen } from '@testing-library/react';
8
+ import { IntlProvider } from 'react-intl';
9
+ import { useWeatherData, getWeatherIcon } from './Weather.js';
10
+ import WeatherCurrent from './WeatherCurrent.jsx';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Mocks (hoisted by babel-jest before imports)
14
+ // ---------------------------------------------------------------------------
15
+
16
+ jest.mock('./Weather.js', () => ({
17
+ weatherMessages: {
18
+ minimumMaximum: {
19
+ id: 'minimum_maximum',
20
+ defaultMessage: 'Minimum-Maximum',
21
+ },
22
+ currentDay: { id: 'current_day', defaultMessage: 'Current day' },
23
+ currentLocation: {
24
+ id: 'current_location',
25
+ defaultMessage: 'Current location',
26
+ },
27
+ currentTemperature: {
28
+ id: 'current_temperature',
29
+ defaultMessage: 'Current temperature',
30
+ },
31
+ currentTime: { id: 'current_time', defaultMessage: 'Current time' },
32
+ loadingCurrent: {
33
+ id: 'loading_weather_forecast',
34
+ defaultMessage: 'Loading weather forecast...',
35
+ },
36
+ weatherNotAvailable: {
37
+ id: 'not_available',
38
+ defaultMessage: 'Not available',
39
+ },
40
+ weatherDescription: {
41
+ id: 'weather_description',
42
+ defaultMessage: 'Weather description',
43
+ },
44
+ },
45
+ getWeatherIcon: jest.fn(() => 'mocked-icon.png'),
46
+ useWeatherData: jest.fn(),
47
+ }));
48
+
49
+ jest.mock('@plone/volto/components/theme/Image/Image', () => {
50
+ const MockImage = ({ src, alt }) => <img src={src} alt={alt} />;
51
+ return MockImage;
52
+ });
53
+
54
+ // Fix moment to a predictable point in time so clock assertions are stable.
55
+ jest.mock('moment', () => {
56
+ const fixedTime = {
57
+ format: jest.fn((pattern) => {
58
+ if (pattern === 'HH:mm') return '14:30';
59
+ if (pattern === 'ddd') return 'Fri';
60
+ if (pattern === 'YYYY-MM-DD') return '2024-01-15';
61
+ return '';
62
+ }),
63
+ };
64
+ return jest.fn(() => fixedTime);
65
+ });
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Test data
69
+ // ---------------------------------------------------------------------------
70
+
71
+ const MOCK_DAY = {
72
+ currentHour: '14',
73
+ date: '2024-01-15',
74
+ name: 'Fuenlabrada',
75
+ provinceName: 'Madrid',
76
+ skyStateDescription: 'Clear sky',
77
+ skyStateText: '11',
78
+ minTemp: '18',
79
+ maxTemp: '33',
80
+ timePeriod: '12-18',
81
+ };
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Helpers
85
+ // ---------------------------------------------------------------------------
86
+
87
+ const renderComponent = () =>
88
+ render(
89
+ <IntlProvider locale="en" messages={{}}>
90
+ <WeatherCurrent />
91
+ </IntlProvider>,
92
+ );
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Tests
96
+ // ---------------------------------------------------------------------------
97
+
98
+ describe('WeatherCurrent', () => {
99
+ afterEach(() => {
100
+ // clearAllMocks preserves mock implementations (including the moment factory)
101
+ // while still resetting call counts. resetAllMocks would strip the
102
+ // jest.fn(() => fixedTime) factory, making moment() return undefined.
103
+ jest.clearAllMocks();
104
+ });
105
+
106
+ // -------------------------------------------------------------------------
107
+ describe('loading state', () => {
108
+ it('shows the loading message when isLoading is true', () => {
109
+ useWeatherData.mockReturnValue({ forecast: [], isLoading: true });
110
+ renderComponent();
111
+ expect(screen.getByText(/Loading weather forecast/i)).toBeInTheDocument();
112
+ });
113
+
114
+ it('shows the loading message when forecast array is empty', () => {
115
+ useWeatherData.mockReturnValue({ forecast: [], isLoading: false });
116
+ renderComponent();
117
+ expect(screen.getByText(/Loading weather forecast/i)).toBeInTheDocument();
118
+ });
119
+
120
+ it('does not render the forecast card while loading', () => {
121
+ useWeatherData.mockReturnValue({ forecast: [], isLoading: true });
122
+ const { container } = renderComponent();
123
+ expect(
124
+ container.querySelector('.weather-current-card'),
125
+ ).not.toBeInTheDocument();
126
+ });
127
+ });
128
+
129
+ // -------------------------------------------------------------------------
130
+ describe('data state', () => {
131
+ beforeEach(() => {
132
+ useWeatherData.mockReturnValue({
133
+ forecast: [MOCK_DAY],
134
+ isLoading: false,
135
+ });
136
+ });
137
+
138
+ it('does not show the loading message when data is available', () => {
139
+ renderComponent();
140
+ expect(
141
+ screen.queryByText(/Loading weather forecast/i),
142
+ ).not.toBeInTheDocument();
143
+ });
144
+
145
+ it('renders the current time in HH:mm format', () => {
146
+ renderComponent();
147
+ expect(screen.getByText('14:30')).toBeInTheDocument();
148
+ });
149
+
150
+ it('renders the day abbreviation in uppercase', () => {
151
+ renderComponent();
152
+ expect(screen.getByText('FRI')).toBeInTheDocument();
153
+ });
154
+
155
+ it('renders the name', () => {
156
+ renderComponent();
157
+ expect(screen.getByText(/Fuenlabrada/)).toBeInTheDocument();
158
+ });
159
+
160
+ it('renders the province name', () => {
161
+ renderComponent();
162
+ expect(screen.getByText(/Madrid/)).toBeInTheDocument();
163
+ });
164
+
165
+ it('renders the temperature range (min-max)', () => {
166
+ renderComponent();
167
+ expect(screen.getByText('18-33\u00ba')).toBeInTheDocument();
168
+ });
169
+
170
+ it('renders the maximum temperature', () => {
171
+ renderComponent();
172
+ expect(screen.getByText('33\u00ba')).toBeInTheDocument();
173
+ });
174
+
175
+ it('renders the weather icon with sky state description as alt text', () => {
176
+ renderComponent();
177
+ expect(screen.getByRole('img')).toHaveAttribute('alt', 'Clear sky');
178
+ });
179
+
180
+ it('sets the sky state description as the icon wrapper title', () => {
181
+ const { container } = renderComponent();
182
+ expect(container.querySelector('.weather-current-icon')).toHaveAttribute(
183
+ 'title',
184
+ 'Clear sky',
185
+ );
186
+ });
187
+
188
+ it('calls getWeatherIcon with the correct sky state text', () => {
189
+ renderComponent();
190
+ expect(getWeatherIcon).toHaveBeenCalledWith('11');
191
+ });
192
+
193
+ it('sets the currentTime title on the time span', () => {
194
+ const { container } = renderComponent();
195
+ expect(container.querySelector('.weather-current-time')).toHaveAttribute(
196
+ 'title',
197
+ 'Current time',
198
+ );
199
+ });
200
+
201
+ it('sets the currentDay title on the day span', () => {
202
+ const { container } = renderComponent();
203
+ expect(container.querySelector('.weather-current-day')).toHaveAttribute(
204
+ 'title',
205
+ 'Current day',
206
+ );
207
+ });
208
+
209
+ it('sets the currentLocation title on the city div', () => {
210
+ const { container } = renderComponent();
211
+ expect(container.querySelector('.weather-current-city')).toHaveAttribute(
212
+ 'title',
213
+ 'Current location',
214
+ );
215
+ });
216
+ });
217
+
218
+ // -------------------------------------------------------------------------
219
+ describe('fallbacks', () => {
220
+ it('still renders province when name is missing', () => {
221
+ useWeatherData.mockReturnValue({
222
+ forecast: [{ ...MOCK_DAY, name: '' }],
223
+ isLoading: false,
224
+ });
225
+ const { container } = renderComponent();
226
+ expect(
227
+ container.querySelector('.weather-current-city').textContent,
228
+ ).toContain('Madrid');
229
+ });
230
+
231
+ it('still renders name when provinceName is missing', () => {
232
+ useWeatherData.mockReturnValue({
233
+ forecast: [{ ...MOCK_DAY, provinceName: '' }],
234
+ isLoading: false,
235
+ });
236
+ const { container } = renderComponent();
237
+ expect(
238
+ container.querySelector('.weather-current-city').textContent,
239
+ ).toContain('Fuenlabrada');
240
+ });
241
+
242
+ it('uses the weatherDescription default message as icon title when skyStateDescription is absent', () => {
243
+ useWeatherData.mockReturnValue({
244
+ forecast: [{ ...MOCK_DAY, skyStateDescription: '' }],
245
+ isLoading: false,
246
+ });
247
+ const { container } = renderComponent();
248
+ expect(container.querySelector('.weather-current-icon')).toHaveAttribute(
249
+ 'title',
250
+ 'Weather description',
251
+ );
252
+ });
253
+ });
254
+
255
+ // -------------------------------------------------------------------------
256
+ describe('clock timer', () => {
257
+ beforeEach(() => {
258
+ jest.useFakeTimers();
259
+ useWeatherData.mockReturnValue({
260
+ forecast: [MOCK_DAY],
261
+ isLoading: false,
262
+ });
263
+ });
264
+
265
+ afterEach(() => {
266
+ jest.useRealTimers();
267
+ });
268
+
269
+ it('registers a 60-second interval for the live clock', () => {
270
+ const spy = jest.spyOn(global, 'setInterval');
271
+ renderComponent();
272
+ expect(spy).toHaveBeenCalledWith(expect.any(Function), 60000);
273
+ });
274
+
275
+ it('clears the interval on unmount to prevent memory leaks', () => {
276
+ const clearSpy = jest.spyOn(global, 'clearInterval');
277
+ const { unmount } = renderComponent();
278
+ unmount();
279
+ expect(clearSpy).toHaveBeenCalled();
280
+ });
281
+ });
282
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Index file for Aemet component.
3
+ * @module components/Aemet
4
+ */
5
+
6
+ import WeatherCurrent from './WeatherCurrent.jsx';
7
+
8
+ export { getWeatherIcon, useWeatherData, weatherMessages } from './Weather.js';
9
+ export default WeatherCurrent;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Aemet Weather Current View mode.
3
+ */
4
+ import React from 'react';
5
+ import WeatherCurrent from 'volto-aemet/components/Aemet';
6
+
7
+ const AemetWeatherCurrentView = (props) => {
8
+ return <WeatherCurrent />;
9
+ };
10
+
11
+ export default AemetWeatherCurrentView;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Aemet Weather Forecast Edit mode.
3
+ */
4
+ import React from 'react';
5
+ import cx from 'classnames';
6
+ import messages from './messages';
7
+ import SidebarPortal from '@plone/volto/components/manage/Sidebar/SidebarPortal';
8
+ import { BlockDataForm } from '@plone/volto/components/manage/Form';
9
+ import { useIntl } from 'react-intl';
10
+
11
+ const AemetWeatherForecastEdit = (props) => {
12
+ const {
13
+ block,
14
+ data,
15
+ style,
16
+ className,
17
+ blocksConfig,
18
+ selected,
19
+ onChangeBlock,
20
+ navRoot,
21
+ contentType,
22
+ blocksErrors,
23
+ } = props;
24
+ const intl = useIntl();
25
+
26
+ const schema = blocksConfig['aemetWeatherForecast'].blockSchema({
27
+ props,
28
+ intl,
29
+ });
30
+
31
+ return (
32
+ <>
33
+ <div style={style} className={cx('block handson', { className })}>
34
+ <div className="block-inner-container">
35
+ <iframe
36
+ name="iframe_aemet_id33044"
37
+ title={intl.formatMessage(messages.aemetWeatherForecastBlock)}
38
+ width="100%"
39
+ height="100%"
40
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
41
+ tabindex="0"
42
+ id="iframe_aemet_id33044"
43
+ src={`https://www.aemet.es/es/eltiempo/prediccion/municipios/mostrarwidget/${data.province || 'sevilla'}-id${data.location_id || '41091'}?w=g4p011100001ohmffffffx4f86d9t95b6e9r1s8n2`}
44
+ frameborder="0"
45
+ scrolling="no"
46
+ ></iframe>
47
+ </div>
48
+ </div>
49
+ <SidebarPortal selected={selected}>
50
+ <BlockDataForm
51
+ schema={schema}
52
+ title={schema.title}
53
+ onChangeField={(id, value) => {
54
+ onChangeBlock(block, {
55
+ ...data,
56
+ [id]: value,
57
+ });
58
+ }}
59
+ onChangeBlock={onChangeBlock}
60
+ formData={data}
61
+ block={block}
62
+ blocksConfig={blocksConfig}
63
+ navRoot={navRoot}
64
+ contentType={contentType}
65
+ errors={blocksErrors}
66
+ />
67
+ </SidebarPortal>
68
+ </>
69
+ );
70
+ };
71
+
72
+ export default AemetWeatherForecastEdit;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Aemet Weather Forecast View mode.
3
+ */
4
+ import React from 'react';
5
+ import { useIntl } from 'react-intl';
6
+ import cx from 'classnames';
7
+ import messages from './messages';
8
+
9
+ const AemetWeatherForecastView = (props) => {
10
+ const intl = useIntl();
11
+ const { data, style, className } = props;
12
+ return (
13
+ <div style={style} className={cx('block handson', { className })}>
14
+ <div className="block-inner-container">
15
+ <iframe
16
+ name="iframe_aemet_id33044"
17
+ title={intl.formatMessage(messages.aemetWeatherForecastBlock)}
18
+ width="100%"
19
+ height="100%"
20
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
21
+ tabindex="0"
22
+ id="iframe_aemet_id33044"
23
+ src={`https://www.aemet.es/es/eltiempo/prediccion/municipios/mostrarwidget/${data.province || 'sevilla'}-id${data.location_id || '41091'}?w=g4p011100001ohmffffffx4f86d9t95b6e9r1s8n2`}
24
+ frameborder="0"
25
+ scrolling="no"
26
+ ></iframe>
27
+ </div>
28
+ </div>
29
+ );
30
+ };
31
+
32
+ export default AemetWeatherForecastView;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Aemet Weather Forecast Messages.
3
+ */
4
+
5
+ import { defineMessages } from 'react-intl';
6
+
7
+ const messages = defineMessages({
8
+ aemetWeatherForecastBlock: {
9
+ id: 'AEMET Weather Forecast',
10
+ defaultMessage: 'AEMET Weather Forecast',
11
+ },
12
+ locationID: {
13
+ id: 'Location ID',
14
+ defaultMessage: 'Location ID',
15
+ },
16
+ locationIDHelp: {
17
+ id: "The Location ID of the AEMET service, for example '41091' to Sevilla location ID.",
18
+ defaultMessage:
19
+ "The Location ID of the AEMET service, for example '41091' to Sevilla location ID.",
20
+ },
21
+ province: {
22
+ id: 'Province',
23
+ defaultMessage: 'Province',
24
+ },
25
+ provinceHelp: {
26
+ id: 'The name of the province of the location (in lowercase) of the AEMET service.',
27
+ defaultMessage:
28
+ 'The name of the province of the location (in lowercase) of the AEMET service.',
29
+ },
30
+ defaultFieldset: {
31
+ id: 'Default',
32
+ defaultMessage: 'Default',
33
+ },
34
+ });
35
+
36
+ export default messages;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Aemet Weather Forecast Schema.
3
+ */
4
+
5
+ import messages from './messages';
6
+
7
+ export function AemetWeatherForecastSchema({ props, intl }) {
8
+ const schema = {
9
+ title: intl.formatMessage(messages.aemetWeatherForecastBlock),
10
+ block: 'aemetWeatherForecast',
11
+
12
+ fieldsets: [
13
+ {
14
+ id: 'default',
15
+ title: intl.formatMessage(messages.defaultFieldset),
16
+ fields: ['province', 'location_id'],
17
+ },
18
+ ],
19
+
20
+ properties: {
21
+ province: {
22
+ title: intl.formatMessage(messages.province),
23
+ description: intl.formatMessage(messages.provinceHelp),
24
+ widget: 'text',
25
+ },
26
+ location_id: {
27
+ title: intl.formatMessage(messages.locationID),
28
+ description: intl.formatMessage(messages.locationIDHelp),
29
+ widget: 'text',
30
+ },
31
+ },
32
+
33
+ required: ['province', 'location_id'],
34
+ };
35
+
36
+ return schema;
37
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Blocks configuration.
3
+ * @module blocks
4
+ */
5
+
6
+ import type { ConfigType } from '@plone/registry';
7
+
8
+ // AEMET Weather Current Block
9
+ import AemetWeatherCurrentView from 'volto-aemet/components/Blocks/AemetWeatherCurrent/View';
10
+
11
+ // AEMET Weather Forecast Block
12
+ import AemetWeatherForecastEdit from 'volto-aemet/components/Blocks/AemetWeatherForecast/Edit';
13
+ import AemetWeatherForecastView from 'volto-aemet/components/Blocks/AemetWeatherForecast/View';
14
+ import { AemetWeatherForecastSchema } from 'volto-aemet/components/Blocks/AemetWeatherForecast/schema';
15
+
16
+ // Icons
17
+ import aemetSVG from 'volto-aemet/icons/aemet-logo.svg';
18
+
19
+ export default function install(config: ConfigType) {
20
+ // Add the new block to the group block order
21
+ config.blocks.groupBlocksOrder = [
22
+ ...config.blocks.groupBlocksOrder,
23
+ {
24
+ id: 'aemetWeatherForecast',
25
+ title: 'AEMET Weather Forecast',
26
+ },
27
+ {
28
+ id: 'aemetWeatherCurrent',
29
+ title: 'AEMET Weather Current',
30
+ },
31
+ ];
32
+
33
+ // AEMET Weather Current Block Config
34
+ (config.blocks.blocksConfig as any).aemetWeatherCurrent = {
35
+ id: 'aemetWeatherCurrent',
36
+ title: 'AEMET Weather Current',
37
+ group: 'media',
38
+ icon: aemetSVG,
39
+ view: AemetWeatherCurrentView,
40
+ // edit: undefined,
41
+ // blockSchema: undefined,
42
+ restricted: false,
43
+ mostUsed: false,
44
+ sidebarTab: 1,
45
+ security: {
46
+ addPermission: [],
47
+ view: [],
48
+ },
49
+ };
50
+
51
+ // AEMET Weather Forecast Block Config
52
+ (config.blocks.blocksConfig as any).aemetWeatherForecast = {
53
+ id: 'aemetWeatherForecast',
54
+ title: 'AEMET Weather Forecast',
55
+ group: 'media',
56
+ icon: aemetSVG,
57
+ view: AemetWeatherForecastView,
58
+ edit: AemetWeatherForecastEdit,
59
+ blockSchema: AemetWeatherForecastSchema,
60
+ restricted: false,
61
+ mostUsed: false,
62
+ sidebarTab: 1,
63
+ security: {
64
+ addPermission: [],
65
+ view: [],
66
+ },
67
+ };
68
+
69
+ return config;
70
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * AEMET Settings.
3
+ * @module settings
4
+ */
5
+
6
+ import type { ConfigType } from '@plone/registry';
7
+ import aemetIconSVG from 'volto-aemet/icons/aemet-logo.svg';
8
+
9
+ export default function install(config: ConfigType) {
10
+ // Add the icon for the AEMET settings control panel
11
+ config.settings.controlPanelsIcons = {
12
+ ...config.settings.controlPanelsIcons,
13
+ 'aemet-settings': aemetIconSVG,
14
+ };
15
+ return config;
16
+ }