Skip to content
Open
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
# run: npm run test

- name: Cypress run
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7.4.8
with:
browser: chrome
install: false
Expand Down
4 changes: 4 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import { LinkButton, CaseProgressBar } from "./renderer";
import "../assets/css/tabs.css";
import FormCollectionRecordControl from "./renderer/form-collection-record-control.vue";
import FormCollectionViewControl from "./renderer/form-collection-view-control.vue";
import VueRenderPreview from "./renderer/vueRender/VueRenderPreview.vue";
import VueRenderEditor from "./renderer/vueRender/VueRenderEditor.vue";

const rendererComponents = {
...renderer,
Expand Down Expand Up @@ -161,6 +163,8 @@ export default {
Vue.component("VueFormRenderer", VueFormRenderer);
Vue.component("DefaultLoadingSpinner", DefaultLoadingSpinner);
Vue.use(VueFormElements);
Vue.component("VueRenderPreview", VueRenderPreview);
Vue.component("VueRenderEditor", VueRenderEditor);
Vue.component("FormButton", FormButton);
Vue.component("FileUpload", FileUpload);
Vue.component("FileDownload", FileDownload);
Expand Down
239 changes: 239 additions & 0 deletions src/components/inspector/vueRender/EditorFieldInspector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
<template>
<div>
<div class="d-flex align-items-center justify-content-between inspector-label-row">
<label :for="'inspector-defaultValue-jsValue'">{{ $t(label) }}</label>
<button
v-if="enablePopup"
type="button"
class="btn btn-light btn-sm"
:title="$t(resolvedOpenPopupLabel)"
data-test="tce-control-expand-button"
:aria-label="resolvedOpenPopupLabel"
@click="openCodePopup"
>
<i class="fas fa-expand" />
</button>
</div>
<MonacoEditor
v-model="jsValue"
:options="monacoOptions"
class="editor"
:language="language"
data-cy="inspector-defaultValue-jsValue"
/>
<b-modal
v-if="enablePopup"
v-model="isPopupOpen"
:title="$t(resolvedPopupTitle)"
size="xl"
dialog-class="editor-dialog-large"
header-close-content="&times;"
no-close-on-backdrop
@hidden="onCodePopupHidden"
>
<div
class="tce-p-4"
data-test="tce-control-code-input"
>
<MonacoEditor
v-model="popupCode"
:options="popupMonacoOptions"
class="popup-editor"
:language="language"
/>
</div>
<template #modal-footer>
<div class="tce-flex tce-justify-end tce-gap-2 w-100">
<button
type="button"
class="btn btn-outline-secondary"
data-test="tce-control-close-button"
@click="closeCodePopup"
>
{{ $t(resolvedCancelPopupLabel) }}
</button>
<button
type="button"
class="btn btn-primary ml-2"
data-test="tce-control-save-button"
@click="saveCodePopup"
>
{{ $t(resolvedSavePopupLabel) }}
</button>
</div>
</template>
</b-modal>
<small
v-if="helper"
class="form-text text-muted mt-2"
>{{ $t(helper) }}</small>
</div>
</template>

<script>
import MonacoEditor from 'vue-monaco';

const _ = window._ || {
isEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b),
};

export default {
components: {
MonacoEditor,
},
props: {
value: {
type: [String, Number, Object],
default: '',
},
helper: {
type: String,
default: '',
},
language: {
type: String,
default: 'javascript',
},
label: {
type: String,
default: '',
},
enablePopup: {
type: Boolean,
default: true,
},
openPopupLabel: {
type: String,
default: 'Open in popup',
},
cancelPopupLabel: {
type: String,
default: 'Cancel',
},
savePopupLabel: {
type: String,
default: 'Save',
},
popupTitle: {
type: String,
default: 'Edit source code',
},
},
emits: ['input'],
data() {
return {
mode: 'basic',
jsValue: '',
basicValue: '',
popupCode: '',
isPopupOpen: false,
monacoOptions: {
lineNumbers: 'off',
glyphMargin: false,
folding: false,
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
minimap: { enabled: false },
fixedOverflowWidgets: true,
automaticLayout: true,
},
};
},
computed: {
popupMonacoOptions() {
return {
...this.monacoOptions,
lineNumbers: 'on',
minimap: { enabled: true },
};
},
resolvedOpenPopupLabel() {
return this.openPopupLabel || 'Open in popup';
},
resolvedPopupTitle() {
return this.popupTitle || 'Edit source code';
},
resolvedCancelPopupLabel() {
return this.cancelPopupLabel || 'Cancel';
},
resolvedSavePopupLabel() {
return this.savePopupLabel || 'Save';
},
},
watch: {
enablePopup(value) {
if (!value) {
this.isPopupOpen = false;
}
},
value: {
handler() {
if (_.isEqual(this.jsValue, this.value)) {
return;
}

this.mode = 'basic';
this.basicValue = '';
this.jsValue = '';

if (!this.value) {
return;
}

this.jsValue = this.value;
},
immediate: true,
},
mode() {
this.emit();
},
jsValue() {
this.emit();
},
basicValue() {
this.emit();
},
},
methods: {
emit() {
this.$emit('input', this.jsValue);
},
openCodePopup() {
this.popupCode = this.jsValue || '';
this.isPopupOpen = true;
},
closeCodePopup() {
this.isPopupOpen = false;
},
onCodePopupHidden() {
this.popupCode = '';
},
saveCodePopup() {
this.jsValue = this.popupCode;
this.isPopupOpen = false;
},
},
};
</script>

<style scoped>
.buttons button {
min-width: 2.2em;
margin-left: 0.5em;
margin-bottom: 0.5em;
}

.editor {
width: 100%;
height: 10em;
}

.popup-editor {
width: 100%;
height: 60vh;
}

.inspector-label-row {
width: 100%;
}
</style>
99 changes: 99 additions & 0 deletions src/components/renderer/vueRender/VueRenderEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<template>
<div class="form-group pointer-events-none">
<div :class="classList">
<VueRenderTemplate
:key="componentRenderKey"
:templateVue="templateVue"
:handlerVue="handlerVue"
:validation-data="validationData || {}"
:transient-data="validationData || {}"
/>
</div>
<div
v-if="error"
class="invalid-feedback"
>
{{ error }}
</div>
<small
v-if="helper"
class="form-text text-muted"
>{{ helper }}</small>
</div>
</template>
<script>
import VueRenderTemplate from './VueRenderTemplate';

export default {
name: 'VueRenderEditor',
components: { VueRenderTemplate },
inheritAttrs: false,
props: {
error: { type: String, default: null },
name: { type: String, default: null },
helper: { type: String, default: null },
controlClass: { type: String, default: null },
templateVue: { type: String, default: null },
handlerVue: { type: String, default: null },
validationData: { type: Object, default: null },
label: { type: String, default: null },
renderVarHtml: { type: Boolean, default: null },
},
data() {
return {
originalEscapeFn: null,
customFunctions: {},
editorSettings: {
inline: true,
menubar: false,
plugins: ['link', 'lists'],
toolbar: `undo redo | link | styleselect | bold italic forecolor |
alignleft aligncenter alignright alignjustify | bullist numlist outdent indent`,
skin: false,
relative_urls: false,
convert_urls: false,
remove_script_host: false,
},
};
},
computed: {
componentRenderKey() {
return [this.templateVue, this.handlerVue].join('::');
},
classList() {
const classList = {
'is-invalid': Boolean(this.error),
};
if (this.controlClass) {
classList[this.controlClass] = true;
}
return classList;
},
rendered() {
// If we have't validationData, we can't evaluate the mustache variables
// Used by ScreenBuilder in Design Mode
return this.templateVue;
},
inPreviewMode() {
return ((this.mode === 'preview' && !window.exampleScreens) || this.mode === 'editor');
},
mode() {
return this.$root.$children[0].mode;
},
},
};
</script>

<style scoped>
.invalid-feedback {
display: block;
}

.is-invalid {
border: 1px solid #dc3545;
border-radius: 0.25rem;
}
.pointer-events-none {
pointer-events: none;
}
</style>
Loading
Loading