This is an automated email from the ASF dual-hosted git repository. machristie pushed a commit to branch airavata-3453 in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git
commit 69190415cd1530518db94e1177f0bab8329a88cf Author: Marcus Christie <[email protected]> AuthorDate: Tue Apr 27 09:41:43 2021 -0400 AIRAVATA-3453 POC: initial supcrtbl2 custom interface with WCs --- django_airavata/apps/workspace/package.json | 4 +- .../static/django_airavata_workspace/.gitignore | 1 + .../js/web-components/ExperimentEditor.vue | 114 +++++++++++++++++++++ .../js/web-components/Foo.vue | 3 + .../js/web-components/store.js | 44 ++++++++ .../django_airavata_workspace/supcrtbl2.html | 80 +++++++++++++++ django_airavata/apps/workspace/views.py | 6 +- django_airavata/apps/workspace/vue.config.js | 15 +-- 8 files changed, 258 insertions(+), 9 deletions(-) diff --git a/django_airavata/apps/workspace/package.json b/django_airavata/apps/workspace/package.json index 8d53566..6d227b1 100644 --- a/django_airavata/apps/workspace/package.json +++ b/django_airavata/apps/workspace/package.json @@ -12,7 +12,9 @@ "test": "npm run test:unit", "test:unit": "vue-cli-service test:unit", "test:unit:watch": "vue-cli-service test:unit --watch", - "format": "prettier --write ." + "format": "prettier --write .", + "wc": "WC_MODE='true' vue-cli-service build --target wc --inline-vue --name adpf './static/django_airavata_workspace/js/web-components/*.vue' --dest ./static/django_airavata_workspace/wc", + "wc:watch": "WC_MODE='true' vue-cli-service build --target wc --inline-vue --name adpf './static/django_airavata_workspace/js/web-components/*.vue' --dest ./static/django_airavata_workspace/wc --watch" }, "dependencies": { "bootstrap": "^4.3.1", diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/.gitignore b/django_airavata/apps/workspace/static/django_airavata_workspace/.gitignore new file mode 100644 index 0000000..ab46eb7 --- /dev/null +++ b/django_airavata/apps/workspace/static/django_airavata_workspace/.gitignore @@ -0,0 +1 @@ +wc diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue new file mode 100644 index 0000000..6b956f5 --- /dev/null +++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue @@ -0,0 +1,114 @@ +<template> + <form v-if="experiment" @input="onInput" @submit.prevent="onSubmit"> + <slot name="experiment-name"> + <input + type="text" + name="experiment-name" + :value="experiment.experimentName" + /> + </slot> + <template v-for="input in experiment.experimentInputs"> + <div :key="input.name"> + <slot :name="input.name"> + {{input.name}} <input v-if="input.type.name == 'STRING'" :name="`input:${input.name}`" :value="input.value"/> + </slot> + <!-- TODO: add support for other input types --> + </div> + </template> + <slot name="save-button"> + <button type="submit" name="save-experiment-button">Save</button> + </slot> + </form> +</template> + +<script> +import { + getApplicationModule, + getApplicationInterfaceForModule, + saveExperiment, + getDefaultProjectId, + getExperiment, +} from "./store"; + +export default { + props: { + applicationId: { + type: String, + required: true, + }, + experimentId: { + type: String, + required: false, + }, + }, + async created() { + this.applicationModule = await getApplicationModule(this.applicationId); + this.appInterface = await getApplicationInterfaceForModule( + this.applicationId + ); + this.experiment = await this.loadExperiment(); + }, + data() { + return { + applicationModule: null, + appInterface: null, + experiment: null, + }; + }, + methods: { + onInput(event) { + console.log(event.target.name, event.target.value); + if (event.target.name === "experiment-name") { + this.experiment.experimentName = event.target.value; + } + if (event.target.name.startsWith("input:")) { + for (const input of this.experiment.experimentInputs) { + if (event.target.name === `input:${input.name}`){ + input.value = event.target.value; + } + } + } + }, + onSubmit(event) { + // console.log(event); + // 'save' event is cancelable. Listener can call .preventDefault() on the event to cancel. + // composed: true allows the shadow DOM event to bubble up through the shadow shadow root. + const saveEvent = new CustomEvent('save', {detail: [this.experiment], cancelable: true, composed: true}); + this.$el.dispatchEvent(saveEvent); + if (saveEvent.defaultPrevented) { + return; + } + if (event.submitter.name === "save-experiment-button") { + this.saveExperiment(); + } else { + // Default submit button handling is save and launch + } + }, + async saveExperiment() { + return await saveExperiment(this.experiment); + }, + async loadExperiment() { + + if (this.experimentId) { + const experiment = await getExperiment(this.experimentId); + this.$emit('loaded', experiment); + return experiment; + } else { + const experiment = this.appInterface.createExperiment(); + experiment.experimentName = + this.applicationModule.appModuleName + + " on " + + new Date().toLocaleString(); + const defaultProjectId = await getDefaultProjectId(); + experiment.projectId = defaultProjectId; + experiment.userConfigurationData.computationalResourceScheduling.resourceHostId = + "bigred3.uits.iu.edu_2141bf96-c458-4ecd-8759-aa3a08f31956"; + this.$emit('loaded', experiment); + return experiment; + } + } + }, +}; +</script> + +<style></style> diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/Foo.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/Foo.vue new file mode 100644 index 0000000..7b8b46c --- /dev/null +++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/Foo.vue @@ -0,0 +1,3 @@ +<template> + <div></div> +</template> diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js new file mode 100644 index 0000000..2f66bfb --- /dev/null +++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js @@ -0,0 +1,44 @@ +import { services } from "django-airavata-api"; +const APPLICATION_MODULES = {}; +const APPLICATION_INTERFACES = {}; +export async function getApplicationModule(applicationId) { + if (applicationId in APPLICATION_MODULES) { + return APPLICATION_MODULES[applicationId]; + } + const result = await services.ApplicationModuleService.retrieve({ + lookup: applicationId, + }); + APPLICATION_MODULES[applicationId] = result; + return result; +} + +export async function getApplicationInterfaceForModule(applicationId) { + if (applicationId in APPLICATION_INTERFACES) { + return APPLICATION_INTERFACES[applicationId]; + } + const result = await services.ApplicationModuleService.getApplicationInterface( + { lookup: applicationId } + ); + APPLICATION_INTERFACES[applicationId] = result; + return result; +} + +export async function saveExperiment(experiment) { + if (experiment.experimentId) { + return await services.ExperimentService.update({ + data: experiment, + lookup: experiment.experimentId, + }); + } else { + return await services.ExperimentService.create({ data: experiment }); + } +} + +export async function getDefaultProjectId() { + const preferences = await services.WorkspacePreferencesService.get(); + return preferences.most_recent_project_id; +} + +export async function getExperiment(experimentId) { + return await services.ExperimentService.retrieve({ lookup: experimentId }); +} diff --git a/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html b/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html new file mode 100644 index 0000000..588fc3b --- /dev/null +++ b/django_airavata/apps/workspace/templates/django_airavata_workspace/supcrtbl2.html @@ -0,0 +1,80 @@ +{% extends 'base.html' %} + +{% load static %} +{% load render_bundle from webpack_loader %} + +{% block css %} +{% comment %} TODO: are chunk-vendors and chunk-common needed? {% endcomment %} +{% comment %} {% render_bundle 'chunk-vendors' 'css' 'WORKSPACE' %} {% endcomment %} +{% comment %} {% render_bundle 'chunk-common' 'css' 'WORKSPACE' %} {% endcomment %} +{% comment %} {% render_bundle 'adpf' 'css' 'WORKSPACE' %} {% endcomment %} +{% endblock %} + +{% block content %} +{% comment %} TODO: maybe use wc- as a prefix? {% endcomment %} +<div class="main-content-wrapper"> + <main class="main-content"> + <div class="container-fluid"> + <h1>SUPCRTBL Online Version 1.0.1</h2> + <adpf-experiment-editor + id="experiment-editor" + application-id="supcrtbl_eb4216b3-fcbf-4422-a70d-27af2550cfb6" + {% if experiment_id %} + experiment-id="{{ experiment_id }}" + {% endif %} > + <div id="solvent" slot="Specify Solvent Phase Region"> + <label for="input:Specify Solvent Phase Region">Specify solvent phase region:</label> + <input type="radio" name="input:Specify Solvent Phase Region" value="0">One-phase region</input> <br/> + <input type="radio" name="input:Specify Solvent Phase Region" value="1">liquid vapor saturation curve</input> + </div> + + <div slot="Input-to-Echo"> + My custom input editor: <input id="myinput" style="background-color: lightgrey;" name="input:Input-to-Echo" value=""/> + </div> + </adpf-experiment-editor> + </div> + </main> +</div> +{% endblock content %} + +{% block scripts %} +{% comment %} {% render_bundle 'chunk-vendors' 'js' 'WORKSPACE' %} {% endcomment %} +<script src="{% static 'django_airavata_workspace/wc/adpf.chunk-vendors.js' %}"></script> +{% comment %} {% render_bundle 'chunk-common' 'js' 'WORKSPACE' %} {% endcomment %} +{% comment %} {% render_bundle 'adpf' 'js' 'WORKSPACE' %} {% endcomment %} +<script src="{% static 'django_airavata_workspace/wc/adpf.min.js' %}"></script> +<script> +document.getElementById("experiment-editor").addEventListener('loaded', e => { + const [experiment] = e.detail; + for (const input of experiment.experimentInputs) { + // This runs before the custom element is registered which is probably why we need setAttribute here + // TODO: just iterate over the slotted inputs + const inputEl = document.querySelector(`[name="input:${input.name}"]`) + if (!inputEl) { + console.warn("Could not find input editor for ", input); + } else { + if (inputEl.type === 'radio') { + const radios = document.querySelectorAll(`[name="input:${input.name}"]`); + for (const radio of radios) { + if (radio.value === input.value) { + radio.checked = true; + break; + } + } + } + inputEl.setAttribute("value", input.value); + } + // document.getElementById("myinput").setAttribute("value", input.value); + } +}); +function validateExperiment(event) { + const [experiment] = event.detail; + // This works, can check the current values of all inputs + if (experiment.experimentInputs[0].value === "Valcustom3") { + console.log('save: preventing default'); + event.preventDefault(); + } +} +document.getElementById('experiment-editor').addEventListener('save', validateExperiment); +</script> +{% endblock %} diff --git a/django_airavata/apps/workspace/views.py b/django_airavata/apps/workspace/views.py index 3f6d336..84cc7e6 100644 --- a/django_airavata/apps/workspace/views.py +++ b/django_airavata/apps/workspace/views.py @@ -114,7 +114,8 @@ def create_experiment(request, app_module_id): context['experiment_data_dir'] = request.GET['experiment-data-dir'] return render(request, - 'django_airavata_workspace/create_experiment.html', + # 'django_airavata_workspace/create_experiment.html', + 'django_airavata_workspace/supcrtbl2.html', context) @@ -123,7 +124,8 @@ def edit_experiment(request, experiment_id): request.active_nav_item = 'experiments' return render(request, - 'django_airavata_workspace/edit_experiment.html', + # 'django_airavata_workspace/edit_experiment.html', + 'django_airavata_workspace/supcrtbl2.html', {'bundle_name': 'edit-experiment', 'experiment_id': experiment_id}) diff --git a/django_airavata/apps/workspace/vue.config.js b/django_airavata/apps/workspace/vue.config.js index 01afb3d..c08ea40 100644 --- a/django_airavata/apps/workspace/vue.config.js +++ b/django_airavata/apps/workspace/vue.config.js @@ -31,12 +31,15 @@ module.exports = { }, }, configureWebpack: { - plugins: [ - new BundleTracker({ - filename: "webpack-stats.json", - path: "./static/django_airavata_workspace/dist/", - }), - ], + plugins: + process.env.WC_MODE !== "true" + ? [ + new BundleTracker({ + filename: "webpack-stats.json", + path: "./static/django_airavata_workspace/dist/", + }), + ] + : [], optimization: { /* * Force creating a vendor bundle so we can load the 'app' and 'vendor'
