From 03a528e815cea1d3e1efd6a20f44c8d6db6cbe38 Mon Sep 17 00:00:00 2001 From: Stanislav Vishnevetskiy Date: Wed, 21 Nov 2018 16:06:21 +0200 Subject: open workflow from context Issue-ID: SDC-1953 Change-Id: I06681f4aae6dff02b57d7f64d9bdcfa81d44c2ab Signed-off-by: Stanislav Vishnevetskiy --- .../scss/components/_versionController.scss | 365 +++++++++++---------- workflow-designer-ui/src/main/frontend/src/App.js | 48 ++- .../main/frontend/src/features/version/Version.js | 8 +- .../frontend/src/features/version/VersionView.jsx | 27 +- .../version/composition/CompositionView.js | 2 +- .../src/features/version/versionConstants.js | 3 + .../version/versionController/VersionController.js | 5 +- .../versionController/VersionControllerView.jsx | 63 ++-- .../VersionControllerView_snapshot-test.js.snap | 2 +- .../versionController/views/ActionButtons.js | 5 + .../views/OperationModeButtons.js | 65 ++++ .../version/versionController/views/SvgButton.js | 5 +- .../src/features/version/versionModeReducer.js | 10 + .../src/main/frontend/src/i18n/languages.json | 4 +- .../src/pluginContext/pluginContextActions.js | 21 ++ .../src/pluginContext/pluginContextConstants.js | 21 ++ .../src/pluginContext/pluginContextReducer.js | 25 ++ .../src/pluginContext/pluginContextSelector.js | 16 + .../src/main/frontend/src/rootReducers.js | 6 +- 19 files changed, 486 insertions(+), 215 deletions(-) create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/OperationModeButtons.js create mode 100644 workflow-designer-ui/src/main/frontend/src/features/version/versionModeReducer.js create mode 100644 workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextActions.js create mode 100644 workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextConstants.js create mode 100644 workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextReducer.js create mode 100644 workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextSelector.js (limited to 'workflow-designer-ui/src/main') diff --git a/workflow-designer-ui/src/main/frontend/resources/scss/components/_versionController.scss b/workflow-designer-ui/src/main/frontend/resources/scss/components/_versionController.scss index 7bf784e4..3cf9b05e 100644 --- a/workflow-designer-ui/src/main/frontend/resources/scss/components/_versionController.scss +++ b/workflow-designer-ui/src/main/frontend/resources/scss/components/_versionController.scss @@ -1,173 +1,196 @@ .version-controller-bar { - display: flex; - height: 70px; - border-bottom: 1px solid $silver; - background-color: transparent; - - .group-name-wrapper { - width: 245px; - .group-name { - @include heading-4-emphasis; - @include ellipsis; - display: block; - padding: 24px 12px 13px 20px; - background-color: $white; - } - } - - .vc-container { - display: flex; - flex: 1; - align-self: center; - background-color: transparent; - justify-content: space-between; - align-items: center; - padding-left: 16px; - padding-right: 100px; - border-left: 1px solid #eaeaea; - height: 45px; - - .vc-separator { - border-left: 1px solid $silver; - height: 37px; - } - - .version-status-container { - display: flex; - align-items: center; - .version-selector-more-versions { - @include body-1-emphasis; - color: $blue; - cursor: pointer; - } - - .version-selector { - margin-top: 0; - padding-right: 10px; - margin-right: 15px; - margin-left: 10px; - border-color: $light-gray; - border-radius: 2px; - width: 243px; - height: 30px; - @include body-1; - } - - .version-section { - .form-group { - margin-right: 20px; - - .input-options { - border: none; - - .input-options-select { - padding-top: 4px; - } - } - } - } - - .vc-status { - display: flex; - padding-left: 20px; - border-left: $light-gray thin solid; - - .status-text { - align-self: center; - margin-top: 2px; - @include heading-5; - color: $dark-gray; - } - } - } - - .save-submit-cancel-container { - display: flex; - align-items: center; - height: 100%; - - .action-buttons, .select-action-buttons, .vc-save-section, .vc-submit-section { - display: flex; - align-items: center; - height: 100%; - cursor: $cursor-pointer; - - .vc-submit-button { - border: 1px solid $dark-gray; - width: 94px; - height: 30px; - border-radius: 2px; - padding-top: 5px; - padding-left: 10px; - margin-left: 10px; - margin-right: 10px; - - &:hover:not(.disabled) { - cursor: pointer; - background-color: $silver; - } - - &.disabled { - border-color: $light-gray; - } - - .vc-v-submit { - width: 11px; - height: 8px; - margin-right: 10px; - position: relative; - top: -1px; - } - } - - .certifyBtn { - margin-left: 20px; - } - - .version-control-buttons { - display: flex; - } - - .action-button-wrapper { - display: flex; - align-items: center; - height: 70px; - padding: 10px; - - &:hover { - background-color: $silver; - } - - &:active { - background-color: $light-gray; - } - - .action-buttons-svg { - padding-left: 10px; - padding-right: 10px; - - .svg-icon { - fill: $text-black; - height: 20px; - - &, &.__version-controller-save { width: 20px; } - &.__version-controller-permissions { width: 32px; } - &.__version-controller-undo, &.__version-controller-revert { width: 20px; } - &.__version-controller-sync, &.__version-controller-commit { width: 28px; } - } - } - - } - - .action-button-label { - @include body-4; - display: block; - height: 1em; - margin-top: 5px; - margin-bottom: 0; - } - - } - } - } + display: flex; + height: 70px; + border-bottom: 1px solid $silver; + background-color: transparent; + justify-content: space-between; + .group-name-wrapper { + width: 245px; + .group-name { + @include heading-4-emphasis; + @include ellipsis; + display: block; + padding: 24px 12px 13px 20px; + background-color: $white; + } + } + + .vc-container { + display: flex; + flex: 1; + align-self: center; + background-color: transparent; + justify-content: space-between; + align-items: center; + padding-left: 16px; + padding-right: 100px; + border-left: 1px solid #eaeaea; + height: 45px; + &.vs-container-operation { + flex: inherit; + } + .vc-separator { + border-left: 1px solid $silver; + height: 37px; + } + + .version-status-container { + display: flex; + align-items: center; + .version-selector-more-versions { + @include body-1-emphasis; + color: $blue; + cursor: pointer; + } + + .version-selector { + margin-top: 0; + padding-right: 10px; + margin-right: 15px; + margin-left: 10px; + border-color: $light-gray; + border-radius: 2px; + width: 243px; + height: 30px; + @include body-1; + } + + .version-section { + .form-group { + margin-right: 20px; + + .input-options { + border: none; + + .input-options-select { + padding-top: 4px; + } + } + } + } + + .vc-status { + display: flex; + padding-left: 20px; + border-left: $light-gray thin solid; + + .status-text { + align-self: center; + margin-top: 2px; + @include heading-5; + color: $dark-gray; + } + } + } + + .save-submit-cancel-container { + display: flex; + align-items: center; + height: 100%; + + .action-buttons, + .select-action-buttons, + .vc-save-section, + .vc-submit-section { + display: flex; + align-items: center; + height: 100%; + cursor: $cursor-pointer; + + .vc-submit-button { + border: 1px solid $dark-gray; + width: 94px; + height: 30px; + border-radius: 2px; + padding-top: 5px; + padding-left: 10px; + margin-left: 10px; + margin-right: 10px; + + &:hover:not(.disabled) { + cursor: pointer; + background-color: $silver; + } + + &.disabled { + border-color: $light-gray; + } + + .vc-v-submit { + width: 11px; + height: 8px; + margin-right: 10px; + position: relative; + top: -1px; + } + } + + .certifyBtn { + margin-left: 20px; + } + + .version-control-buttons { + display: flex; + } + + .action-button-wrapper { + display: flex; + align-items: center; + height: 70px; + padding: 10px; + + &:hover { + background-color: $silver; + } + + &:active { + background-color: $light-gray; + } + + .action-buttons-svg { + padding-left: 10px; + padding-right: 10px; + + .svg-icon { + fill: $text-black; + height: 20px; + + &, + &.__version-controller-save { + width: 20px; + } + &.__version-controller-permissions { + width: 32px; + } + &.__version-controller-undo, + &.__version-controller-revert { + width: 20px; + } + &.__version-controller-sync, + &.__version-controller-commit { + width: 28px; + } + } + .vs-back-btn { + height: '35px'; + width: '35px'; + .svg-icon { + height: '35px'; + width: '35px'; + transform: rotate(90deg); + } + } + } + } + + .action-button-label { + @include body-4; + display: block; + height: 1em; + margin-top: 5px; + margin-bottom: 0; + } + } + } + } } diff --git a/workflow-designer-ui/src/main/frontend/src/App.js b/workflow-designer-ui/src/main/frontend/src/App.js index 3d9f302b..5b7c154d 100644 --- a/workflow-designer-ui/src/main/frontend/src/App.js +++ b/workflow-designer-ui/src/main/frontend/src/App.js @@ -16,15 +16,18 @@ import { hot } from 'react-hot-loader'; import React, { Component } from 'react'; -import { Route } from 'react-router-dom'; +import { Route, withRouter } from 'react-router-dom'; import qs from 'qs'; - +import { connect } from 'react-redux'; import { PluginPubSub } from 'shared/pubsub/plugin-pubsub'; import 'resources/scss/style.scss'; import 'bpmn-js-properties-panel/styles/properties.less'; import { routes } from 'wfapp/routes'; import { USER_ID } from 'wfapp/appConstants'; - +import { getVersionsAction } from 'features/workflow/overview/overviewConstansts'; +import { setOperationModeAction } from 'features/version/versionConstants'; +import { setPluginContext } from './pluginContext/pluginContextActions'; +import { notificationType } from 'wfapp/pluginContext/pluginContextConstants'; const RouteWithSubRoutes = route => ( ( /> ); +function mapActionsToProps(dispatch) { + return { + getOverview: workflowId => { + dispatch(getVersionsAction(workflowId)); + dispatch(setOperationModeAction()); + }, + setPluginContext: payload => dispatch(setPluginContext(payload)) + }; +} + class App extends Component { constructor(props) { super(props); @@ -48,12 +61,26 @@ class App extends Component { componentDidMount() { if (this.searchParams) { - const { eventsClientId, parentUrl } = this.searchParams; + const { + eventsClientId, + parentUrl, + workflowId, + versionId + } = this.searchParams; if (eventsClientId && parentUrl) { + this.props.setPluginContext({ + eventsClientId, + parentUrl + }); const client = new PluginPubSub(eventsClientId, parentUrl); - - client.notify('READY'); + client.notify(notificationType.READY); + } + if (workflowId && versionId) { + this.props.getOverview(workflowId); + this.props.history.push( + `/workflow/${workflowId}/version/${versionId}/composition` + ); } } } @@ -69,4 +96,11 @@ class App extends Component { } } -export default hot(module)(App); +export default hot(module)( + withRouter( + connect( + null, + mapActionsToProps + )(App) + ) +); diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/Version.js b/workflow-designer-ui/src/main/frontend/src/features/version/Version.js index cfc627ca..5df68759 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/Version.js +++ b/workflow-designer-ui/src/main/frontend/src/features/version/Version.js @@ -2,12 +2,18 @@ import { connect } from 'react-redux'; import VersionView from 'features/version/VersionView'; import { workflowVersionFetchRequestedAction } from 'features/version/versionConstants'; +const mapStateToProps = ({ currentVersion: { operationMode } }) => { + return { + operationMode + }; +}; + const mapDispatchToProps = dispatch => ({ loadSelectedVersion: payload => dispatch(workflowVersionFetchRequestedAction(payload)) }); export default connect( - null, + mapStateToProps, mapDispatchToProps )(VersionView); diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/VersionView.jsx b/workflow-designer-ui/src/main/frontend/src/features/version/VersionView.jsx index a7c0f88e..48c671e2 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/VersionView.jsx +++ b/workflow-designer-ui/src/main/frontend/src/features/version/VersionView.jsx @@ -75,7 +75,7 @@ class VersionView extends React.Component { }; render() { - const { match, routes, history } = this.props; + const { match, routes, history, operationMode } = this.props; const groups = this.getGroups(); const activeItemId = this.getActiveItemIdProps(); @@ -83,18 +83,24 @@ class VersionView extends React.Component { return (
-
-
- -
+ +
+ {!operationMode && ( +
+ +
+ )} {routes.map((route, i) => ( payload ); +export const setOperationModeAction = createAction(SET_OPERRATION_MODE); + export const versionState = { DRAFT: 'draft', CERTIFIED: 'certified' diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionController.js b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionController.js index 8f9c9d7d..7f8769cc 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionController.js +++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionController.js @@ -34,7 +34,7 @@ import { workflowVersionFetchRequestedAction } from '../versionConstants'; import { getIsCertified } from 'features/version/general/generalSelectors'; import { getIOErrors } from 'features/version/inputOutput/inputOutputSelectors'; import { getCompositionHasErrors } from 'features/version/composition/compositionSelectors'; - +import { pluginContextSelector } from 'wfapp/pluginContext/pluginContextSelector'; function mapStateToProps(state) { return { workflowName: getWorkflowName(state), @@ -44,7 +44,8 @@ function mapStateToProps(state) { hasErrors: getIOErrors(state) || getCompositionHasErrors(state), isCertifyDisable: getIsCertified(state), isArchive: isWorkflowArchive(state), - currentWorkflowVersion: state.currentVersion.general + currentWorkflowVersion: state.currentVersion.general, + pluginContext: pluginContextSelector(state) }; } diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionControllerView.jsx b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionControllerView.jsx index cdaf9aeb..2f86db4e 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionControllerView.jsx +++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/VersionControllerView.jsx @@ -17,9 +17,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import ActionButtons from 'features/version/versionController/views/ActionButtons'; +import OperationModeButtons from 'features/version/versionController/views/OperationModeButtons'; import VersionContainer from 'features/version/versionController/views/VersionsContainer'; import WorkflowTitle from 'features/version/versionController/views/WorkflowTitle'; - +import { PluginPubSub } from 'shared/pubsub/plugin-pubsub.ts'; +import { notificationType } from 'wfapp/pluginContext/pluginContextConstants'; export default class VersionControllerView extends Component { static propTypes = { location: PropTypes.object, @@ -38,7 +40,9 @@ export default class VersionControllerView extends Component { changeVersion: PropTypes.func, isCertifyDisable: PropTypes.bool, hasErrors: PropTypes.bool, - isArchive: PropTypes.bool + isArchive: PropTypes.bool, + operationMode: PropTypes.bool, + pluginContext: PropTypes.object }; constructor(props) { @@ -60,7 +64,13 @@ export default class VersionControllerView extends Component { } = this.props; saveParamsToServer({ params: savedParams, workflowId, workflowName }); }; - + handleSendMsgToCatalog = isCompeleted => { + const { + pluginContext: { eventsClientId, parentUrl } + } = this.props; + const client = new PluginPubSub(eventsClientId, parentUrl); + client.notify(notificationType.CLOSE, { isCompleted: isCompeleted }); + }; certifyVersion = () => { const { certifyVersion, @@ -98,27 +108,42 @@ export default class VersionControllerView extends Component { versionsList, hasErrors, isCertifyDisable, - isArchive + isArchive, + operationMode } = this.props; const isReadonly = isCertifyDisable || hasErrors || isArchive; return (
-
- - +
+ {!operationMode && ( + + )} + {operationMode && ( + + )} + {!operationMode && ( + + )}
); diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/__snapshots__/VersionControllerView_snapshot-test.js.snap b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/__snapshots__/VersionControllerView_snapshot-test.js.snap index ce3cd37c..1c8ffba3 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/__snapshots__/VersionControllerView_snapshot-test.js.snap +++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/__tests__/__snapshots__/VersionControllerView_snapshot-test.js.snap @@ -18,7 +18,7 @@ exports[`Version Controller View Snapshot renders correctly 1`] = `
{ onUndoClick, saveDisabled } = props; + return (
@@ -40,7 +41,9 @@ const ActionButtons = props => { disabled={saveDisabled} onClick={onSaveClick} /> +
+ { disabled={certifyDisabled} onClick={onUndoClick} /> +
+ + + +
+
+
+ ); +}; + +OperationModeButtons.propTypes = { + onSaveClick: PropTypes.func, + saveDisabled: PropTypes.bool, + sendMsgToCatalog: PropTypes.func +}; + +export default OperationModeButtons; diff --git a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/SvgButton.js b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/SvgButton.js index d08ff434..4a607e6b 100644 --- a/workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/SvgButton.js +++ b/workflow-designer-ui/src/main/frontend/src/features/version/versionController/views/SvgButton.js @@ -19,6 +19,7 @@ import PropTypes from 'prop-types'; const SvgButton = props => { const { + className = '', name, tooltipText, disabled, @@ -35,6 +36,7 @@ const SvgButton = props => { onClick={onClickAction}>
{ + switch (action.type) { + case SET_OPERRATION_MODE: + return true; + default: + return state; + } +}; diff --git a/workflow-designer-ui/src/main/frontend/src/i18n/languages.json b/workflow-designer-ui/src/main/frontend/src/i18n/languages.json index 6b839375..d978e61b 100644 --- a/workflow-designer-ui/src/main/frontend/src/i18n/languages.json +++ b/workflow-designer-ui/src/main/frontend/src/i18n/languages.json @@ -18,9 +18,11 @@ "createBtn": "Create", "cancelBtn": "Cancel", "certifyBtn": "Certify", + "completeBtn": "Complete", "undoBtn": "Undo", "closeBtn": "Close", - "okBtn": "Ok" + "okBtn": "Ok", + "backToCatalog": "Catalog" }, "form": { "name": "Name", diff --git a/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextActions.js b/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextActions.js new file mode 100644 index 00000000..553b8911 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextActions.js @@ -0,0 +1,21 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +*http://www.apache.org/licenses/LICENSE-2.0 +* + * Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { SET_CONTEXT } from './pluginContextConstants'; + +export const setPluginContext = payload => ({ + type: SET_CONTEXT, + payload +}); diff --git a/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextConstants.js b/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextConstants.js new file mode 100644 index 00000000..942555b8 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextConstants.js @@ -0,0 +1,21 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +*http://www.apache.org/licenses/LICENSE-2.0 +* + * Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +export const SET_CONTEXT = 'pluginContext/SET_CONTEXT'; + +export const notificationType = { + READY: 'READY', + CLOSE: 'CLOSE' +}; diff --git a/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextReducer.js b/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextReducer.js new file mode 100644 index 00000000..6e61a3bd --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextReducer.js @@ -0,0 +1,25 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +*http://www.apache.org/licenses/LICENSE-2.0 +* + * Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { SET_CONTEXT } from './pluginContextConstants'; +export default (state = {}, action) => { + switch (action.type) { + case SET_CONTEXT: { + return action.payload; + } + default: + return state; + } +}; diff --git a/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextSelector.js b/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextSelector.js new file mode 100644 index 00000000..65599c55 --- /dev/null +++ b/workflow-designer-ui/src/main/frontend/src/pluginContext/pluginContextSelector.js @@ -0,0 +1,16 @@ +/* +* Copyright © 2018 European Support Limited +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +*http://www.apache.org/licenses/LICENSE-2.0 +* + * Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +export const pluginContextSelector = state => state && state.pluginContext; diff --git a/workflow-designer-ui/src/main/frontend/src/rootReducers.js b/workflow-designer-ui/src/main/frontend/src/rootReducers.js index 825f5825..33fd8a82 100644 --- a/workflow-designer-ui/src/main/frontend/src/rootReducers.js +++ b/workflow-designer-ui/src/main/frontend/src/rootReducers.js @@ -27,6 +27,8 @@ import overviewReducer from 'features/workflow/overview/overviewReducer'; import workflowReducer from 'features/workflow/workflowReducer'; import compositionReducer from 'features/version/composition/compositionReducer'; import activitiesReducer from 'features/activities/activitiesReducer'; +import operationModeReducer from 'features/version/versionModeReducer'; +import pluginContextReducer from './pluginContext/pluginContextReducer'; export default combineReducers({ i18n: i18nReducer, @@ -35,12 +37,14 @@ export default combineReducers({ currentVersion: combineReducers({ general: versionReducer, inputOutput, - composition: compositionReducer + composition: compositionReducer, + operationMode: operationModeReducer }), workflow: combineReducers({ data: workflowReducer, versions: overviewReducer }), + pluginContext: pluginContextReducer, activities: activitiesReducer, loader, modal -- cgit 1.2.3-korg