Source: management-system/src/frontend/components/processes/ProcessViewsBaseLayout.vue

<template>
  <div>
    <v-toolbar>
      <v-toolbar-title>{{ title }}</v-toolbar-title>
      <!-- List or Card view buttons -->
      <v-btn-toggle v-model="processViewMode" class="ml-3" color="primary" mandatory dense group>
        <v-btn value="table">
          <v-icon>mdi-format-align-justify</v-icon>
        </v-btn>
        <v-btn value="card">
          <v-icon>mdi-apps</v-icon>
        </v-btn>
      </v-btn-toggle>
      <v-spacer />

      <!-- Import and Add button -->
      <v-btn @click.stop="showProcessForm('Import')" v-if="$can('create', 'Process')">
        <v-icon> mdi-upload </v-icon>
      </v-btn>
      <v-btn
        color="primary"
        @click.stop="showProcessForm('Add')"
        v-if="$can('create', 'Process')"
        >{{ addText }}</v-btn
      >
    </v-toolbar>

    <slot name="before-table"></slot>

    <v-row>
      <v-col
        :cols="
          $useAuthorization && isAuthenticated && expanded[0] && $can('share', expanded[0]) ? 8 : 12
        "
        :class="
          $useAuthorization && isAuthenticated && expanded[0] && $can('share', expanded[0])
            ? 'pr-0'
            : ''
        "
      >
        <v-container fluid>
          <v-row row wrap justify-center id="wrapper">
            <v-col class="text-center centered">
              <v-card>
                <v-card-title>
                  <!-- Drop Down menu "Select Action" -->
                  <v-menu :disabled="!selected.length" offset-y>
                    <template v-slot:activator="{ on }">
                      <v-btn color="primary" :disabled="!selected.length" v-on="on">
                        <v-icon left>mdi-menu-down</v-icon>Select Action
                      </v-btn>
                    </template>
                    <v-list>
                      <v-list-item @click="expandSelected">
                        <v-list-item-title>
                          <v-icon left>mdi-arrow-expand-vertical</v-icon>Expand
                        </v-list-item-title>
                      </v-list-item>
                      <v-list-item @click="collapseSelected">
                        <v-list-item-title>
                          <v-icon left>mdi-arrow-collapse-vertical</v-icon>Collapse
                        </v-list-item-title>
                      </v-list-item>
                      <v-list-item v-if="selected.length === 1" disabled>
                        <v-list-item-title>
                          <v-icon left>mdi-checkbox-outline</v-icon>Deploy
                        </v-list-item-title>
                      </v-list-item>
                      <v-list-item @click="openExportDialog">
                        <v-list-item-title>
                          <v-icon left>mdi-export</v-icon>Export
                        </v-list-item-title>
                      </v-list-item>
                      <v-list-item
                        v-if="selected.length === 1"
                        :disabled="!canEditProcesses"
                        @click="handleEditProcessRequest(selected[0])"
                      >
                        <v-list-item-title>
                          <v-icon left>mdi-pencil</v-icon>Edit
                        </v-list-item-title>
                      </v-list-item>
                      <v-list-item @click="handleCopyProcessRequest(selected)">
                        <v-list-item-title>
                          <v-icon left>mdi-content-copy</v-icon>Copy
                        </v-list-item-title>
                      </v-list-item>
                      <v-list-item
                        @click="removeSelectedProcessesDialog = true"
                        :disabled="!canDeleteProcesses"
                      >
                        <v-list-item-title>
                          <v-icon left>mdi-delete</v-icon>Delete
                        </v-list-item-title>
                      </v-list-item>
                    </v-list>
                  </v-menu>
                  <v-spacer></v-spacer>
                  <v-text-field
                    class="ma-0"
                    v-model="search"
                    append-icon="mdi-magnify"
                    label="Search"
                    single-line
                    hide-details
                    :disabled="!typeProcesses || typeProcesses.length === 0"
                    clearable
                  ></v-text-field>
                </v-card-title>
                <v-divider />
                <ProcessCardList
                  v-if="processViewMode === 'card'"
                  :processes="mappedProcesses"
                  :search.sync="search"
                  :selected.sync="selected"
                  :expanded.sync="expanded"
                  :showFavoriteList.sync="showFavoriteList"
                  :callToActionText="callToAction"
                  @handleFavorite="handleFavorite"
                  @createProcessHierarchy="handleProcessHierarchy"
                  @editBpmn="openProcess"
                  @editProcessRequest="handleEditProcessRequest"
                  @deleteProcessRequest="handleDeleteProcessRequest"
                  @exportProcess="exportProcess"
                />
                <ProcessDataTable
                  v-else-if="processViewMode === 'table'"
                  :processes="mappedProcesses"
                  :search.sync="search"
                  :selected.sync="selected"
                  :expanded.sync="expanded"
                  :showFavoriteList.sync="showFavoriteList"
                  :isProjectMode="processType === 'project'"
                  :callToActionText="callToAction"
                  @handleFavorite="handleFavorite"
                  @createProcessHierarchy="handleProcessHierarchy"
                  @editBpmn="openProcess"
                  @editProcessRequest="handleEditProcessRequest"
                  @deleteProcessRequest="handleDeleteProcessRequest"
                  @exportProcess="exportProcess"
                />
              </v-card>
            </v-col>
          </v-row>
          <DepartmentsList @changeSearch="changeSearchItem"></DepartmentsList>
        </v-container>
      </v-col>
      <v-col
        v-if="$useAuthorization && isAuthenticated && expanded[0] && $can('share', expanded[0])"
        cols="4"
        class="pl-0"
      >
        <v-container fluid class="pl-0">
          <ShareProcessPanel :expanded.sync="expanded" />
        </v-container>
      </v-col>
    </v-row>

    <!-- Delete dialog -->
    <confirmation
      :title="deleteConfirmationTitle"
      :continueButtonText="`Delete ${
        selected.length > 1 ? `${selected.length} ${this.title}` : this.upperCaseType
      }`"
      continueButtonColor="error"
      :show="removeSelectedProcessesDialog"
      maxWidth="500px"
      @cancel="removeSelectedProcessesDialog = false"
      @continue="deleteSelectedProcesses"
    >
      <div v-if="selected.length === 1">
        You are about to delete the {{ processType }} <b>{{ selected[0].name }}</b> and all of the
        subprocesses!
      </div>
      <div v-else>
        You are about to delete <b>{{ selected.length }} {{ title.toLowerCase() }}</b> and all of
        their subprocesses:
        <ul style="list-style-position: inside">
          <li v-for="process of selected" :key="process.id">
            {{ process.name }}
          </li>
        </ul>
      </div>
    </confirmation>

    <!-- Delete dialog -->
    <confirmation
      :title="forceDeleteConfirmationTitle"
      :continueButtonText="`Delete ${
        selected.length > 1 ? `${selected.length} ${this.title}` : this.upperCaseType
      }`"
      continueButtonColor="error"
      :show="removeBlockedProcessesDialog"
      maxWidth="500px"
      @cancel="removeBlockedProcessesDialog = false"
      @continue="deleteSelectedProcesses"
    >
      <div v-if="selected.length === 1">
        You are about to delete the {{ processType }} <b>{{ selected[0].name }}</b
        >!
      </div>
      <div v-else>
        You are about to delete <b>{{ selected.length }} {{ title.toLowerCase() }}</b
        >:
        <ul style="list-style-position: inside">
          <li v-for="process of selected" :key="process.id">
            {{ process.name }}
          </li>
        </ul>
      </div>
    </confirmation>

    <add-form
      :type="processType"
      :show="isAddFormShown"
      @cancel="removeProcessForm('Add')"
      @process="openInEditor"
      @done="removeProcessForm('Add')"
    />

    <edit-form
      :type="processType"
      :processToBeEdited="processToBeEditedProp"
      :show="isEditFormShown"
      @cancel="removeProcessForm('Edit')"
      @done="removeProcessForm('Edit')"
    />

    <import-form
      :type="processType"
      :show="isImportFormShown"
      @cancel="removeProcessForm('Import')"
      @process="openInEditor"
      @done="removeProcessForm('Import')"
    />

    <copy-form
      :processesToBeCopied="processesToBeCopied"
      :type="processType"
      :show="isCopyFormShown"
      @cancel="removeProcessForm('Copy')"
      @done="removeProcessForm('Copy')"
      @process="openInEditor"
    />

    <ExportModal
      :loading="exportRunning"
      :title="`Export selected ${selected.length > 1 ? title.toLowerCase() : processType}`"
      :text="selectionText"
      :error="errorSelectionText"
      :max="max"
      :show="exportSelectedProcessesDialog"
      @cancel="exportSelectedProcessesDialog = false"
      @continue="exportSelected"
    />

    <SubprocessesModal
      :process="processForSubprocessModal"
      :show="isSubprocessesModalVisible"
      @cancel="isSubprocessesModalVisible = false"
      @editBpmn="openProcess($event.process)"
    />

    <slot name="after-table"></slot>

    <local-process-modal
      :processType="title"
      :processes="typeProcesses"
      v-if="$can('create', 'Process')"
    ></local-process-modal>
  </div>
</template>
<script>
import ImportForm from '@/frontend/components/processes/processForm/ImportProcessForm.vue';
import AddForm from '@/frontend/components/processes/processForm/AddProcessForm.vue';
import EditForm from '@/frontend/components/processes/processForm/EditProcessForm.vue';
import CopyForm from '@/frontend/components/processes/processForm/CopyProcessForm.vue';
import ExportModal from '@/frontend/components/processes/ExportModal.vue';
import SubprocessesModal from '@/frontend/components/processes/subprocesses/SubprocessesModal.vue';
import Confirmation from '@/frontend/components/universal/Confirmation.vue';
import DepartmentsList from '@/frontend/components/departments/DepartmentsList.vue';
import ProcessCardList from '@/frontend/components/processes/cards/ProcessCardList.vue';
import ProcessDataTable from '@/frontend/components/processes/datatable/ProcessDatatable.vue';
import { exportSelectedProcesses } from '@/frontend/helpers/process-export/process-export.js';
import { getMaximumResolution } from '@/frontend/helpers/process-export/process-max-resolution.js';
import { subject } from '@casl/ability';
import { mapGetters } from 'vuex';
import ShareProcessPanel from '@/frontend/components/processes/ShareProcessPanel.vue';
import LocalProcessModal from './LocalProcessModal.vue';

/**
 * @module views
 */
/**
 * @memberof module:views
 * @module Vue:Process
 *
 * @vue-computed showFavoriteList
 * @vue-computed processViewMode
 * @vue-computed departments
 * @vue-computed favorites
 * @vue-computed processes
 * @vue-computed mappedProcesses
 * @vue-computed deleteConfirmationTitle
 * @vue-computed forceDeleteConfirmationTitle
 */
export default {
  props: {
    processType: {
      type: String,
      required: false,
      default: 'process',
    },
    title: {
      type: String,
      required: false,
      default: 'Processes',
    },
    addText: {
      type: String,
      required: false,
      default: 'Add',
    },
    callToAction: {
      type: String,
      required: false,
      default: 'Open Editor',
    },
  },

  components: {
    Confirmation,
    DepartmentsList,
    ProcessCardList,
    ProcessDataTable,
    ExportModal,
    SubprocessesModal,
    ImportForm,
    AddForm,
    EditForm,
    CopyForm,
    ShareProcessPanel,
    LocalProcessModal,
  },

  computed: {
    showFavoriteList: {
      get() {
        return this.$store.getters['userPreferencesStore/getShowFavoritesProcessView'];
      },
      set(newValue) {
        this.$store.dispatch('userPreferencesStore/setShowFavoritesProcessView', newValue);
        this.resetSelectionAndExpansion();
      },
    },
    ...mapGetters({
      isAuthenticated: 'authStore/isAuthenticated',
    }),
    canDeleteProcesses() {
      return this.selected.every((process) => this.$can('delete', process));
    },
    canEditProcesses() {
      return this.selected.every((process) => this.$can('update', process));
    },
    processViewMode: {
      get() {
        return this.$store.getters['userPreferencesStore/getSelectedProcessView'];
      },
      set(newValue) {
        this.$store.dispatch('userPreferencesStore/setSelectedProcessView', newValue);
      },
    },
    departments() {
      return this.$store.getters['departmentStore/getDepartments'];
    },
    favorites() {
      return this.$store.getters['userPreferencesStore/getUserFavorite'];
    },
    processes() {
      return this.$store.getters['processStore/processes'];
    },
    typeProcesses() {
      return this.processes.filter((p) => p.type === this.processType);
    },
    visibleProcesses() {
      if (this.$store.getters['authStore/isAuthenticated']) {
        return this.typeProcesses.filter((p) => p.owner || p.shared);
      } else {
        return this.typeProcesses;
      }
    },
    mappedProcesses() {
      if (!this.visibleProcesses) return [];

      let processes = this.visibleProcesses.map((process) => {
        const type = process.type[0].toUpperCase() + process.type.slice(1);
        return subject(type, {
          ...process,
          departments: process.departments
            ? process.departments.map((department) => {
                return {
                  name: department,
                  color: this.getDepartmentColor(department),
                };
              })
            : '',
          isFavorite: this.isFavorite(process.id),
        });
      });

      if (this.showFavoriteList) {
        processes = processes.filter((process) => process.isFavorite);
      }

      return processes;
    },
    deleteConfirmationTitle() {
      return `delete the selected ${
        this.selected.length === 1 ? this.processType : this.title.toLowerCase()
      }?`;
    },
    forceDeleteConfirmationTitle() {
      return `delete the selected ${
        this.selected.length === 1
          ? `${this.processType}? This ${this.processType} is`
          : `${this.title.toLowerCase()}? One or more of the ${this.title.toLowerCase()} are`
      } being edited by someone else`;
    },
  },
  data() {
    return {
      upperCaseType: `${this.processType[0].toUpperCase()}${this.processType.slice(1)}`,

      /** see ProcessForm.vue: add, import, edit, select */
      isImportFormShown: false,
      isAddFormShown: false,
      isEditFormShown: false,
      isCopyFormShown: false,
      /** */
      removeSelectedProcessesDialog: false,
      /** */
      removeBlockedProcessesDialog: false,
      /** */
      confirmationText: 'Do you want to continue?',
      /** */
      selectionText: 'Please select a format for the file export',
      /** */
      errorSelectionText: 'Please select a format',
      /** */
      processToBeEditedProp: undefined,
      /** */
      processesToBeCopied: [],
      /** */
      search: '',
      /** */
      selected: [],
      /** */
      expanded: [],
      /** */
      exportRunning: false,
      /** */
      exportSelectedProcessesDialog: false,
      /** */
      isSubprocessesModalVisible: false,
      /** */
      processForSubprocessModal: null,
      /** */
      max: 10,
    };
  },
  watch: {
    search() {
      this.resetSelectionAndExpansion();
    },
    processViewMode() {
      this.resetSelectionAndExpansion();
    },
    isProcessFormShown(isShown) {
      // make sure to reset the form when user closed it by clicking outside it
      if (!isShown) {
        this.processToBeEditedProp = undefined;
        this.processesToBeCopied = [];
      }
    },
  },
  methods: {
    /** */
    resetSelectionAndExpansion() {
      this.selected = [];
      this.expanded = [];
    },
    showProcessForm(processFormType) {
      this[`is${processFormType}FormShown`] = true;
    },
    /** */
    handleEditProcessRequest(process) {
      this.processToBeEditedProp = this.typeProcesses.find((pro) => pro.id === process.id);
      this.showProcessForm('Edit');
    },
    handleCopyProcessRequest(processes) {
      this.processesToBeCopied = processes;
      this.showProcessForm('Copy');
    },
    removeProcessForm(processFormType) {
      this.processToBeEditedProp = undefined;
      this[`is${processFormType}FormShown`] = false;
    },

    /** */
    async exportProcess(process) {
      this.selected = this.typeProcesses.filter((pro) => pro.id === process.id);
      this.max = await getMaximumResolution(this.selected);
      this.exportSelectedProcessesDialog = true;
    },
    /**
     * Expand selected Processes in the Process Data Table
     */
    expandSelected() {
      this.expanded = this.expanded
        .concat(this.selected)
        .filter((value, index, self) => self.indexOf(value) === index);
    },
    /**
     * Collapse selected expanded Processes in the Process Data Table
     */
    collapseSelected() {
      this.expanded = this.expanded.filter((process) => !this.selected.includes(process));
    },
    /**
     * Add or Delete ProcessDefinitionsId from Favorite in userPreferencesStore
     */
    handleFavorite(process) {
      if (process.isFavorite) {
        this.$store.dispatch('userPreferencesStore/deleteProcessFromFavoritesById', process.id);
      } else {
        this.$store.dispatch('userPreferencesStore/addNewProcessToFavorites', process.id);
      }
    },
    /**
     * return true if processDefinitionsId is in Favorites
     */
    isFavorite(processDefinitionsId) {
      return this.favorites.includes(processDefinitionsId);
    },
    /** */
    changeSearchItem(searchItem) {
      this.search = searchItem;
    },
    /** */
    getDepartmentColor(departmentName) {
      return this.departments
        .filter((department) => department.name === departmentName)
        .map((department) => department.color)
        .pop();
    },
    /** Handles requests to open a process */
    openInEditor(process) {
      if (process) this.openProcess(process);
    },
    /** */
    deleteSelectedProcesses() {
      this.selected
        .map((process) => process.id)
        .forEach((id) => {
          this.$store.dispatch('processStore/remove', { id });
        });
      this.removeBlockedProcessesDialog = false;
      this.removeSelectedProcessesDialog = false;
      this.selected = [];
    },

    /** */
    handleDeleteProcessRequest(process) {
      this.selected = this.typeProcesses.filter((pro) => pro.id === process.id);
      if (process.inEditingBy && process.inEditingBy.length > 0) {
        this.removeBlockedProcessesDialog = true;
      } else {
        this.removeSelectedProcessesDialog = true;
      }
    },
    /** */
    chooseDeleteDialog() {
      let blocked = false;
      this.selected.forEach((process) => {
        if (process.inEditingBy && process.inEditingBy.length > 0) {
          blocked = true;
        }
      });
      if (blocked) {
        this.removeBlockedProcessesDialog = true;
      } else {
        this.removeSelectedProcessesDialog = true;
      }
    },
    openProcess(process) {
      this.$emit('open', process.id);
    },
    /** */
    handleProcessHierarchy(process) {
      this.processForSubprocessModal = process;
      this.isSubprocessesModalVisible = true;
    },
    /**
     * Open export dialog for exporting multiple processes
     * get the max supported resolution, based on the size of the processes
     */
    async openExportDialog() {
      this.max = await getMaximumResolution(this.selected);
      this.exportSelectedProcessesDialog = true;
    },
    /**
     * - export selected processes in selected format
     * - add additional data (bpmn, user tasks, subprocesses, call activities)
     *
     * @param {*} selectedOption selected export format
     */
    async exportSelected(selectedOption) {
      if (!selectedOption) {
        this.exportSelectedProcessesDialog = true;
        return;
      }
      this.exportRunning = true;
      await exportSelectedProcesses(this.processes, this.selected, selectedOption);

      this.exportSelectedProcessesDialog = false;
      this.exportRunning = false;
      this.selected = [];
    },
  },
};
</script>