mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-02 09:54:37 +08:00
344 lines
12 KiB
Vue
344 lines
12 KiB
Vue
<template>
|
|
<div v-if="form" class="content-pane flexible with-grey-background" data-e2e="e2e-CO-forms-builder">
|
|
<div class="content-header flex items-center mb-4">
|
|
<div class="title-row">
|
|
<h1>
|
|
<span class="w-12" v-if="form.attributes.archived">{{ i18n.t('labels.archived') }}</span>
|
|
<InlineEdit
|
|
v-if="canManage"
|
|
:value="form.attributes.name"
|
|
:characterLimit="255"
|
|
attributeName="name"
|
|
:characterMinLimit="2"
|
|
:allowBlank="false"
|
|
@editingEnabled="editingName = true"
|
|
@editingDisabled="editingName = false"
|
|
@update="updateName"
|
|
/>
|
|
<template v-else>
|
|
{{ form.attributes.name }}
|
|
</template>
|
|
</h1>
|
|
</div>
|
|
<div class="flex items-center gap-4 ml-auto">
|
|
<button v-if="preview && !this.form.attributes.published_on && this.form.attributes.can_manage_form_draft"
|
|
class="btn btn-secondary"
|
|
@click="preview = false"
|
|
data-e2e="e2e-BT-forms-builder-editForm">
|
|
<i class="sn-icon sn-icon-edit"></i>
|
|
{{ i18n.t('forms.show.edit_form') }}
|
|
</button>
|
|
<button v-if="!preview"
|
|
class="btn btn-secondary"
|
|
@click="preview = true"
|
|
data-e2e="e2e-BT-forms-builder-testForm">
|
|
<i class="sn-icon sn-icon-visibility-show"></i>
|
|
{{ i18n.t('forms.show.test_form') }}
|
|
</button>
|
|
<button v-if="form.attributes.urls.publish"
|
|
class="btn btn-primary"
|
|
@click="publishForm"
|
|
:disabled="submitting || !isValid"
|
|
data-e2e="e2e-BT-forms-builder-publish">
|
|
{{ i18n.t('forms.show.publish') }}
|
|
</button>
|
|
<button v-if="form.attributes.published_on"
|
|
:disabled="!form.attributes.urls.unpublish || submitting"
|
|
class="btn btn-secondary"
|
|
@click="unpublishForm"
|
|
data-e2e="e2e-BT-forms-builder-unpublish">
|
|
{{ i18n.t('forms.show.unpublish') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="content-body">
|
|
<Preview v-if="preview" :form="form" :fields="savedFields" />
|
|
<div v-else class="bg-white rounded-xl grid grid-cols-[360px_auto] min-h-[calc(100vh_-_200px)]">
|
|
<div class="p-6 border-transparent border-r-sn-sleepy-grey border-solid border-r">
|
|
<h3 class="m-0">{{ i18n.t('forms.show.build_form') }}</h3>
|
|
<div class="my-3 flex flex-col gap-3">
|
|
<Draggable
|
|
v-model="fields"
|
|
class="flex flex-col gap-3"
|
|
handle=".element-grip"
|
|
item-key="id"
|
|
@end="saveOrder"
|
|
>
|
|
<template #item="{element, index}">
|
|
<div class="flex items-center relative group -ml-6 w-[calc(100%_+_24px)]">
|
|
<i class="sn-icon sn-icon-drag text-sn-grey element-grip cursor-grab opacity-0 group-hover:opacity-100"></i>
|
|
<div @click="activeField = element"
|
|
:key="element.id"
|
|
class="font-bold relative grow p-2 rounded-lg overflow-hidden flex items-center gap-2 border-sn-grey-100 cursor-pointer border"
|
|
:class="{ '!border-sn-blue bg-sn-super-light-blue': activeField.id === element.id }"
|
|
>
|
|
<i class="sn-icon rounded text-sn-blue bg-sn-super-light-blue p-1" :class="fieldIcon[element.attributes.data.type]"></i>
|
|
<span :title="element.attributes.name" class="truncate">{{ element.attributes.name }}</span>
|
|
<i v-if="element.isValid == false" class="ml-auto text-sn-delete-red sn-icon sn-icon-alert-warning"></i>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Draggable>
|
|
</div>
|
|
<GeneralDropdown ref="addFieldDropdown">
|
|
<template v-slot:field>
|
|
<button class="btn btn-secondary w-full" data-e2e="e2e-DD-forms-builder-addBlock">
|
|
<i class="sn-icon sn-icon-new-task"></i>
|
|
{{ i18n.t('forms.show.add_block') }}
|
|
</button>
|
|
</template>
|
|
<template v-slot:flyout>
|
|
<div class="flex flex-col -m-2.5 py-2.5 max-h-[inherit]">
|
|
<div class="overflow-y-auto">
|
|
<div v-for="e in newFields" :key="e.type" @click="addField(e.type)" class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer flex items-center gap-2">
|
|
<i class="sn-icon rounded text-sn-blue bg-sn-super-light-blue p-1" :class="fieldIcon[e.type]"></i>
|
|
{{ e.name }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</GeneralDropdown>
|
|
</div>
|
|
<div class="p-6">
|
|
<EditField
|
|
:key="activeField.id"
|
|
v-if="activeField.id"
|
|
ref="editField"
|
|
:field="activeField"
|
|
:icon="fieldIcon[activeField.attributes.data.type]"
|
|
@update="updateField"
|
|
@delete="deleteField"
|
|
@duplicate="duplicateField"
|
|
@validChanged="isValidChanged"
|
|
/>
|
|
<div v-if="!activeField.id"
|
|
class="text-xl font-semibold text-sn-grey font-inter flex items-center justify-center w-full h-full">
|
|
{{ i18n.t('forms.show.no_block') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<ConfirmationModal
|
|
:title="i18n.t('forms.publish.title')"
|
|
:description="i18n.t('forms.publish.description_html')"
|
|
confirmClass="btn btn-primary"
|
|
:confirmText="i18n.t('forms.show.publish')"
|
|
ref="publishModal">
|
|
</ConfirmationModal>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
|
|
import Draggable from 'vuedraggable';
|
|
import InlineEdit from '../shared/inline_edit.vue';
|
|
import axios from '../../packs/custom_axios.js';
|
|
import GeneralDropdown from '../shared/general_dropdown.vue';
|
|
import EditField from './edit_field.vue';
|
|
import Preview from './preview.vue';
|
|
import ConfirmationModal from '../shared/confirmation_modal.vue';
|
|
|
|
export default {
|
|
name: 'ShowForm',
|
|
props: {
|
|
formUrl: String
|
|
},
|
|
components: {
|
|
InlineEdit,
|
|
GeneralDropdown,
|
|
EditField,
|
|
Draggable,
|
|
Preview,
|
|
ConfirmationModal
|
|
},
|
|
computed: {
|
|
canManage() {
|
|
return !this.form.attributes.published_on;
|
|
},
|
|
newFields() {
|
|
return [
|
|
{ name: this.i18n.t('forms.show.blocks.TextField'), type: 'TextField' },
|
|
{ name: this.i18n.t('forms.show.blocks.NumberField'), type: 'NumberField' },
|
|
{ name: this.i18n.t('forms.show.blocks.SingleChoiceField'), type: 'SingleChoiceField' },
|
|
{ name: this.i18n.t('forms.show.blocks.MultipleChoiceField'), type: 'MultipleChoiceField' },
|
|
{ name: this.i18n.t('forms.show.blocks.DatetimeField'), type: 'DatetimeField' },
|
|
{ name: this.i18n.t('forms.show.blocks.ActionField'), type: 'ActionField' },
|
|
{ name: this.i18n.t('forms.show.blocks.RepositoryRowsField'), type: 'RepositoryRowsField' }
|
|
];
|
|
},
|
|
fieldIcon() {
|
|
return {
|
|
TextField: 'sn-icon-result-text',
|
|
NumberField: 'sn-icon-value',
|
|
SingleChoiceField: 'sn-icon-choice-single',
|
|
MultipleChoiceField: 'sn-icon-choice-multiple',
|
|
DatetimeField: 'sn-icon-created',
|
|
ActionField: 'sn-icon-check',
|
|
RepositoryRowsField: 'sn-icon-inventory'
|
|
};
|
|
}
|
|
},
|
|
created() {
|
|
this.loadForm();
|
|
},
|
|
data() {
|
|
return {
|
|
form: null,
|
|
fields: [],
|
|
savedFields: [],
|
|
activeField: {},
|
|
preview: false,
|
|
submitting: false,
|
|
isValid: false
|
|
};
|
|
},
|
|
methods: {
|
|
isValidChanged() {
|
|
this.$nextTick(() => {
|
|
if (this.$refs.editField?.field) {
|
|
const index = this.fields.findIndex((f) => f.id === this.$refs.editField.field.id);
|
|
this.fields[index].isValid = this.$refs.editField.isValid;
|
|
}
|
|
|
|
this.isValid = !this.fields.some((field) => field.isValid === false);
|
|
});
|
|
},
|
|
syncSavedFields() {
|
|
this.savedFields = this.fields.map((f) => ({ ...f }));
|
|
},
|
|
loadForm() {
|
|
axios.get(this.formUrl).then((response) => {
|
|
this.form = response.data.data;
|
|
this.fields = response.data.included || [];
|
|
this.fields = this.fields.sort((a, b) => a.attributes.position - b.attributes.position);
|
|
if (this.fields.length > 0) {
|
|
[this.activeField] = this.fields;
|
|
}
|
|
this.syncSavedFields();
|
|
this.isValidChanged();
|
|
|
|
if (this.form.attributes.published_on || !this.form.attributes.urls.create_field) {
|
|
this.preview = true;
|
|
}
|
|
});
|
|
},
|
|
addField(type) {
|
|
if (this.submitting) {
|
|
return;
|
|
}
|
|
|
|
this.submitting = true;
|
|
axios.post(this.form.attributes.urls.create_field, {
|
|
form_field: {
|
|
name: this.i18n.t(`forms.show.blocks.${type}`),
|
|
data: {
|
|
type
|
|
}
|
|
}
|
|
}).then((response) => {
|
|
this.fields.push(response.data.data);
|
|
this.activeField = this.fields[this.fields.length - 1];
|
|
this.$refs.addFieldDropdown.isOpen = false;
|
|
this.syncSavedFields();
|
|
}).then(() => {
|
|
this.submitting = false;
|
|
});
|
|
},
|
|
updateName(name) {
|
|
axios.put(this.formUrl, {
|
|
form: {
|
|
name
|
|
}
|
|
}).then(() => {
|
|
this.form.attributes.name = name;
|
|
});
|
|
},
|
|
updateField(field) {
|
|
const index = this.fields.findIndex((f) => f.id === field.id);
|
|
axios.put(field.attributes.urls.show, {
|
|
form_field: field.attributes
|
|
}).then((response) => {
|
|
this.fields.splice(index, 1, response.data.data);
|
|
this.syncSavedFields();
|
|
});
|
|
},
|
|
saveOrder() {
|
|
axios.post(this.form.attributes.urls.reorder_fields, {
|
|
form_field_positions: this.fields.map((f, i) => ({ id: f.id, position: i }))
|
|
}).then(() => {
|
|
this.syncSavedFields();
|
|
});
|
|
},
|
|
duplicateField(field) {
|
|
if (this.submitting) {
|
|
return;
|
|
}
|
|
|
|
this.submitting = true;
|
|
axios.post(field.attributes.urls.duplicate, {
|
|
form_field_id: field.id
|
|
}).then((response) => {
|
|
const index = this.fields.findIndex((f) => f.id === field.id);
|
|
this.fields.splice(index + 1, 0, response.data.data);
|
|
this.activeField = response.data.data;
|
|
this.syncSavedFields();
|
|
}).finally(() => {
|
|
this.submitting = false;
|
|
});
|
|
},
|
|
deleteField(field) {
|
|
if (this.submitting) {
|
|
return;
|
|
}
|
|
|
|
this.submitting = true;
|
|
|
|
const index = this.fields.findIndex((f) => f.id === field.id);
|
|
axios.delete(field.attributes.urls.show).then(() => {
|
|
this.fields.splice(index, 1);
|
|
if (this.fields.length > 0) {
|
|
if (index === 0) {
|
|
[this.activeField] = this.fields;
|
|
} else {
|
|
this.activeField = this.fields[index - 1];
|
|
}
|
|
} else {
|
|
this.activeField = {};
|
|
}
|
|
this.syncSavedFields();
|
|
}).finally(() => {
|
|
this.submitting = false;
|
|
});
|
|
},
|
|
async publishForm() {
|
|
const ok = await this.$refs.publishModal.show();
|
|
if (ok) {
|
|
if (this.submitting) {
|
|
return;
|
|
}
|
|
|
|
this.submitting = true;
|
|
axios.post(this.form.attributes.urls.publish).then((response) => {
|
|
this.form = response.data.data;
|
|
this.preview = true;
|
|
}).finally(() => {
|
|
this.submitting = false;
|
|
});
|
|
}
|
|
},
|
|
unpublishForm() {
|
|
if (this.submitting) {
|
|
return;
|
|
}
|
|
|
|
this.submitting = true;
|
|
axios.post(this.form.attributes.urls.unpublish).then((response) => {
|
|
this.form = response.data.data;
|
|
this.preview = false;
|
|
}).finally(() => {
|
|
this.submitting = false;
|
|
});
|
|
}
|
|
}
|
|
};
|
|
</script>
|