<script setup lang="ts"> import { computed, onMounted, reactive, watch } from "vue"; import type { WorkflowOut, WorkflowVersionReduced } from "@/client/workflow"; import { Status, WorkflowService, WorkflowVersionService, } from "@/client/workflow"; import { useRoute, useRouter } from "vue-router"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { latestVersion as calculateLatestVersion, sortedVersions, } from "@/utils/Workflow"; import { determineGitIcon } from "@/utils/GitRepository"; import { useAuthStore } from "@/stores/auth"; const userRepository = useAuthStore(); // Props // ============================================================================= const props = defineProps<{ workflowId: string; versionId?: string; }>(); // Constants // ============================================================================= const router = useRouter(); const route = useRoute(); // Reactive State // ============================================================================= const workflowState = reactive<{ loading: boolean; workflow?: WorkflowOut; activeVersionId: string; initialOpen: boolean; }>({ loading: true, workflow: undefined, activeVersionId: "", initialOpen: true, }); // Watchers // ============================================================================= watch( () => props.workflowId, (newWorkflowId, oldWorkflowId) => { if (newWorkflowId !== oldWorkflowId) { updateWorkflow(newWorkflowId); } } ); watch( () => props.versionId, (newWorkflowId) => { workflowState.activeVersionId = newWorkflowId ?? ""; } ); watch( () => workflowState.activeVersionId, (newVersionId, oldVersionId) => { if ( newVersionId && newVersionId !== oldVersionId && route.name !== "workflow-start" ) { router.push({ name: "workflow-version", params: { versionId: newVersionId }, query: { tab: route.query.tab }, }); } } ); // Computed Properties // ============================================================================= const latestVersion = computed<WorkflowVersionReduced | undefined>(() => calculateLatestVersion(workflowState.workflow?.versions || []) ); const activeVersion = computed<WorkflowVersionReduced | undefined>(() => workflowState.workflow?.versions.find( (w) => w.git_commit_hash === workflowState.activeVersionId ) ); const activeVersionString = computed<string>( () => activeVersion.value?.version ?? "" ); const activeVersionIcon = computed<string | undefined>( () => activeVersion.value?.icon_url ); const versionLaunchable = computed<boolean>( () => activeVersion.value?.status == Status.PUBLISHED ?? false ); const gitIcon = computed<string>(() => determineGitIcon(workflowState.workflow?.repository_url) ); const allowVersionDeprecation = computed<boolean>(() => { if (activeVersion.value?.status === Status.PUBLISHED) { if (userRepository.workflowReviewer || userRepository.admin) { return true; } else if ( userRepository.workflowDev && workflowState.workflow?.developer_id === userRepository.currentUID ) { return true; } } return false; }); // Functions // ============================================================================= function updateWorkflow(workflowId: string) { workflowState.loading = true; WorkflowService.workflowGetWorkflow(workflowId) .then((workflow) => { workflowState.workflow = workflow; if (!workflowState.initialOpen || !route.params.versionId) { workflowState.activeVersionId = workflow.versions[workflow.versions.length - 1].git_commit_hash; } else { workflowState.activeVersionId = route.params.versionId as string; } }) .catch(() => { workflowState.workflow = undefined; }) .finally(() => { workflowState.loading = false; workflowState.initialOpen = false; }); } function deprecateCurrentWorkflowVersion() { if (props.versionId) { WorkflowVersionService.workflowVersionDeprecateWorkflowVersion( props.versionId, props.workflowId ).then((version) => { if (workflowState.workflow) { const versionIndex = workflowState.workflow.versions.findIndex( (v) => v.git_commit_hash === version.git_commit_hash ); if (versionIndex > -1) { workflowState.workflow.versions[versionIndex].status = version.status; } } }); } } // Lifecycle Events // ============================================================================= onMounted(() => { updateWorkflow(props.workflowId); }); </script> <template> <div v-if="workflowState.loading"> <div class="d-flex mt-5 justify-content-between align-items-center placeholder-glow" > <span class="fs-0 placeholder col-6"></span> <span class="fs-0 placeholder col-1"></span> </div> <div class="fs-4 mb-5 mt-4 placeholder-glow"> <span class="placeholder col-10"></span> </div> <div class="row align-items-center placeholder-glow my-1"> <span class="mx-auto col-2 placeholder bg-success fs-0"></span> <span class="position-absolute end-0 col-1 placeholder fs-2"></span> </div> <div class="row w-100 mb-4 mt-3 mx-0 placeholder-glow"> <span class="placeholder col-3 mx-auto"></span> </div> </div> <div v-else-if="workflowState.workflow != null"> <div class="d-flex justify-content-between align-items-center"> <div class="fs-0 w-fit text-light"> {{ workflowState.workflow.name }} <span v-if="activeVersionString">@{{ activeVersionString }}</span> </div> <a :href="workflowState.workflow.repository_url" target="_blank"> <img v-if="activeVersionIcon != null" :src="activeVersionIcon" class="img-fluid icon" alt="Workflow icon" /></a> </div> <p class="fs-4 mb-5 mt-3">{{ workflowState.workflow.short_description }}</p> <template v-if="route.name !== 'workflow-start'"> <div v-if="!versionLaunchable" class="alert alert-warning w-fit mx-auto" role="alert" > This version can not be used. <router-link v-if="latestVersion != null" class="alert-link" :to="{ name: 'workflow-version', params: { versionId: latestVersion.git_commit_hash, }, query: { tab: route.query.tab }, }" >Try the latest version {{ latestVersion?.version }}.</router-link > </div> <div class="row align-items-center"> <div class="w-fit position-absolute start-0"> <button v-if="props.versionId && allowVersionDeprecation" type="button" class="btn btn-warning" @click="deprecateCurrentWorkflowVersion" > Deprecate version </button> </div> <router-link role="button" class="btn btn-success btn-lg w-fit mx-auto" :class="{ disabled: !versionLaunchable }" :to="{ name: 'workflow-start', params: { versionId: props.versionId, workflowId: props.workflowId, }, }" > <font-awesome-icon icon="fa-solid fa-rocket" class="me-2" /> <span class="align-middle">Launch {{ activeVersionString }}</span> </router-link> <div v-if="latestVersion != null" class="input-group w-fit position-absolute end-0" > <span class="input-group-text px-2" id="workflow-version-wrapping" ><font-awesome-icon icon="fa-solid fa-tags" class="text-secondary" /></span> <select class="form-select form-select-sm" aria-label="Workflow version selection" aria-describedby="workflow-version-wrapping" v-model="workflowState.activeVersionId" > <option v-for="version in sortedVersions( workflowState.workflow?.versions )" :key="version.git_commit_hash" :value="version.git_commit_hash" > {{ version.version }} </option> </select> </div> </div> <div class="row w-100 mb-4 mt-2 mx-0"> <a :href="workflowState.workflow.repository_url" target="_blank" class="text-secondary text-decoration-none mx-auto w-fit p-0" > <font-awesome-icon :icon="gitIcon" class="me-1" /> <span class="align-middle"> {{ workflowState.workflow.repository_url }}</span ></a > </div> </template> </div> <router-view v-if="workflowState.loading || workflowState.workflow != null" /> <div v-else class="text-center fs-1 mt-5"> <font-awesome-icon icon="fa-solid fa-magnifying-glass" class="my-5 fs-0" style="color: var(--bs-secondary)" /> <p class="my-5"> Could not find any Workflow with ID <br />'{{ workflowId }}' </p> <router-link :to="{ name: 'workflows' }" class="mt-5">Back</router-link> </div> </template> <style scoped> .icon:hover { opacity: 0.8; } .icon { max-width: 60px; max-height: 60px; min-width: 50px; min-height: 50px; } </style>