|
@@ -0,0 +1,239 @@
|
|
|
+<template>
|
|
|
+ <ui-card class="w-full max-w-4xl share-workflow overflow-auto scroll">
|
|
|
+ <h1 class="text-xl font-semibold">Share workflow with team</h1>
|
|
|
+ <p class="text-gray-600 dark:text-gray-200">
|
|
|
+ This workflow will be shared with your team
|
|
|
+ </p>
|
|
|
+ <div class="flex items-start mt-4">
|
|
|
+ <div class="flex-1 mr-8">
|
|
|
+ <ui-input
|
|
|
+ v-model="state.workflow.name"
|
|
|
+ :label="t('workflow.name')"
|
|
|
+ type="text"
|
|
|
+ name="workflow name"
|
|
|
+ class="w-full"
|
|
|
+ />
|
|
|
+ <div class="relative mb-2 mt-2">
|
|
|
+ <label
|
|
|
+ for="short-description"
|
|
|
+ class="text-sm ml-2 text-gray-600 dark:text-gray-200"
|
|
|
+ >
|
|
|
+ Short description
|
|
|
+ </label>
|
|
|
+ <ui-textarea
|
|
|
+ id="short-description"
|
|
|
+ v-model="state.workflow.description"
|
|
|
+ :max="300"
|
|
|
+ label="Short description"
|
|
|
+ placeholder="Write here..."
|
|
|
+ class="w-full h-28 scroll resize-none"
|
|
|
+ />
|
|
|
+ <p
|
|
|
+ class="text-sm text-gray-600 dark:text-gray-200 absolute bottom-2 right-2"
|
|
|
+ >
|
|
|
+ {{ state.workflow.description.length }}/300
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <shared-wysiwyg
|
|
|
+ v-model="state.workflow.content"
|
|
|
+ :placeholder="t('common.description')"
|
|
|
+ :limit="5000"
|
|
|
+ class="prose prose-zinc dark:prose-invert max-w-none content-editor p-4 bg-box-transparent rounded-lg relative"
|
|
|
+ @count="state.contentLength = $event"
|
|
|
+ >
|
|
|
+ <template #append>
|
|
|
+ <p
|
|
|
+ class="text-sm text-gray-600 dark:text-gray-200 absolute bottom-2 right-2"
|
|
|
+ >
|
|
|
+ {{ state.contentLength }}/5000
|
|
|
+ </p>
|
|
|
+ </template>
|
|
|
+ </shared-wysiwyg>
|
|
|
+ </div>
|
|
|
+ <div class="w-64 sticky top-4 pb-4">
|
|
|
+ <div class="flex">
|
|
|
+ <ui-button
|
|
|
+ v-tooltip="'Save as draft'"
|
|
|
+ :disabled="state.isPublishing"
|
|
|
+ icon
|
|
|
+ @click="saveDraft"
|
|
|
+ >
|
|
|
+ <v-remixicon name="riSaveLine" />
|
|
|
+ </ui-button>
|
|
|
+ <ui-button
|
|
|
+ :loading="state.isPublishing"
|
|
|
+ :disabled="!state.workflow.name.trim()"
|
|
|
+ variant="accent"
|
|
|
+ class="w-full ml-2"
|
|
|
+ @click="publishWorkflow"
|
|
|
+ >
|
|
|
+ Publish
|
|
|
+ </ui-button>
|
|
|
+ </div>
|
|
|
+ <ui-button
|
|
|
+ :disabled="state.isPublishing"
|
|
|
+ class="mt-2 w-full"
|
|
|
+ @click="$emit('close')"
|
|
|
+ >
|
|
|
+ Cancel
|
|
|
+ </ui-button>
|
|
|
+ <ui-select
|
|
|
+ v-model="state.workflow.category"
|
|
|
+ class="mt-4 w-full"
|
|
|
+ :label="t('common.category')"
|
|
|
+ >
|
|
|
+ <option value="">(none)</option>
|
|
|
+ <option
|
|
|
+ v-for="(category, id) in workflowCategories"
|
|
|
+ :key="id"
|
|
|
+ :value="id"
|
|
|
+ >
|
|
|
+ {{ category }}
|
|
|
+ </option>
|
|
|
+ </ui-select>
|
|
|
+ <span class="text-sm ml-2 text-gray-600 dark:text-gray-200 mt-5 block">
|
|
|
+ Environment
|
|
|
+ </span>
|
|
|
+ <ui-tabs v-model="state.workflow.tag" type="fill" fill>
|
|
|
+ <ui-tab value="stage"> Stage </ui-tab>
|
|
|
+ <ui-tab value="production"> Production </ui-tab>
|
|
|
+ </ui-tabs>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </ui-card>
|
|
|
+</template>
|
|
|
+<script setup>
|
|
|
+import { reactive, watch, onMounted } from 'vue';
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
+import { useToast } from 'vue-toastification';
|
|
|
+import browser from 'webextension-polyfill';
|
|
|
+import { fetchApi } from '@/utils/api';
|
|
|
+import { useUserStore } from '@/stores/user';
|
|
|
+import { useSharedWorkflowStore } from '@/stores/sharedWorkflow';
|
|
|
+import { workflowCategories } from '@/utils/shared';
|
|
|
+import { parseJSON, debounce } from '@/utils/helper';
|
|
|
+import { convertWorkflow } from '@/utils/workflowData';
|
|
|
+import SharedWysiwyg from '@/components/newtab/shared/SharedWysiwyg.vue';
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ workflow: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
+ isUpdate: Boolean,
|
|
|
+});
|
|
|
+const emit = defineEmits(['close', 'publish', 'change']);
|
|
|
+
|
|
|
+const { t } = useI18n();
|
|
|
+const toast = useToast();
|
|
|
+const userStore = useUserStore();
|
|
|
+const sharedWorkflowStore = useSharedWorkflowStore();
|
|
|
+
|
|
|
+const state = reactive({
|
|
|
+ contentLength: 0,
|
|
|
+ isPublishing: false,
|
|
|
+ workflow: JSON.parse(JSON.stringify(props.workflow)),
|
|
|
+});
|
|
|
+
|
|
|
+async function publishWorkflow() {
|
|
|
+ try {
|
|
|
+ state.isPublishing = true;
|
|
|
+
|
|
|
+ const workflow = convertWorkflow(state.workflow, ['id', 'category']);
|
|
|
+ workflow.name = workflow.name || 'unnamed';
|
|
|
+ workflow.tag = state.workflow.tag || 'stage';
|
|
|
+ workflow.content = state.workflow.content || null;
|
|
|
+ workflow.description = state.workflow.description.slice(0, 300);
|
|
|
+ workflow.drawflow = parseJSON(workflow.drawflow, workflow.drawflow);
|
|
|
+
|
|
|
+ delete workflow.extVersion;
|
|
|
+
|
|
|
+ const response = await fetchApi(
|
|
|
+ `/teams/${userStore.user.team.id}/workflows`,
|
|
|
+ {
|
|
|
+ method: 'POST',
|
|
|
+ body: JSON.stringify({ workflow }),
|
|
|
+ }
|
|
|
+ );
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ const error = new Error(response.statusText);
|
|
|
+ error.data = result.data;
|
|
|
+
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ workflow.drawflow = props.workflow.drawflow;
|
|
|
+
|
|
|
+ sharedWorkflowStore.insert(workflow);
|
|
|
+ sessionStorage.setItem(
|
|
|
+ 'shared-workflows',
|
|
|
+ JSON.stringify(sharedWorkflowStore.shared)
|
|
|
+ );
|
|
|
+
|
|
|
+ state.isPublishing = false;
|
|
|
+
|
|
|
+ emit('publish');
|
|
|
+ } catch (error) {
|
|
|
+ let errorMessage = t('message.somethingWrong');
|
|
|
+
|
|
|
+ if (error.data && error.data.show) {
|
|
|
+ errorMessage = error.message;
|
|
|
+ }
|
|
|
+
|
|
|
+ toast.error(errorMessage);
|
|
|
+ console.error(error);
|
|
|
+
|
|
|
+ state.isPublishing = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+function saveDraft() {
|
|
|
+ const key = `draft-team:${props.workflow.id}`;
|
|
|
+ browser.storage.local.set({
|
|
|
+ [key]: {
|
|
|
+ name: state.workflow.name,
|
|
|
+ tag: state.tag,
|
|
|
+ content: state.workflow.content,
|
|
|
+ category: state.workflow.category,
|
|
|
+ description: state.workflow.description,
|
|
|
+ },
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => state.workflow,
|
|
|
+ debounce(() => {
|
|
|
+ emit('change', state.workflow);
|
|
|
+ }, 200),
|
|
|
+ { deep: true }
|
|
|
+);
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ const key = `draft-team:${props.workflow.id}`;
|
|
|
+ browser.storage.local.get(key).then((data) => {
|
|
|
+ Object.assign(state.workflow, data[key]);
|
|
|
+
|
|
|
+ if (!state.workflow.tag) {
|
|
|
+ state.workflow.tag = 'stage';
|
|
|
+ }
|
|
|
+ });
|
|
|
+});
|
|
|
+</script>
|
|
|
+<style scoped>
|
|
|
+.share-workflow {
|
|
|
+ min-height: 500px;
|
|
|
+ max-height: calc(100vh - 4rem);
|
|
|
+}
|
|
|
+.editor-menu-btn {
|
|
|
+ @apply p-1 rounded-md transition;
|
|
|
+}
|
|
|
+</style>
|
|
|
+<style>
|
|
|
+.content-editor .ProseMirror {
|
|
|
+ min-height: 200px;
|
|
|
+}
|
|
|
+.content-editor .ProseMirror :first-child {
|
|
|
+ margin-top: 0 !important;
|
|
|
+}
|
|
|
+</style>
|