main
vue 243 lines 6.82 KB
Raw
1 <template>
2 <n-spin :show="loading" class="active-response-wizard">
3 <div class="flex min-h-120 flex-col">
4 <div class="flex grow flex-col">
5 <n-scrollbar x-scrollable trigger="none">
6 <div class="p-6 pt-3">
7 <n-steps :current size="small" :status="currentStatus">
8 <n-step title="Operative System" />
9 <n-step title="Active Response" />
10 <n-step title="Submission" />
11 </n-steps>
12 </div>
13 </n-scrollbar>
14
15 <div class="mt-4 flex grow flex-col">
16 <Transition :name="`slide-form-${slideFormDirection}`">
17 <div v-if="current === 1" class="flex flex-col gap-2 px-6">
18 <CardEntity embedded hoverable clickable @click.stop="setOs('linux')">
19 <div class="flex items-center gap-3">
20 <Icon :size="18" :name="iconFromOs('linux')" />
21 <span>LINUX</span>
22 </div>
23 </CardEntity>
24 <CardEntity embedded hoverable clickable @click.stop="setOs('windows')">
25 <div class="flex items-center gap-3">
26 <Icon :size="18" :name="iconFromOs('windows')" />
27 <span>WINDOWS</span>
28 </div>
29 </CardEntity>
30 <CardEntity embedded hoverable clickable @click.stop="setOs('macos')">
31 <div class="flex items-center gap-3">
32 <Icon :size="18" :name="iconFromOs('macos')" />
33 <span>MACOS</span>
34 </div>
35 </CardEntity>
36 </div>
37
38 <div v-else-if="current === 2" class="px-6">
39 <n-spin :show="loadingActiveResponse">
40 <div class="flex flex-col gap-2">
41 <template v-if="activeResponseFiltered.length">
42 <ActiveResponseItem
43 v-for="activeResponse of activeResponseFiltered"
44 :key="activeResponse.name"
45 :active-response
46 embedded
47 clickable
48 hide-actions
49 @click.stop="setActiveResponse(activeResponse)"
50 />
51 </template>
52 <template v-else>
53 <n-empty
54 v-if="!loadingActiveResponse"
55 description="No items found"
56 class="h-48 justify-center"
57 />
58 </template>
59 </div>
60 </n-spin>
61 </div>
62 <div v-else-if="current === 3" class="flex min-h-[401px] grow flex-col px-6 pb-7">
63 <ActiveResponseInvokeForm
64 v-if="selectedActiveResponse"
65 :active-response="selectedActiveResponse"
66 @mounted="activeResponseInvokeFormCTX = $event"
67 @submitted="reset()"
68 @start-loading="loadingActiveResponseInvoke = true"
69 @stop-loading="loadingActiveResponseInvoke = false"
70 >
71 <template #additionalActions>
72 <n-button :disabled="loadingActiveResponseInvoke" @click.stop="prev()">
73 <template #icon>
74 <Icon :name="ArrowLeftIcon" />
75 </template>
76 Prev
77 </n-button>
78 </template>
79 </ActiveResponseInvokeForm>
80 </div>
81 </Transition>
82 </div>
83 </div>
84
85 <div v-if="current !== 3" class="flex justify-between gap-4 p-6 pt-3">
86 <div class="flex gap-4">
87 <n-button v-if="isPrevStepEnabled" @click.stop="prev()">
88 <template #icon>
89 <Icon :name="ArrowLeftIcon" />
90 </template>
91 Prev
92 </n-button>
93 </div>
94 <div class="flex gap-4">
95 <slot name="additionalActions"></slot>
96 </div>
97 </div>
98 </div>
99 </n-spin>
100 </template>
101
102 <script setup lang="ts">
103 import type { StepsProps } from "naive-ui"
104 import type { SupportedActiveResponse } from "@/types/activeResponse.d"
105 import type { OsTypesLower } from "@/types/common.d"
106 import { NButton, NEmpty, NScrollbar, NSpin, NStep, NSteps, useMessage } from "naive-ui"
107 import { computed, onBeforeMount, onMounted, ref, watch } from "vue"
108 import Api from "@/api"
109 import CardEntity from "@/components/common/cards/CardEntity.vue"
110 import Icon from "@/components/common/Icon.vue"
111 import { iconFromOs } from "@/utils"
112 import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
113 import ActiveResponseItem from "./ActiveResponseItem.vue"
114
115 const emit = defineEmits<{
116 (e: "update:loading", value: boolean): void
117 (
118 e: "mounted",
119 value: {
120 reset: () => void
121 }
122 ): void
123 }>()
124
125 const ArrowLeftIcon = "carbon:arrow-left"
126
127 const loadingActiveResponse = ref(false)
128 const loadingActiveResponseInvoke = ref(false)
129 const loading = computed(() => loadingActiveResponseInvoke.value)
130 const slideFormDirection = ref<"right" | "left">("right")
131 const activeResponseList = ref<SupportedActiveResponse[]>([])
132 const message = useMessage()
133 const current = ref<number>(1)
134 const currentStatus = ref<StepsProps["status"]>("process")
135 const selectedOS = ref<OsTypesLower | null>(null)
136 const selectedActiveResponse = ref<SupportedActiveResponse | null>(null)
137 const activeResponseInvokeFormCTX = ref<{ reset: () => void } | null>(null)
138
139 watch(loading, val => {
140 emit("update:loading", val)
141 })
142
143 const activeResponseFiltered = computed(() => {
144 if (selectedOS.value === null) {
145 return activeResponseList.value
146 }
147 return activeResponseList.value.filter(o => o.name.toLowerCase().indexOf(selectedOS.value || "") === 0)
148 })
149 const isPrevStepEnabled = computed(() => current.value > 1)
150
151 function next() {
152 currentStatus.value = "process"
153 slideFormDirection.value = "right"
154 current.value++
155 }
156
157 function prev() {
158 currentStatus.value = "process"
159 slideFormDirection.value = "left"
160 current.value--
161 activeResponseInvokeFormCTX.value?.reset()
162 }
163
164 function reset() {
165 if (!loadingActiveResponseInvoke.value) {
166 currentStatus.value = "process"
167 slideFormDirection.value = "right"
168 current.value = 1
169 selectedOS.value = null
170 selectedActiveResponse.value = null
171 activeResponseInvokeFormCTX.value?.reset()
172 }
173 }
174
175 function getActiveResponseList() {
176 loadingActiveResponse.value = true
177
178 Api.activeResponse
179 .getSupported()
180 .then(res => {
181 if (res.data.success) {
182 activeResponseList.value = res.data?.supported_active_responses || []
183 } else {
184 message.warning(res.data?.message || "An error occurred. Please try again later.")
185 }
186 })
187 .catch(err => {
188 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
189 })
190 .finally(() => {
191 loadingActiveResponse.value = false
192 })
193 }
194
195 function setOs(os: OsTypesLower) {
196 selectedOS.value = os
197 next()
198 }
199
200 function setActiveResponse(activeResponse: SupportedActiveResponse) {
201 selectedActiveResponse.value = activeResponse
202 next()
203 }
204
205 onBeforeMount(() => {
206 getActiveResponseList()
207 })
208
209 onMounted(() => {
210 emit("mounted", {
211 reset
212 })
213 })
214 </script>
215
216 <style lang="scss" scoped>
217 .active-response-wizard {
218 .slide-form-right-enter-active,
219 .slide-form-right-leave-active,
220 .slide-form-left-enter-active,
221 .slide-form-left-leave-active {
222 transition: all 0.2s ease-out;
223 position: absolute;
224 width: 100%;
225 }
226
227 .slide-form-left-enter-from {
228 transform: translateX(-100%);
229 }
230
231 .slide-form-left-leave-to {
232 transform: translateX(100%);
233 }
234
235 .slide-form-right-enter-from {
236 transform: translateX(100%);
237 }
238
239 .slide-form-right-leave-to {
240 transform: translateX(-100%);
241 }
242 }
243 </style>