Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
238 changes: 238 additions & 0 deletions src/components/inspector/vueRender/EditorFieldInspector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<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',
},
},
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>
36 changes: 36 additions & 0 deletions src/components/renderer/vueRender/VueRenderPreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<template>
<vue-render-template
v-bind="$attrs"
:name="name"
:helper="helper"
:error="error"
:control-class="controlClass"
:templateVue="templateVue"
:handlerVue="handlerVue"
:validation-data="validationData"
:label="label"
:render-var-html="renderVarHtml"
v-on="$listeners"
/>
</template>

<script>
import VueRenderTemplate from './VueRenderTemplate';

export default {
name: 'VueRenderPreview',
components: { VueRenderTemplate },
inheritAttrs: false,
props: {
name: { type: String, default: null },
helper: { type: String, default: null },
error: { 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 },
},
};
</script>
Loading
Loading