mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 13:55:21 +02:00
refactor(ui/editor): migrate code-editor to react [EE-4848] (#8257)
This commit is contained in:
parent
273a3f9a10
commit
5507b1e8c9
18 changed files with 259 additions and 130 deletions
|
@ -2,8 +2,6 @@ import 'bootstrap/dist/css/bootstrap.css';
|
|||
import 'toastr/build/toastr.css';
|
||||
import 'xterm/dist/xterm.css';
|
||||
import 'angularjs-slider/dist/rzslider.css';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/addon/lint/lint.css';
|
||||
import 'angular-json-tree/dist/angular-json-tree.css';
|
||||
import 'angular-loading-bar/build/loading-bar.css';
|
||||
import 'angular-moment-picker/dist/angular-moment-picker.min.css';
|
||||
|
|
|
@ -148,8 +148,8 @@ function BuildImageController($scope, $async, $window, BuildService, Notificatio
|
|||
return true;
|
||||
};
|
||||
|
||||
$scope.editorUpdate = function (cm) {
|
||||
$scope.formValues.DockerFileContent = cm.getValue();
|
||||
$scope.editorUpdate = function (value) {
|
||||
$scope.formValues.DockerFileContent = value;
|
||||
$scope.state.isEditorDirty = true;
|
||||
};
|
||||
|
||||
|
|
|
@ -112,8 +112,8 @@ export class EdgeJobFormController {
|
|||
this.formAction(this.formValues.method);
|
||||
}
|
||||
|
||||
editorUpdate(cm) {
|
||||
this.model.FileContent = cm.getValue();
|
||||
editorUpdate(value) {
|
||||
this.model.FileContent = value;
|
||||
this.isEditorDirty = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/* @ngInject */
|
||||
export default function CodeEditorController($scope) {
|
||||
this.handleChange = (value) => {
|
||||
$scope.$evalAsync(() => {
|
||||
this.onChange(value);
|
||||
});
|
||||
};
|
||||
}
|
8
app/portainer/components/code-editor/code-editor.html
Normal file
8
app/portainer/components/code-editor/code-editor.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<react-code-editor
|
||||
id="$ctrl.identifier"
|
||||
placeholder="$ctrl.placeholder"
|
||||
yaml="$ctrl.yml"
|
||||
readonly="$ctrl.readOnly"
|
||||
on-change="($ctrl.handleChange)"
|
||||
value="$ctrl.value"
|
||||
></react-code-editor>
|
|
@ -1,6 +1,8 @@
|
|||
import controller from './code-editor.controller';
|
||||
|
||||
angular.module('portainer.app').component('codeEditor', {
|
||||
templateUrl: './codeEditor.html',
|
||||
controller: 'CodeEditorController',
|
||||
templateUrl: './code-editor.html',
|
||||
controller,
|
||||
bindings: {
|
||||
identifier: '@',
|
||||
placeholder: '@',
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<textarea id="{{ $ctrl.identifier }}" class="form-control" placeholder="{{ $ctrl.placeholder }}"></textarea>
|
|
@ -27,8 +27,8 @@ export default class EnvironmentVariablesPanelController {
|
|||
this.onChange(value);
|
||||
}
|
||||
|
||||
editorUpdate(cm) {
|
||||
this.editorText = cm.getValue();
|
||||
editorUpdate(value) {
|
||||
this.editorText = value;
|
||||
this.onChange(parseDotEnvFile(this.editorText));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<pr-icon icon="'alert-circle'" mode="'primary'"></pr-icon>
|
||||
Switch to simple mode to define variables line by line, or load from .env file
|
||||
</div>
|
||||
<div class="form-group" style="margin-left: 1px">
|
||||
<div class="col-sm-12">
|
||||
<code-editor identifier="environment-variables-editor" placeholder="e.g. key=value" value="$ctrl.editorText" yml="false" on-change="($ctrl.editorUpdate)"></code-editor>
|
||||
</div>
|
||||
<div class="col-sm-12 small text-muted" ng-if="$ctrl.showHelpMessage">
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
class WebEditorFormController {
|
||||
/* @ngInject */
|
||||
constructor(BROWSER_OS_PLATFORM) {
|
||||
this.editorUpdate = this.editorUpdate.bind(this);
|
||||
this.BROWSER_OS_PLATFORM = BROWSER_OS_PLATFORM;
|
||||
}
|
||||
|
||||
editorUpdate(cm) {
|
||||
this.onChange(cm.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
export default WebEditorFormController;
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
read-only="$ctrl.readOnly"
|
||||
yml="$ctrl.yml"
|
||||
value="$ctrl.value"
|
||||
on-change="($ctrl.editorUpdate)"
|
||||
on-change="($ctrl.onChange)"
|
||||
></code-editor>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -39,6 +39,7 @@ import { Slider } from '@@/form-components/Slider';
|
|||
import { TagButton } from '@@/TagButton';
|
||||
import { BETeaserButton } from '@@/BETeaserButton';
|
||||
import { TimeWindowDisplay } from '@@/TimeWindowDisplay';
|
||||
import { CodeEditor } from '@@/CodeEditor';
|
||||
|
||||
import { fileUploadField } from './file-upload-field';
|
||||
import { switchField } from './switch-field';
|
||||
|
@ -241,4 +242,16 @@ export const componentsModule = angular
|
|||
.component(
|
||||
'timeWindowDisplay',
|
||||
r2a(withReactQuery(withUIRouter(TimeWindowDisplay)), [])
|
||||
)
|
||||
.component(
|
||||
'reactCodeEditor',
|
||||
r2a(CodeEditor, [
|
||||
'id',
|
||||
'placeholder',
|
||||
'yaml',
|
||||
'readonly',
|
||||
'onChange',
|
||||
'value',
|
||||
'height',
|
||||
])
|
||||
).name;
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
import _ from 'lodash-es';
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/mode/yaml/yaml.js';
|
||||
import 'codemirror/addon/lint/lint.js';
|
||||
import 'codemirror/addon/lint/yaml-lint.js';
|
||||
import 'codemirror/addon/display/placeholder.js';
|
||||
import 'codemirror/addon/search/search.js';
|
||||
import 'codemirror/addon/search/searchcursor.js';
|
||||
import 'codemirror/addon/search/jump-to-line.js';
|
||||
import 'codemirror/addon/dialog/dialog.js';
|
||||
import './codeMirrorDialog.css';
|
||||
|
||||
angular.module('portainer.app').factory('CodeMirrorService', function CodeMirrorService() {
|
||||
'use strict';
|
||||
|
||||
var service = {};
|
||||
|
||||
var codeMirrorGenericOptions = {
|
||||
lineNumbers: true,
|
||||
extraKeys: {
|
||||
'Alt-F': 'findPersistent',
|
||||
},
|
||||
};
|
||||
|
||||
var codeMirrorYAMLOptions = {
|
||||
mode: 'text/x-yaml',
|
||||
gutters: ['CodeMirror-lint-markers'],
|
||||
lint: true,
|
||||
extraKeys: {
|
||||
'Alt-F': 'findPersistent',
|
||||
Tab: function (cm) {
|
||||
var spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
||||
cm.replaceSelection(spaces);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
service.applyCodeMirrorOnElement = function (element, yamlLint, readOnly) {
|
||||
var options = angular.copy(codeMirrorGenericOptions);
|
||||
|
||||
if (yamlLint) {
|
||||
_.assign(options, codeMirrorYAMLOptions);
|
||||
}
|
||||
|
||||
if (readOnly) {
|
||||
options.readOnly = true;
|
||||
}
|
||||
|
||||
var cm = CodeMirror.fromTextArea(element, options);
|
||||
cm.setSize('100%', 500);
|
||||
return cm;
|
||||
};
|
||||
|
||||
return service;
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
/* styles from https://github.com/codemirror/codemirror5/blob/master/addon/dialog/dialog.css with the button styles updated */
|
||||
|
||||
.CodeMirror-dialog {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: inherit;
|
||||
z-index: 15;
|
||||
padding: 0.1em 0.8em;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-top {
|
||||
border-bottom: 1px solid #eee;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-bottom {
|
||||
border-top: 1px solid #eee;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
width: 20em;
|
||||
color: inherit;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog button {
|
||||
/* apply styles from btn-default */
|
||||
@apply border-gray-5 bg-white text-gray-9;
|
||||
@apply hover:border-gray-5 hover:bg-gray-3 hover:text-gray-10;
|
||||
/* dark mode */
|
||||
@apply th-dark:border-gray-warm-7 th-dark:bg-gray-warm-10 th-dark:text-gray-warm-4;
|
||||
@apply th-dark:hover:border-gray-6 th-dark:hover:bg-gray-warm-9 th-dark:hover:text-gray-warm-4;
|
||||
/* highcontrast mode */
|
||||
@apply th-highcontrast:border-gray-warm-7 th-highcontrast:bg-gray-warm-10 th-highcontrast:text-white;
|
||||
@apply th-highcontrast:hover:border-gray-6 th-highcontrast:hover:bg-gray-warm-9 th-highcontrast:hover:text-white;
|
||||
|
||||
@apply font-sans;
|
||||
@apply border border-solid;
|
||||
font-size: 85%;
|
||||
padding: 0px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
|
@ -268,10 +268,10 @@ angular.module('portainer.app').controller('StackController', [
|
|||
});
|
||||
};
|
||||
|
||||
$scope.editorUpdate = function (cm) {
|
||||
if ($scope.stackFileContent.replace(/(\r\n|\n|\r)/gm, '') !== cm.getValue().replace(/(\r\n|\n|\r)/gm, '')) {
|
||||
$scope.editorUpdate = function (value) {
|
||||
if ($scope.stackFileContent.replace(/(\r\n|\n|\r)/gm, '') !== value.replace(/(\r\n|\n|\r)/gm, '')) {
|
||||
$scope.state.isEditorDirty = true;
|
||||
$scope.stackFileContent = cm.getValue();
|
||||
$scope.stackFileContent = value;
|
||||
$scope.state.yamlError = StackHelper.validateYAML($scope.stackFileContent, $scope.containerNames, $scope.state.originalContainerNames);
|
||||
}
|
||||
};
|
||||
|
|
41
app/react/components/CodeEditor.tsx
Normal file
41
app/react/components/CodeEditor.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { StreamLanguage } from '@codemirror/language';
|
||||
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
placeholder?: string;
|
||||
yaml?: boolean;
|
||||
readonly?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
value: string;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
export function CodeEditor({
|
||||
id,
|
||||
onChange,
|
||||
placeholder,
|
||||
readonly,
|
||||
value,
|
||||
height = '500px',
|
||||
yaml: isYaml,
|
||||
}: Props) {
|
||||
const extensions = useMemo(
|
||||
() => (isYaml ? [StreamLanguage.define(yaml)] : []),
|
||||
[isYaml]
|
||||
);
|
||||
|
||||
return (
|
||||
<CodeMirror
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
readOnly={readonly}
|
||||
placeholder={placeholder}
|
||||
id={id}
|
||||
extensions={extensions}
|
||||
height={height}
|
||||
/>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue