Source: management-system/src/frontend/views/EditorExplorer.vue

<template>
  <v-btn v-if="pullingProcess" class="loading-message" :loading="pullingProcess" text color="green">
    <template v-slot:loader>
      <span>Pulling requested process from server...</span>
    </template>
  </v-btn>
  <div v-else class="editor-explorer">
    <v-tabs v-model="currentTab" show-arrows="mobile" class="flex-grow-0">
      <v-btn class="ml-2 mt-2" fab x-small color="primary" @click="returnToOverview()">
        <v-icon> mdi-arrow-left </v-icon>
      </v-btn>
      <ProcessTab
        v-for="(tab, index) in tabs"
        :key="index"
        :processDefinitionsId="tab.processDefinitionsId"
        :subprocessId="tab.subprocessId"
        @addTab="addTab($event.processDefinitionsId)"
        @deleteTab="deleteTab(tab)"
      />
      <v-btn class="d-flex align-self-center" icon center @click="showNewTabDialog = true">
        <v-icon>mdi-plus</v-icon>
      </v-btn>
      <v-btn class="d-flex align-self-center" plain text center @click="tabs = [tabs[currentTab]]">
        Clear tab bar
      </v-btn>
    </v-tabs>

    <ProcessModal
      callToActionText="Open in new tab"
      :show="showNewTabDialog"
      maxWidth="800px"
      @cancel="showNewTabDialog = false"
      @click:bpmnPreview="addTab"
    ></ProcessModal>

    <AlertWindow :popupData="popupData" />

    <EditProcessBpmn
      v-if="tabs[currentTab] && tabs[currentTab].processDefinitionsId"
      :processDefinitionsId="tabs[currentTab].processDefinitionsId"
      :subprocessId="tabs[currentTab].subprocessId"
      @addTab="addTab"
      @inactivityTimeout="returnToOverview()"
    ></EditProcessBpmn>
  </div>
</template>

<script>
import EditProcessBpmn from '@/frontend/views/ProcessBpmnEditor.vue';
import ProcessModal from '@/frontend/components/processes/editor/ProcessModal.vue';
import ProcessTab from '@/frontend/components/processes/editor/ProcessTab.vue';
import AlertWindow from '@/frontend/components/universal/Alert.vue';
import { eventHandler, processInterface } from '@/frontend/backend-api/index.js';

/**
 * @module views
 */
/**
 * @memberof module:views
 * @module Vue:EditorExplorer
 */
export default {
  name: 'edit-explorer',
  components: {
    ProcessTab,
    ProcessModal,
    EditProcessBpmn,
    AlertWindow,
  },
  data() {
    return {
      /**
       * Indicates the current tab
       * @type {number}
       */
      currentTab: 0,
      /** */
      tabs: [],
      /** */
      showNewTabDialog: false,
      /** */
      previousTab: 0,
      /** */
      popupData: {
        body: '',
        display: 'none',
        color: 'warning',
      },
      processRemovedCallback: null,
      pullingProcess: false,
    };
  },
  computed: {},
  watch: {
    currentTab() {
      if (this.tabs[this.previousTab]) {
        this.signalEditingEnd(this.tabs[this.previousTab].processDefinitionsId);
      }
      if (this.tabs[this.currentTab]) {
        this.signalEditing(this.tabs[this.currentTab].processDefinitionsId);
        this.previousTab = this.currentTab;
      }

      const newPath = this.$router.currentRoute.path.replace(
        this.$router.currentRoute.params.id,
        this.tabs[this.currentTab].processDefinitionsId
      );
      history.replaceState({}, '', '/#' + newPath);
    },
  },
  methods: {
    async addTab(processDefinitionsId, subprocessId) {
      // lookup if the tab which should be added already exists
      const selectedTab = this.tabs.find(
        (tab) =>
          tab.processDefinitionsId === processDefinitionsId && tab.subprocessId === subprocessId
      );
      if (selectedTab) {
        this.currentTab = this.tabs.indexOf(selectedTab);
        this.showNewTabDialog = false;
        return;
      }

      const newTab = {
        processDefinitionsId,
        subprocessId,
      };

      // to insert the new process tab infront of potential subprocesses tabs
      const subprocessTabIndex = this.tabs.findIndex(
        (tab) =>
          tab.processDefinitionsId === processDefinitionsId && tab.subprocessId && !subprocessId
      );
      if (subprocessTabIndex !== -1) {
        this.tabs.splice(subprocessTabIndex, 0, newTab);
      } else {
        this.tabs.push(newTab);
      }

      // switch to the added tab
      this.currentTab = this.tabs.findIndex(
        (tab) =>
          tab.processDefinitionsId === processDefinitionsId && tab.subprocessId === subprocessId
      );

      this.showNewTabDialog = false;
    },
    /** */
    deleteTab(tab) {
      const index = this.tabs.indexOf(tab);
      if (tab.processDefinitionsId) {
        this.signalEditingEnd(tab.processDefinitionsId);
      }
      this.tabs.splice(index, 1);
      if (this.tabs.length === 0) {
        this.returnToOverview();
      }
    },
    // Tries to pull the process that is supposed to be modeled from the backend
    async pullProcess(id) {
      try {
        const process = await processInterface.pullProcess(id);
        const bpmn = process.bpmn;
        delete process.bpmn;
        await this.$store.dispatch('processStore/add', { process, bpmn });
      } catch (err) {
        throw new Error(`Could not find process with id ${id}`);
      }
    },
    signalEditing(definitionsId) {
      this.$store.dispatch('processStore/startEditing', {
        id: definitionsId,
      });
      this.$store.dispatch('processStore/startObservingEditing', {
        id: definitionsId,
      });
    },
    signalEditingEnd(definitionsId) {
      this.$store.dispatch('processStore/stopEditing', {
        id: definitionsId,
      });
      this.$store.dispatch('processStore/stopObservingEditing', {
        id: definitionsId,
      });
    },
    returnToOverview() {
      if (this.$router.currentRoute.name === 'edit-project-bpmn') {
        const projectStatusPath = this.$router.currentRoute.path.replace('bpmn', 'status');
        this.$router.push({ path: projectStatusPath });
      } else {
        const [processesOverviewPath] = this.$router.currentRoute.path.split('/bpmn', 1);
        this.$router.push({ path: processesOverviewPath });
      }
    },
  },
  mounted() {
    this.processRemovedCallback = eventHandler.on('processRemoved', ({ processDefinitionsId }) => {
      const deletedTab = this.tabs.find((tab) => tab.processDefinitionsId === processDefinitionsId);
      if (deletedTab) {
        this.deleteTab(deletedTab);
        this.popupData.body = 'A process was deleted so its tab was automatically removed';
        this.popupData.display = 'block';
      }
    });
  },
  async beforeMount() {
    const routerProcessDefinitionsId = this.$router.currentRoute.params.id;

    if (routerProcessDefinitionsId) {
      // check if the process is locally available
      if (!this.$store.getters['processStore/processById'](routerProcessDefinitionsId)) {
        // if not try pulling it from the backend
        this.pullingProcess = true;
        await this.pullProcess(routerProcessDefinitionsId);
        this.pullingProcess = false;
      }
      this.addTab(routerProcessDefinitionsId);
      this.signalEditing(routerProcessDefinitionsId);
    }
  },
  beforeRouteLeave(to, from, next) {
    if (this.processRemovedCallback) {
      eventHandler.off('processRemoved', this.processRemovedCallback);
    }
    if (this.tabs[this.currentTab]) {
      this.signalEditingEnd(this.tabs[this.currentTab].processDefinitionsId);
    }
    next();
  },
};
</script>

<style>
.editor-explorer {
  display: flex;
  flex: 1;
  flex-direction: column;
  height: 100%;
}
.loading-message {
  position: absolute;
  bottom: 50%;
  left: 50%;
  right: 50%;
}
</style>