Browse Source

feat: share package

Ahmad Kholid 2 years ago
parent
commit
54cd6a8130

+ 58 - 0
src/components/newtab/package/PackageDetails.vue

@@ -0,0 +1,58 @@
+<template>
+  <div class="w-full max-w-2xl mt-8">
+    <ui-input
+      :model-value="data.name"
+      label="Package name"
+      class="w-full"
+      placeholder="My package"
+      @change="updatePackage({ name: $event })"
+    />
+    <label class="mt-4 block w-full">
+      <span class="text-sm ml-1 text-gray-600 dark:text-gray-200">
+        Short description
+      </span>
+      <ui-textarea
+        :model-value="data.description"
+        placeholder="Short description"
+        @change="updatePackage({ description: $event })"
+      />
+    </label>
+    <shared-wysiwyg
+      :model-value="data.content"
+      :placeholder="$t('common.description')"
+      :limit="5000"
+      class="prose prose-zinc dark:prose-invert mt-4 max-w-none content-editor p-4 bg-box-transparent rounded-lg relative"
+      @change="updatePackage({ content: $event })"
+      @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>
+</template>
+<script setup>
+import { reactive } from 'vue';
+import { debounce } from '@/utils/helper';
+import SharedWysiwyg from '@/components/newtab/shared/SharedWysiwyg.vue';
+
+defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+const emit = defineEmits(['update']);
+
+const state = reactive({
+  contentLength: 0,
+});
+
+const updatePackage = debounce((data) => {
+  emit('update', data);
+}, 400);
+</script>

+ 8 - 5
src/components/newtab/package/PackageSettings.vue

@@ -1,11 +1,11 @@
 <template>
 <template>
   <label class="inline-flex items-center">
   <label class="inline-flex items-center">
-    <ui-switch v-model="packageState.asBlock" />
+    <ui-switch v-model="packageState.settings.asBlock" />
     <span class="ml-4">
     <span class="ml-4">
       {{ $t('packages.settings.asBlock') }}
       {{ $t('packages.settings.asBlock') }}
     </span>
     </span>
   </label>
   </label>
-  <div v-if="packageState.asBlock" class="mt-6 pb-8 flex space-x-6">
+  <div v-if="packageState.settings.asBlock" class="mt-6 pb-8 flex space-x-6">
     <div class="flex-1">
     <div class="flex-1">
       <p class="font-semibold">Block inputs</p>
       <p class="font-semibold">Block inputs</p>
       <div class="mt-4">
       <div class="mt-4">
@@ -68,7 +68,10 @@
     <div class="flex-1">
     <div class="flex-1">
       <p class="font-semibold">Block outputs</p>
       <p class="font-semibold">Block outputs</p>
       <div class="mt-4">
       <div class="mt-4">
-        <div class="grid grid-cols-12 gap-x-4">
+        <div
+          v-if="packageState.outputs.length > 0"
+          class="grid grid-cols-12 gap-x-4"
+        >
           <div class="col-span-5 pl-1 text-sm">Output name</div>
           <div class="col-span-5 pl-1 text-sm">Output name</div>
           <div class="col-span-6 pl-1 text-sm">Block</div>
           <div class="col-span-6 pl-1 text-sm">Block</div>
         </div>
         </div>
@@ -147,7 +150,7 @@ const state = reactive({
 const packageState = reactive({
 const packageState = reactive({
   inputs: [],
   inputs: [],
   outputs: [],
   outputs: [],
-  asBlock: false,
+  settings: { asBlock: false },
 });
 });
 
 
 function deleteBlockIo(type, index) {
 function deleteBlockIo(type, index) {
@@ -207,8 +210,8 @@ onMounted(() => {
     packageState,
     packageState,
     cloneDeep({
     cloneDeep({
       inputs: props.data.inputs,
       inputs: props.data.inputs,
-      asBlock: props.data.asBlock,
       outputs: props.data.outputs,
       outputs: props.data.outputs,
+      settings: props.data.settings || {},
     })
     })
   );
   );
 
 

+ 4 - 21
src/components/newtab/workflow/editor/EditorLocalActions.vue

@@ -6,10 +6,7 @@
   >
   >
     {{ workflow.tag }}
     {{ workflow.tag }}
   </span>
   </span>
-  <ui-card
-    v-if="!isPackage && (!isTeam || !canEdit)"
-    padding="p-1 pointer-events-auto"
-  >
+  <ui-card v-if="!isTeam || !canEdit" padding="p-1 pointer-events-auto">
     <button
     <button
       v-tooltip.group="'Workflow note'"
       v-tooltip.group="'Workflow note'"
       class="hoverable p-2 rounded-lg"
       class="hoverable p-2 rounded-lg"
@@ -19,7 +16,7 @@
     </button>
     </button>
   </ui-card>
   </ui-card>
   <ui-card
   <ui-card
-    v-if="!isTeam && !isPackage"
+    v-if="!isTeam"
     padding="p-1"
     padding="p-1"
     class="flex items-center pointer-events-auto ml-4"
     class="flex items-center pointer-events-auto ml-4"
   >
   >
@@ -101,7 +98,7 @@
       </ui-list>
       </ui-list>
     </ui-popover>
     </ui-popover>
   </ui-card>
   </ui-card>
-  <ui-card v-if="canEdit && !isPackage" padding="p-1 ml-4 pointer-events-auto">
+  <ui-card v-if="canEdit" padding="p-1 ml-4 pointer-events-auto">
     <button
     <button
       v-for="item in modalActions"
       v-for="item in modalActions"
       :key="item.id"
       :key="item.id"
@@ -112,10 +109,7 @@
       <v-remixicon :name="item.icon" />
       <v-remixicon :name="item.icon" />
     </button>
     </button>
   </ui-card>
   </ui-card>
-  <ui-card
-    v-if="!isPackage"
-    padding="p-1 ml-4 flex items-center pointer-events-auto"
-  >
+  <ui-card padding="p-1 ml-4 flex items-center pointer-events-auto">
     <button
     <button
       v-if="!workflow.isDisabled"
       v-if="!workflow.isDisabled"
       v-tooltip.group="
       v-tooltip.group="
@@ -162,7 +156,6 @@
           <span>{{ t('workflow.host.sync.title') }}</span>
           <span>{{ t('workflow.host.sync.title') }}</span>
         </ui-list-item>
         </ui-list-item>
         <ui-list-item
         <ui-list-item
-          v-if="!isPackage"
           class="cursor-pointer"
           class="cursor-pointer"
           @click="updateWorkflow({ isDisabled: !workflow.isDisabled })"
           @click="updateWorkflow({ isDisabled: !workflow.isDisabled })"
         >
         >
@@ -419,11 +412,6 @@ function updateWorkflow(data = {}, changedIndicator = false) {
       teamId,
       teamId,
       id: props.workflow.id,
       id: props.workflow.id,
     });
     });
-  } else if (props.isPackage) {
-    store = packageStore.update({
-      data,
-      id: props.workflow.id,
-    });
   } else {
   } else {
     store = workflowStore.update({
     store = workflowStore.update({
       data,
       data,
@@ -670,11 +658,6 @@ async function saveWorkflow() {
       return edge;
       return edge;
     });
     });
 
 
-    if (props.isPackage) {
-      updateWorkflow({ data: flow }, false);
-      return;
-    }
-
     const triggerBlock = flow.nodes.find((node) => node.label === 'trigger');
     const triggerBlock = flow.nodes.find((node) => node.label === 'trigger');
     if (!triggerBlock) {
     if (!triggerBlock) {
       toast.error(t('message.noTriggerBlock'));
       toast.error(t('message.noTriggerBlock'));

+ 272 - 0
src/components/newtab/workflow/editor/EditorPkgActions.vue

@@ -0,0 +1,272 @@
+<template>
+  <ui-card
+    v-if="userStore.user"
+    class="pointer-events-auto space-x-1 mr-2"
+    padding="p-1"
+  >
+    <ui-popover>
+      <template #trigger>
+        <ui-button
+          :class="{ 'text-primary': isPkgShared }"
+          icon
+          type="transparent"
+        >
+          <v-remixicon name="riShareLine" />
+        </ui-button>
+      </template>
+      <div class="w-64">
+        <div class="flex items-center">
+          <p class="flex-1">Share package</p>
+          <ui-spinner
+            v-if="state.isSharing || state.isLoadData"
+            color="text-accent"
+          />
+          <ui-switch
+            v-else
+            v-tooltip:bottom="
+              isPkgShared ? 'Unpublish package' : 'Share package'
+            "
+            :model-value="isPkgShared"
+            @change="toggleSharePackage"
+          />
+        </div>
+        <transition-expand>
+          <ui-input
+            v-if="isPkgShared"
+            :model-value="`https://automa.site/packages/${data.id}`"
+            readonly
+            title="URL"
+            type="url"
+            class="w-full mt-2"
+            @click="$event.target.select()"
+          />
+        </transition-expand>
+      </div>
+    </ui-popover>
+  </ui-card>
+  <ui-card class="pointer-events-auto flex items-center" padding="p-1">
+    <ui-popover>
+      <template #trigger>
+        <ui-button icon type="transparent">
+          <v-remixicon name="riMore2Line" />
+        </ui-button>
+      </template>
+      <ui-list class="space-y-1" style="min-width: 9rem">
+        <ui-list-item
+          v-close-popover
+          class="text-red-400 dark:text-red-500 cursor-pointer"
+          @click="deletePackage"
+        >
+          <v-remixicon name="riDeleteBin7Line" class="mr-2 -ml-1" />
+          <span>
+            {{ t('common.delete') }}
+          </span>
+        </ui-list-item>
+      </ui-list>
+    </ui-popover>
+    <ui-button
+      :title="shortcuts['editor:save'].readable"
+      :variant="isPkgShared ? 'default' : 'accent'"
+      class="relative ml-1"
+      @click="savePackage"
+    >
+      <span
+        v-if="isDataChanged"
+        class="flex h-3 w-3 absolute top-0 left-0 -ml-1 -mt-1"
+      >
+        <span
+          class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"
+        ></span>
+        <span
+          class="relative inline-flex rounded-full h-3 w-3 bg-blue-600"
+        ></span>
+      </span>
+      <v-remixicon name="riSaveLine" class="mr-2 -ml-1 my-1" />
+      {{ $t('common.save') }}
+    </ui-button>
+    <ui-button
+      v-if="isPkgShared"
+      :loading="state.isUpdating"
+      variant="accent"
+      class="ml-4"
+      @click="updateSharedPackage"
+    >
+      {{ $t('common.update') }}
+    </ui-button>
+  </ui-card>
+</template>
+<script setup>
+import { onMounted, computed, reactive } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useRouter } from 'vue-router';
+import { useToast } from 'vue-toastification';
+import { useUserStore } from '@/stores/user';
+import { usePackageStore } from '@/stores/package';
+import { getShortcut, useShortcut } from '@/composable/shortcut';
+import { useDialog } from '@/composable/dialog';
+import { fetchApi } from '@/utils/api';
+
+const props = defineProps({
+  isDataChanged: {
+    type: Boolean,
+    default: false,
+  },
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+  editor: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+const emit = defineEmits(['update']);
+
+const { t } = useI18n();
+const toast = useToast();
+const dialog = useDialog();
+const router = useRouter();
+const userStore = useUserStore();
+const packageStore = usePackageStore();
+const shortcuts = useShortcut([
+  /* eslint-disable-next-line */
+  getShortcut('editor:save', savePackage),
+]);
+
+const state = reactive({
+  isSharing: false,
+  isUpdating: false,
+  isLoadData: false,
+});
+
+const isPkgShared = computed(() => packageStore.isShared(props.data.id));
+
+function deletePackage() {
+  dialog.confirm({
+    okVariant: 'danger',
+    okText: 'Delete',
+    title: 'Delete package',
+    body: `Are you sure want to delete the "${props.data.name}" package?`,
+    onConfirm: () => {
+      packageStore.delete(props.data.id);
+      router.replace('/packages');
+    },
+  });
+}
+function updatePackage(data = {}, changedIndicator = false) {
+  return packageStore
+    .update({
+      data,
+      id: props.data.id,
+    })
+    .then((result) => {
+      emit('update', { data, changedIndicator });
+
+      return result;
+    });
+}
+function savePackage() {
+  const flow = props.editor.toObject();
+  flow.edges = flow.edges.map((edge) => {
+    delete edge.sourceNode;
+    delete edge.targetNode;
+
+    return edge;
+  });
+
+  updatePackage({ data: flow }, false);
+}
+async function toggleSharePackage() {
+  state.isSharing = true;
+
+  try {
+    if (!isPkgShared.value) {
+      const keys = [
+        'data',
+        'description',
+        'icon',
+        'id',
+        'inputs',
+        'outputs',
+        'name',
+        'settings',
+      ];
+      const payload = {};
+
+      keys.forEach((key) => {
+        payload[key] = props.data[key];
+      });
+
+      const response = await fetchApi('/packages', {
+        method: 'POST',
+        body: JSON.stringify({
+          package: payload,
+        }),
+      });
+      const data = await response.json();
+
+      if (!response.ok) throw new Error(data.message);
+
+      packageStore.insertShared(props.data.id);
+    } else {
+      const response = await fetchApi(`/packages/${props.data.id}`, {
+        method: 'DELETE',
+      });
+      const result = await response.json();
+
+      if (!response.ok) throw new Error(result.message);
+
+      packageStore.deleteShared(props.data.id);
+    }
+  } catch (error) {
+    console.error(error);
+    toast.error('Something went wrong');
+  } finally {
+    state.isSharing = false;
+  }
+}
+async function updateSharedPackage() {
+  try {
+    state.isUpdating = true;
+
+    const keys = [
+      'data',
+      'description',
+      'icon',
+      'inputs',
+      'outputs',
+      'name',
+      'settings',
+    ];
+    const payload = {};
+
+    keys.forEach((key) => {
+      payload[key] = props.data[key];
+    });
+
+    const response = await fetchApi(`/packages/${props.data.id}`, {
+      method: 'PATCH',
+      body: JSON.stringify({ package: payload }),
+    });
+    const result = await response.json();
+
+    if (!response.ok) throw new Error(result.message);
+  } catch (error) {
+    console.error(error);
+    toast.error('Something went wrong!');
+  } finally {
+    state.isUpdating = false;
+  }
+}
+
+onMounted(async () => {
+  try {
+    state.isLoadData = true;
+    await packageStore.loadShared();
+  } catch (error) {
+    console.error(error);
+  } finally {
+    state.isLoadData = false;
+  }
+});
+</script>

+ 17 - 8
src/components/ui/UiButton.vue

@@ -4,7 +4,7 @@
     role="button"
     role="button"
     class="ui-button h-10 relative transition"
     class="ui-button h-10 relative transition"
     :class="[
     :class="[
-      color ? color : variants[variant],
+      color ? color : variants[type][variant],
       icon ? 'p-2' : 'py-2 px-4',
       icon ? 'p-2' : 'py-2 px-4',
       circle ? 'rounded-full' : 'rounded-lg',
       circle ? 'rounded-full' : 'rounded-lg',
       {
       {
@@ -49,6 +49,10 @@ export default {
       type: String,
       type: String,
       default: 'button',
       default: 'button',
     },
     },
+    type: {
+      type: String,
+      default: 'fill',
+    },
     variant: {
     variant: {
       type: String,
       type: String,
       default: 'default',
       default: 'default',
@@ -56,13 +60,18 @@ export default {
   },
   },
   setup() {
   setup() {
     const variants = {
     const variants = {
-      default: 'bg-input',
-      accent:
-        'bg-accent hover:bg-gray-700 dark:bg-gray-100 dark:hover:bg-gray-200 dark:text-black text-white',
-      primary:
-        'bg-primary text-white dark:bg-secondary dark:hover:bg-primary hover:bg-secondary',
-      danger:
-        'bg-red-400 text-white dark:bg-red-500 dark:hover:bg-red-500 hover:bg-red-400',
+      transparent: {
+        default: 'hoverable',
+      },
+      fill: {
+        default: 'bg-input',
+        accent:
+          'bg-accent hover:bg-gray-700 dark:bg-gray-100 dark:hover:bg-gray-200 dark:text-black text-white',
+        primary:
+          'bg-primary text-white dark:bg-secondary dark:hover:bg-primary hover:bg-secondary',
+        danger:
+          'bg-red-400 text-white dark:bg-red-500 dark:hover:bg-red-500 hover:bg-red-400',
+      },
     };
     };
 
 
     return {
     return {

+ 32 - 17
src/newtab/pages/workflows/[id].vue

@@ -70,9 +70,12 @@
             />
             />
           </button>
           </button>
           <ui-tab value="editor">{{ t('common.editor') }}</ui-tab>
           <ui-tab value="editor">{{ t('common.editor') }}</ui-tab>
-          <ui-tab v-if="isPackage" value="package-settings">
-            {{ t('common.settings') }}
-          </ui-tab>
+          <template v-if="isPackage">
+            <ui-tab value="package-details"> Details </ui-tab>
+            <ui-tab value="package-settings">
+              {{ t('common.settings') }}
+            </ui-tab>
+          </template>
           <ui-tab v-else value="logs" class="flex items-center">
           <ui-tab v-else value="logs" class="flex items-center">
             {{ t('common.log', 2) }}
             {{ t('common.log', 2) }}
             <span
             <span
@@ -95,7 +98,15 @@
         </ui-card>
         </ui-card>
         <div class="flex-grow pointer-events-none" />
         <div class="flex-grow pointer-events-none" />
         <editor-used-credentials v-if="editor" :editor="editor" />
         <editor-used-credentials v-if="editor" :editor="editor" />
+        <editor-pkg-actions
+          v-if="isPackage"
+          :editor="editor"
+          :data="workflow"
+          :is-data-changed="state.dataChanged"
+          @update="onActionUpdated"
+        />
         <editor-local-actions
         <editor-local-actions
+          v-else
           :editor="editor"
           :editor="editor"
           :workflow="workflow"
           :workflow="workflow"
           :is-data-changed="state.dataChanged"
           :is-data-changed="state.dataChanged"
@@ -109,24 +120,25 @@
       </div>
       </div>
       <ui-tab-panels
       <ui-tab-panels
         v-model="state.activeTab"
         v-model="state.activeTab"
-        :class="{ 'overflow-hidden': state.activeTab !== 'package-settings' }"
+        :class="{ 'overflow-hidden': !state.activeTab.startsWith('package') }"
         class="h-full w-full"
         class="h-full w-full"
         @drop="onDropInEditor"
         @drop="onDropInEditor"
         @dragend="clearHighlightedElements"
         @dragend="clearHighlightedElements"
         @dragover.prevent="onDragoverEditor"
         @dragover.prevent="onDragoverEditor"
       >
       >
-        <ui-tab-panel
-          v-if="isPackage"
-          value="package-settings"
-          class="mt-24 container"
-        >
-          <package-settings
-            :data="workflow"
-            :editor="editor"
-            @update="updateWorkflow"
-            @goBlock="goToPkgBlock"
-          />
-        </ui-tab-panel>
+        <template v-if="isPackage">
+          <ui-tab-panel value="package-details" class="pt-24 container">
+            <package-details :data="workflow" @update="updateWorkflow" />
+          </ui-tab-panel>
+          <ui-tab-panel value="package-settings" class="pt-24 container">
+            <package-settings
+              :data="workflow"
+              :editor="editor"
+              @update="updateWorkflow"
+              @goBlock="goToPkgBlock"
+            />
+          </ui-tab-panel>
+        </template>
         <ui-tab-panel cache value="editor" class="w-full">
         <ui-tab-panel cache value="editor" class="w-full">
           <workflow-editor
           <workflow-editor
             v-if="state.workflowConverted"
             v-if="state.workflowConverted"
@@ -308,10 +320,12 @@ import WorkflowDetailsCard from '@/components/newtab/workflow/WorkflowDetailsCar
 import SharedPermissionsModal from '@/components/newtab/shared/SharedPermissionsModal.vue';
 import SharedPermissionsModal from '@/components/newtab/shared/SharedPermissionsModal.vue';
 import EditorLogs from '@/components/newtab/workflow/editor/EditorLogs.vue';
 import EditorLogs from '@/components/newtab/workflow/editor/EditorLogs.vue';
 import EditorAddPackage from '@/components/newtab/workflow/editor/EditorAddPackage.vue';
 import EditorAddPackage from '@/components/newtab/workflow/editor/EditorAddPackage.vue';
+import EditorPkgActions from '@/components/newtab/workflow/editor/EditorPkgActions.vue';
 import EditorLocalCtxMenu from '@/components/newtab/workflow/editor/EditorLocalCtxMenu.vue';
 import EditorLocalCtxMenu from '@/components/newtab/workflow/editor/EditorLocalCtxMenu.vue';
 import EditorLocalActions from '@/components/newtab/workflow/editor/EditorLocalActions.vue';
 import EditorLocalActions from '@/components/newtab/workflow/editor/EditorLocalActions.vue';
 import EditorUsedCredentials from '@/components/newtab/workflow/editor/EditorUsedCredentials.vue';
 import EditorUsedCredentials from '@/components/newtab/workflow/editor/EditorUsedCredentials.vue';
 import EditorLocalSavedBlocks from '@/components/newtab/workflow/editor/EditorLocalSavedBlocks.vue';
 import EditorLocalSavedBlocks from '@/components/newtab/workflow/editor/EditorLocalSavedBlocks.vue';
+import PackageDetails from '@/components/newtab/package/PackageDetails.vue';
 import PackageSettings from '@/components/newtab/package/PackageSettings.vue';
 import PackageSettings from '@/components/newtab/package/PackageSettings.vue';
 
 
 const blocks = { ...tasks, ...customBlocks };
 const blocks = { ...tasks, ...customBlocks };
@@ -1056,8 +1070,9 @@ async function updateWorkflow(data) {
 }
 }
 function onActionUpdated({ data, changedIndicator }) {
 function onActionUpdated({ data, changedIndicator }) {
   state.dataChanged = changedIndicator;
   state.dataChanged = changedIndicator;
+
   workflowPayload.data = { ...workflowPayload.data, ...data };
   workflowPayload.data = { ...workflowPayload.data, ...data };
-  updateHostedWorkflow();
+  if (!isPackage) updateHostedWorkflow();
 }
 }
 function onEditorInit(instance) {
 function onEditorInit(instance) {
   editor.value = instance;
   editor.value = instance;

+ 35 - 2
src/stores/package.js

@@ -1,16 +1,20 @@
 import { defineStore } from 'pinia';
 import { defineStore } from 'pinia';
 import { nanoid } from 'nanoid';
 import { nanoid } from 'nanoid';
 import browser from 'webextension-polyfill';
 import browser from 'webextension-polyfill';
+import { fetchApi } from '@/utils/api';
 
 
 const defaultPackage = {
 const defaultPackage = {
   id: '',
   id: '',
   name: '',
   name: '',
-  icon: '',
+  icon: 'mdiPackageVariantClosed',
   isExtenal: false,
   isExtenal: false,
-  asBlock: false,
+  content: null,
   inputs: [],
   inputs: [],
   outputs: [],
   outputs: [],
   variable: [],
   variable: [],
+  settings: {
+    asBlock: false,
+  },
   data: {
   data: {
     edges: [],
     edges: [],
     nodes: [],
     nodes: [],
@@ -23,12 +27,17 @@ export const usePackageStore = defineStore('packages', {
   },
   },
   state: () => ({
   state: () => ({
     packages: [],
     packages: [],
+    sharedPkgs: [],
     retrieved: false,
     retrieved: false,
+    sharedRetrieved: false,
   }),
   }),
   getters: {
   getters: {
     getById: (state) => (pkgId) => {
     getById: (state) => (pkgId) => {
       return state.packages.find((pkg) => pkg.id === pkgId);
       return state.packages.find((pkg) => pkg.id === pkgId);
     },
     },
+    isShared: (state) => (pkgId) => {
+      return state.sharedPkgs.some((pkg) => pkg.id === pkgId);
+    },
   },
   },
   actions: {
   actions: {
     async insert(data) {
     async insert(data) {
@@ -60,6 +69,13 @@ export const usePackageStore = defineStore('packages', {
 
 
       return data;
       return data;
     },
     },
+    deleteShared(id) {
+      const index = this.sharedPkgs.findIndex((item) => item.id === id);
+      if (index !== -1) this.sharedPkgs.splice(index, 1);
+    },
+    insertShared(id) {
+      this.sharedPkgs.push({ id });
+    },
     async loadData() {
     async loadData() {
       if (this.retrieved) return this.packages;
       if (this.retrieved) return this.packages;
 
 
@@ -70,5 +86,22 @@ export const usePackageStore = defineStore('packages', {
 
 
       return this.packages;
       return this.packages;
     },
     },
+    async loadShared() {
+      try {
+        if (this.sharedRetrieved) return;
+
+        const response = await fetchApi('/me/packages');
+        const result = await response.json();
+
+        if (!response.ok) throw new Error(result.message);
+
+        this.sharedPkgs = result;
+        this.sharedRetrieved = true;
+      } catch (error) {
+        console.error(error.message);
+
+        throw error;
+      }
+    },
   },
   },
 });
 });

+ 1 - 0
src/stores/user.js

@@ -8,6 +8,7 @@ export const useUserStore = defineStore('user', {
     backupIds: [],
     backupIds: [],
     retrieved: false,
     retrieved: false,
     hostedWorkflows: {},
     hostedWorkflows: {},
+    sharedPackages: [],
   }),
   }),
   getters: {
   getters: {
     getHostedWorkflows: (state) => Object.values(state.hostedWorkflows),
     getHostedWorkflows: (state) => Object.values(state.hostedWorkflows),