Compare commits
3 Commits
refactor/w
...
2024.3.3-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c0805fafe | ||
|
|
26b4f64824 | ||
|
|
4156551b24 |
@@ -47,6 +47,7 @@ RATE_LIMIT_MAX=100 # Max requests per IP
|
|||||||
|
|
||||||
|
|
||||||
# Base URLs
|
# Base URLs
|
||||||
|
VITE_BACKEND_LOGIN_API_URL=http://localhost:5444
|
||||||
VITE_BASE_URL=http://localhost:3000
|
VITE_BASE_URL=http://localhost:3000
|
||||||
VITE_SHORTCODE_BASE_URL=http://localhost:3000
|
VITE_SHORTCODE_BASE_URL=http://localhost:3000
|
||||||
VITE_ADMIN_URL=http://localhost:3100
|
VITE_ADMIN_URL=http://localhost:3100
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"vue": "3.4.27"
|
"vue": "3.3.9"
|
||||||
},
|
},
|
||||||
"packageExtensions": {
|
"packageExtensions": {
|
||||||
"httpsnippet@3.0.1": {
|
"httpsnippet@3.0.1": {
|
||||||
|
|||||||
@@ -1027,8 +1027,7 @@
|
|||||||
"personal": "Personal Workspace",
|
"personal": "Personal Workspace",
|
||||||
"other_workspaces": "My Workspaces",
|
"other_workspaces": "My Workspaces",
|
||||||
"team": "Workspace",
|
"team": "Workspace",
|
||||||
"title": "Workspaces",
|
"title": "Workspaces"
|
||||||
"no_workspace": "No Workspace"
|
|
||||||
},
|
},
|
||||||
"site_protection": {
|
"site_protection": {
|
||||||
"login_to_continue": "Login to continue",
|
"login_to_continue": "Login to continue",
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
"util": "0.12.5",
|
"util": "0.12.5",
|
||||||
"uuid": "9.0.1",
|
"uuid": "9.0.1",
|
||||||
"verzod": "0.2.2",
|
"verzod": "0.2.2",
|
||||||
"vue": "3.4.27",
|
"vue": "3.3.9",
|
||||||
"vue-i18n": "9.8.0",
|
"vue-i18n": "9.8.0",
|
||||||
"vue-pdf-embed": "1.2.1",
|
"vue-pdf-embed": "1.2.1",
|
||||||
"vue-router": "4.2.5",
|
"vue-router": "4.2.5",
|
||||||
|
|||||||
@@ -181,10 +181,6 @@ declare module 'vue' {
|
|||||||
LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
|
LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
|
||||||
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
||||||
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
||||||
NewCollections: typeof import('./components/new-collections/index.vue')['default']
|
|
||||||
NewCollectionsRest: typeof import('./components/new-collections/rest/index.vue')['default']
|
|
||||||
NewCollectionsRestCollection: typeof import('./components/new-collections/rest/Collection.vue')['default']
|
|
||||||
NewCollectionsRestRequest: typeof import('./components/new-collections/rest/Request.vue')['default']
|
|
||||||
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']
|
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']
|
||||||
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
|
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
|
||||||
RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default']
|
RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default']
|
||||||
@@ -216,8 +212,6 @@ declare module 'vue' {
|
|||||||
TeamsTeam: typeof import('./components/teams/Team.vue')['default']
|
TeamsTeam: typeof import('./components/teams/Team.vue')['default']
|
||||||
Tippy: typeof import('vue-tippy')['Tippy']
|
Tippy: typeof import('vue-tippy')['Tippy']
|
||||||
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
|
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
|
||||||
WorkspacePersonalWorkspaceSelector: typeof import('./components/workspace/PersonalWorkspaceSelector.vue')['default']
|
|
||||||
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
|
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
|
||||||
WorkspaceTestWorkspaceSelector: typeof import('./components/workspace/TestWorkspaceSelector.vue')['default']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,73 +2,74 @@
|
|||||||
<div>
|
<div>
|
||||||
<header
|
<header
|
||||||
ref="headerRef"
|
ref="headerRef"
|
||||||
class="flex flex-1 flex-shrink-0 items-center justify-between space-x-2 overflow-x-auto overflow-y-hidden px-2 py-2"
|
class="grid grid-cols-5 grid-rows-1 gap-2 overflow-x-auto overflow-y-hidden p-2"
|
||||||
@mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()"
|
@mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="inline-flex flex-1 items-center justify-start space-x-2"
|
class="col-span-2 flex items-center justify-between space-x-2"
|
||||||
:style="{
|
:style="{
|
||||||
paddingTop: platform.ui?.appHeader?.paddingTop?.value,
|
paddingTop: platform.ui?.appHeader?.paddingTop?.value,
|
||||||
paddingLeft: platform.ui?.appHeader?.paddingLeft?.value,
|
paddingLeft: platform.ui?.appHeader?.paddingLeft?.value,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div class="flex">
|
||||||
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
|
|
||||||
:label="t('app.name')"
|
|
||||||
to="/"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex flex-1 items-center justify-center space-x-2">
|
|
||||||
<button
|
|
||||||
class="flex max-w-[15rem] flex-1 cursor-text items-center justify-between self-stretch rounded border border-dividerDark bg-primaryDark px-2 py-1 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
|
||||||
@click="invokeAction('modals.search.toggle')"
|
|
||||||
>
|
|
||||||
<span class="inline-flex flex-1 items-center">
|
|
||||||
<icon-lucide-search class="svg-icons mr-2" />
|
|
||||||
{{ t("app.search") }}
|
|
||||||
</span>
|
|
||||||
<span class="flex space-x-1">
|
|
||||||
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
|
|
||||||
<kbd class="shortcut-key">K</kbd>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-if="showInstallButton"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('header.install_pwa')"
|
|
||||||
:icon="IconDownload"
|
|
||||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
|
||||||
@click="installPWA()"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
|
||||||
:title="`${
|
|
||||||
mdAndLarger ? t('support.title') : t('app.options')
|
|
||||||
} <kbd>?</kbd>`"
|
|
||||||
:icon="IconLifeBuoy"
|
|
||||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
|
||||||
@click="invokeAction('modals.support.toggle')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex flex-1 items-center justify-end space-x-2">
|
|
||||||
<div
|
|
||||||
v-if="currentUser === null"
|
|
||||||
class="inline-flex items-center space-x-2"
|
|
||||||
>
|
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:icon="IconUploadCloud"
|
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
:label="t('header.save_workspace')"
|
:label="t('app.name')"
|
||||||
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 hidden border border-green-600/25 bg-green-500/[.15] !text-green-500 hover:border-green-800/50 hover:bg-green-400/10 focus-visible:border-green-800/50 focus-visible:bg-green-400/10 md:flex"
|
to="/"
|
||||||
@click="invokeAction('modals.login.toggle')"
|
|
||||||
/>
|
|
||||||
<HoppButtonPrimary
|
|
||||||
:label="t('header.login')"
|
|
||||||
@click="invokeAction('modals.login.toggle')"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="inline-flex items-center space-x-2">
|
</div>
|
||||||
|
<div class="col-span-1 flex items-center justify-between space-x-2">
|
||||||
|
<AppSpotlightSearch />
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2 flex items-center justify-between space-x-2">
|
||||||
|
<div class="flex">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="showInstallButton"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('header.install_pwa')"
|
||||||
|
:icon="IconDownload"
|
||||||
|
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
|
@click="installPWA()"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
|
:title="`${
|
||||||
|
mdAndLarger ? t('support.title') : t('app.options')
|
||||||
|
} <kbd>?</kbd>`"
|
||||||
|
:icon="IconLifeBuoy"
|
||||||
|
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
|
@click="invokeAction('modals.support.toggle')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex"
|
||||||
|
:class="{
|
||||||
|
'flex-row-reverse gap-2':
|
||||||
|
workspaceSelectorFlagEnabled && !currentUser,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="currentUser === null"
|
||||||
|
class="inline-flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="!workspaceSelectorFlagEnabled"
|
||||||
|
:icon="IconUploadCloud"
|
||||||
|
:label="t('header.save_workspace')"
|
||||||
|
class="!focus-visible:text-emerald-600 !hover:text-emerald-600 hidden h-8 border border-emerald-600/25 bg-emerald-500/10 !text-emerald-500 hover:border-emerald-600/20 hover:bg-emerald-600/20 focus-visible:border-emerald-600/20 focus-visible:bg-emerald-600/20 md:flex"
|
||||||
|
@click="invokeAction('modals.login.toggle')"
|
||||||
|
/>
|
||||||
|
<HoppButtonPrimary
|
||||||
|
:label="t('header.login')"
|
||||||
|
class="h-8"
|
||||||
|
@click="invokeAction('modals.login.toggle')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<TeamsMemberStack
|
<TeamsMemberStack
|
||||||
v-if="
|
v-else-if="
|
||||||
|
currentUser !== null &&
|
||||||
workspace.type === 'team' &&
|
workspace.type === 'team' &&
|
||||||
selectedTeam &&
|
selectedTeam &&
|
||||||
selectedTeam.teamMembers.length > 1
|
selectedTeam.teamMembers.length > 1
|
||||||
@@ -79,145 +80,142 @@
|
|||||||
@handle-click="handleTeamEdit()"
|
@handle-click="handleTeamEdit()"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="flex divide-x divide-green-600/25 rounded border border-green-600/25 bg-green-500/[.15] focus-within:divide-green-800/50 focus-within:border-green-800/50 focus-within:bg-green-400/10 hover:divide-green-800/50 hover:border-green-800/50 hover:bg-green-400/10"
|
v-if="workspaceSelectorFlagEnabled || currentUser"
|
||||||
|
class="inline-flex items-center space-x-2"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<div
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
class="flex h-8 divide-x divide-emerald-600/25 rounded border border-emerald-600/25 bg-emerald-500/10 focus-within:divide-emerald-600/20 focus-within:border-emerald-600/20 focus-within:bg-emerald-600/20 hover:divide-emerald-600/20 hover:border-emerald-600/20 hover:bg-emerald-600/20"
|
||||||
:title="t('team.invite_tooltip')"
|
>
|
||||||
:icon="IconUserPlus"
|
<HoppButtonSecondary
|
||||||
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 !text-green-500"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@click="handleInvite()"
|
:title="t('team.invite_tooltip')"
|
||||||
/>
|
:icon="IconUserPlus"
|
||||||
<HoppButtonSecondary
|
class="!focus-visible:text-emerald-600 !hover:text-emerald-600 !text-emerald-500"
|
||||||
v-if="
|
@click="handleInvite()"
|
||||||
workspace.type === 'team' &&
|
/>
|
||||||
selectedTeam &&
|
<HoppButtonSecondary
|
||||||
selectedTeam?.myRole === 'OWNER'
|
v-if="
|
||||||
"
|
currentUser &&
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
workspace.type === 'team' &&
|
||||||
:title="t('team.edit')"
|
selectedTeam &&
|
||||||
:icon="IconSettings"
|
selectedTeam?.myRole === 'OWNER'
|
||||||
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 !text-green-500"
|
"
|
||||||
@click="handleTeamEdit()"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
/>
|
:title="t('team.edit')"
|
||||||
</div>
|
:icon="IconSettings"
|
||||||
<tippy
|
class="!focus-visible:text-emerald-600 !hover:text-emerald-600 !text-emerald-500"
|
||||||
interactive
|
@click="handleTeamEdit()"
|
||||||
trigger="click"
|
/>
|
||||||
theme="popover"
|
</div>
|
||||||
:on-shown="() => accountActions.focus()"
|
|
||||||
>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('workspace.change')"
|
|
||||||
:label="mdAndLarger ? activeWorkspaceName : ``"
|
|
||||||
:icon="activeWorkspaceIcon"
|
|
||||||
class="select-wrapper !focus-visible:text-blue-600 !hover:text-blue-600 rounded border border-blue-600/25 bg-blue-500/[.15] py-[0.4375rem] pr-8 !text-blue-500 hover:border-blue-800/50 hover:bg-blue-400/10 focus-visible:border-blue-800/50 focus-visible:bg-blue-400/10"
|
|
||||||
/>
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
ref="accountActions"
|
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
@click="hide()"
|
|
||||||
>
|
|
||||||
<WorkspaceSelector />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
<span class="px-2">
|
|
||||||
<tippy
|
<tippy
|
||||||
interactive
|
interactive
|
||||||
trigger="click"
|
trigger="click"
|
||||||
theme="popover"
|
theme="popover"
|
||||||
:on-shown="() => tippyActions.focus()"
|
:on-shown="() => accountActions.focus()"
|
||||||
>
|
>
|
||||||
<HoppSmartPicture
|
<HoppSmartSelectWrapper
|
||||||
v-if="currentUser.photoURL"
|
class="!text-blue-500 !focus-visible:text-blue-600 !hover:text-blue-600"
|
||||||
v-tippy="{
|
>
|
||||||
theme: 'tooltip',
|
<HoppButtonSecondary
|
||||||
}"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:url="currentUser.photoURL"
|
:title="t('workspace.change')"
|
||||||
:alt="
|
:label="mdAndLarger ? workspaceName : ``"
|
||||||
currentUser.displayName ||
|
:icon="workspace.type === 'personal' ? IconUser : IconUsers"
|
||||||
t('profile.default_hopp_displayname')
|
class="!focus-visible:text-blue-600 !hover:text-blue-600 h-8 rounded border border-blue-600/25 bg-blue-500/10 pr-8 !text-blue-500 hover:border-blue-600/20 hover:bg-blue-600/20 focus-visible:border-blue-600/20 focus-visible:bg-blue-600/20"
|
||||||
"
|
/>
|
||||||
:title="
|
</HoppSmartSelectWrapper>
|
||||||
currentUser.displayName ||
|
|
||||||
currentUser.email ||
|
|
||||||
t('profile.default_hopp_displayname')
|
|
||||||
"
|
|
||||||
indicator
|
|
||||||
:indicator-styles="
|
|
||||||
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartPicture
|
|
||||||
v-else
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="
|
|
||||||
currentUser.displayName ||
|
|
||||||
currentUser.email ||
|
|
||||||
t('profile.default_hopp_displayname')
|
|
||||||
"
|
|
||||||
:initial="currentUser.displayName || currentUser.email"
|
|
||||||
indicator
|
|
||||||
:indicator-styles="
|
|
||||||
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
<div
|
<div
|
||||||
ref="tippyActions"
|
ref="accountActions"
|
||||||
class="flex flex-col focus:outline-none"
|
class="flex flex-col focus:outline-none"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keyup.p="profile.$el.click()"
|
|
||||||
@keyup.s="settings.$el.click()"
|
|
||||||
@keyup.l="logout.$el.click()"
|
|
||||||
@keyup.escape="hide()"
|
@keyup.escape="hide()"
|
||||||
|
@click="hide()"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col px-2 text-tiny">
|
<WorkspaceSelector />
|
||||||
<span class="inline-flex truncate font-semibold">
|
|
||||||
{{
|
|
||||||
currentUser.displayName ||
|
|
||||||
t("profile.default_hopp_displayname")
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
<span class="inline-flex truncate text-secondaryLight">
|
|
||||||
{{ currentUser.email }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="profile"
|
|
||||||
to="/profile"
|
|
||||||
:icon="IconUser"
|
|
||||||
:label="t('navigation.profile')"
|
|
||||||
:shortcut="['P']"
|
|
||||||
@click="hide()"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="settings"
|
|
||||||
to="/settings"
|
|
||||||
:icon="IconSettings"
|
|
||||||
:label="t('navigation.settings')"
|
|
||||||
:shortcut="['S']"
|
|
||||||
@click="hide()"
|
|
||||||
/>
|
|
||||||
<FirebaseLogout
|
|
||||||
ref="logout"
|
|
||||||
:shortcut="['L']"
|
|
||||||
@confirm-logout="hide()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
</span>
|
<span v-if="currentUser" class="px-2">
|
||||||
|
<tippy
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => tippyActions.focus()"
|
||||||
|
>
|
||||||
|
<HoppSmartPicture
|
||||||
|
v-tippy="{
|
||||||
|
theme: 'tooltip',
|
||||||
|
}"
|
||||||
|
:name="currentUser.uid"
|
||||||
|
:title="
|
||||||
|
currentUser.displayName ||
|
||||||
|
currentUser.email ||
|
||||||
|
t('profile.default_hopp_displayname')
|
||||||
|
"
|
||||||
|
indicator
|
||||||
|
:indicator-styles="
|
||||||
|
network.isOnline ? 'bg-green-500' : 'bg-red-500'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="tippyActions"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.p="profile.$el.click()"
|
||||||
|
@keyup.s="settings.$el.click()"
|
||||||
|
@keyup.l="logout.$el.click()"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col px-2">
|
||||||
|
<span class="inline-flex truncate font-semibold">
|
||||||
|
{{
|
||||||
|
currentUser.displayName ||
|
||||||
|
t("profile.default_hopp_displayname")
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="inline-flex truncate text-secondaryLight text-tiny"
|
||||||
|
>
|
||||||
|
{{ currentUser.email }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="profile"
|
||||||
|
to="/profile"
|
||||||
|
:icon="IconUser"
|
||||||
|
:label="t('navigation.profile')"
|
||||||
|
:shortcut="['P']"
|
||||||
|
@click="hide()"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
ref="settings"
|
||||||
|
to="/settings"
|
||||||
|
:icon="IconSettings"
|
||||||
|
:label="t('navigation.settings')"
|
||||||
|
:shortcut="['S']"
|
||||||
|
@click="hide()"
|
||||||
|
/>
|
||||||
|
<FirebaseLogout
|
||||||
|
ref="logout"
|
||||||
|
:shortcut="['L']"
|
||||||
|
@confirm-logout="hide()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<AppBanner v-if="bannerContent" :banner="bannerContent" />
|
<AppBanner
|
||||||
|
v-if="bannerContent"
|
||||||
|
:banner="bannerContent"
|
||||||
|
@dismiss="dismissOfflineBanner"
|
||||||
|
/>
|
||||||
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
||||||
<TeamsInvite
|
<TeamsInvite
|
||||||
v-if="workspace.type === 'team' && workspace.teamID"
|
v-if="workspace.type === 'team' && workspace.teamID"
|
||||||
@@ -233,7 +231,6 @@
|
|||||||
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
||||||
@refetch-teams="refetchTeams"
|
@refetch-teams="refetchTeams"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmRemove"
|
:show="confirmRemove"
|
||||||
:title="t('confirm.remove_team')"
|
:title="t('confirm.remove_team')"
|
||||||
@@ -247,35 +244,40 @@
|
|||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
import { installPWA, pwaDefferedPrompt } from "@modules/pwa"
|
import { installPWA, pwaDefferedPrompt } from "@modules/pwa"
|
||||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import * as TE from "fp-ts/TaskEither"
|
|
||||||
import { pipe } from "fp-ts/function"
|
|
||||||
import { computed, reactive, ref, watch } from "vue"
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
|
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
||||||
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
|
||||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import {
|
|
||||||
BANNER_PRIORITY_HIGH,
|
|
||||||
BannerContent,
|
|
||||||
BannerService,
|
|
||||||
} from "~/services/banner.service"
|
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import IconDownload from "~icons/lucide/download"
|
import IconDownload from "~icons/lucide/download"
|
||||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||||
import IconSettings from "~icons/lucide/settings"
|
import IconSettings from "~icons/lucide/settings"
|
||||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
import IconUserPlus from "~icons/lucide/user-plus"
|
||||||
|
import IconUsers from "~icons/lucide/users"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
|
||||||
|
import {
|
||||||
|
BannerService,
|
||||||
|
BannerContent,
|
||||||
|
BANNER_PRIORITY_HIGH,
|
||||||
|
} from "~/services/banner.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flag to enable the workspace selector login conversion
|
||||||
|
*/
|
||||||
|
const workspaceSelectorFlagEnabled = computed(
|
||||||
|
() => !!platform.platformFeatureFlags.workspaceSwitcherLogin?.value
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Once the PWA code is initialized, this holds a method
|
* Once the PWA code is initialized, this holds a method
|
||||||
* that can be called to show the user the installation
|
* that can be called to show the user the installation
|
||||||
@@ -294,10 +296,11 @@ const bannerContent = computed(() => banner.content.value?.content)
|
|||||||
let bannerID: number | null = null
|
let bannerID: number | null = null
|
||||||
|
|
||||||
const offlineBanner: BannerContent = {
|
const offlineBanner: BannerContent = {
|
||||||
type: "info",
|
type: "warning",
|
||||||
text: (t) => t("helpers.offline"),
|
text: (t) => t("helpers.offline"),
|
||||||
alternateText: (t) => t("helpers.offline_short"),
|
alternateText: (t) => t("helpers.offline_short"),
|
||||||
score: BANNER_PRIORITY_HIGH,
|
score: BANNER_PRIORITY_HIGH,
|
||||||
|
dismissible: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const network = reactive(useNetwork())
|
const network = reactive(useNetwork())
|
||||||
@@ -314,6 +317,8 @@ watch(isOnline, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const dismissOfflineBanner = () => banner.removeBanner(bannerID!)
|
||||||
|
|
||||||
const currentUser = useReadonlyStream(
|
const currentUser = useReadonlyStream(
|
||||||
platform.auth.getProbableUserStream(),
|
platform.auth.getProbableUserStream(),
|
||||||
platform.auth.getProbableUser()
|
platform.auth.getProbableUser()
|
||||||
@@ -331,6 +336,12 @@ const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
|||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
|
const workspaceName = computed(() => {
|
||||||
|
return workspace.value.type === "personal"
|
||||||
|
? t("workspace.personal")
|
||||||
|
: workspace.value.teamName
|
||||||
|
})
|
||||||
|
|
||||||
const refetchTeams = () => {
|
const refetchTeams = () => {
|
||||||
teamListAdapter.fetchList()
|
teamListAdapter.fetchList()
|
||||||
}
|
}
|
||||||
@@ -365,23 +376,6 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const newWorkspaceService = useService(NewWorkspaceService)
|
|
||||||
|
|
||||||
const activeWorkspaceName = computed(() => {
|
|
||||||
const activeWorkspaceHandleRef =
|
|
||||||
newWorkspaceService.activeWorkspaceHandle.value?.get()
|
|
||||||
|
|
||||||
if (activeWorkspaceHandleRef?.value.type === "ok") {
|
|
||||||
return activeWorkspaceHandleRef.value.data.name
|
|
||||||
}
|
|
||||||
|
|
||||||
return t("workspace.no_workspace")
|
|
||||||
})
|
|
||||||
|
|
||||||
const activeWorkspaceIcon = computed(() => {
|
|
||||||
return newWorkspaceService.activeWorkspaceDecor.value?.value.headerCurrentIcon
|
|
||||||
})
|
|
||||||
|
|
||||||
const showModalInvite = ref(false)
|
const showModalInvite = ref(false)
|
||||||
const showModalEdit = ref(false)
|
const showModalEdit = ref(false)
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { defineStep } from "~/composables/step-components"
|
|||||||
|
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { restCollections$ } from "~/newstore/collections"
|
import { appendRESTCollections, restCollections$ } from "~/newstore/collections"
|
||||||
import MyCollectionImport from "~/components/importExport/ImportExportSteps/MyCollectionImport.vue"
|
import MyCollectionImport from "~/components/importExport/ImportExportSteps/MyCollectionImport.vue"
|
||||||
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
@@ -47,14 +47,13 @@ import { getTeamCollectionJSON } from "~/helpers/backend/helpers"
|
|||||||
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
||||||
import { gistExporter } from "~/helpers/import-export/export/gist"
|
import { gistExporter } from "~/helpers/import-export/export/gist"
|
||||||
|
import { myCollectionsExporter } from "~/helpers/import-export/export/myCollections"
|
||||||
import { teamCollectionsExporter } from "~/helpers/import-export/export/teamCollections"
|
import { teamCollectionsExporter } from "~/helpers/import-export/export/teamCollections"
|
||||||
|
|
||||||
import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource"
|
import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource"
|
||||||
import { ImporterOrExporter } from "~/components/importExport/types"
|
import { ImporterOrExporter } from "~/components/importExport/types"
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
|
||||||
import { TeamWorkspace } from "~/services/workspace.service"
|
import { TeamWorkspace } from "~/services/workspace.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -85,45 +84,15 @@ const currentUser = useReadonlyStream(
|
|||||||
|
|
||||||
const myCollections = useReadonlyStream(restCollections$, [])
|
const myCollections = useReadonlyStream(restCollections$, [])
|
||||||
|
|
||||||
const workspaceService = useService(NewWorkspaceService)
|
|
||||||
|
|
||||||
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle
|
|
||||||
|
|
||||||
const showImportFailedError = () => {
|
const showImportFailedError = () => {
|
||||||
toast.error(t("import.failed"))
|
toast.error(t("import.failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleImportToStore = async (collections: HoppCollection[]) => {
|
const handleImportToStore = async (collections: HoppCollection[]) => {
|
||||||
if (props.collectionsType.type === "my-collections") {
|
const importResult =
|
||||||
if (!activeWorkspaceHandle.value) {
|
props.collectionsType.type === "my-collections"
|
||||||
return
|
? await importToPersonalWorkspace(collections)
|
||||||
}
|
: await importToTeamsWorkspace(collections)
|
||||||
|
|
||||||
const collectionHandleResult = await workspaceService.importRESTCollections(
|
|
||||||
activeWorkspaceHandle.value,
|
|
||||||
collections
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(collectionHandleResult)) {
|
|
||||||
// INVALID_WORKSPACE_HANDLE
|
|
||||||
return toast.error(t("import.failed"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const resultHandle = collectionHandleResult.right
|
|
||||||
|
|
||||||
const requestHandleRef = resultHandle.get()
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
// WORKSPACE_INVALIDATED
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success(t("state.file_imported"))
|
|
||||||
emit("hide-modal")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const importResult = await importToTeamsWorkspace(collections)
|
|
||||||
|
|
||||||
if (E.isRight(importResult)) {
|
if (E.isRight(importResult)) {
|
||||||
toast.success(t("state.file_imported"))
|
toast.success(t("state.file_imported"))
|
||||||
@@ -133,6 +102,13 @@ const handleImportToStore = async (collections: HoppCollection[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const importToPersonalWorkspace = (collections: HoppCollection[]) => {
|
||||||
|
appendRESTCollections(collections)
|
||||||
|
return E.right({
|
||||||
|
success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function translateToTeamCollectionFormat(x: HoppCollection) {
|
function translateToTeamCollectionFormat(x: HoppCollection) {
|
||||||
const folders: HoppCollection[] = (x.folders ?? []).map(
|
const folders: HoppCollection[] = (x.folders ?? []).map(
|
||||||
translateToTeamCollectionFormat
|
translateToTeamCollectionFormat
|
||||||
@@ -412,34 +388,28 @@ const HoppMyCollectionsExporter: ImporterOrExporter = {
|
|||||||
applicableTo: ["personal-workspace"],
|
applicableTo: ["personal-workspace"],
|
||||||
isLoading: isHoppMyCollectionExporterInProgress,
|
isLoading: isHoppMyCollectionExporterInProgress,
|
||||||
},
|
},
|
||||||
action: async () => {
|
action: () => {
|
||||||
if (!myCollections.value.length) {
|
if (!myCollections.value.length) {
|
||||||
return toast.error(t("error.no_collections_to_export"))
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activeWorkspaceHandle.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isHoppMyCollectionExporterInProgress.value = true
|
isHoppMyCollectionExporterInProgress.value = true
|
||||||
|
|
||||||
const result = await workspaceService.exportRESTCollections(
|
const message = initializeDownloadCollection(
|
||||||
activeWorkspaceHandle.value,
|
myCollectionsExporter(myCollections.value),
|
||||||
myCollections.value
|
"Collections"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
if (E.isRight(message)) {
|
||||||
// INVALID_WORKSPACE_HANDLE
|
toast.success(t(message.right))
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_EXPORT_COLLECTION",
|
||||||
|
exporter: "json",
|
||||||
|
platform: "rest",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success(t("state.download_started"))
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
|
||||||
exporter: "json",
|
|
||||||
platform: "rest",
|
|
||||||
})
|
|
||||||
|
|
||||||
isHoppMyCollectionExporterInProgress.value = false
|
isHoppMyCollectionExporterInProgress.value = false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -473,7 +443,10 @@ const HoppTeamCollectionsExporter: ImporterOrExporter = {
|
|||||||
return toast.error(t("error.no_collections_to_export"))
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeDownloadFile(exportCollectionsToJSON, "team-collections")
|
initializeDownloadCollection(
|
||||||
|
exportCollectionsToJSON,
|
||||||
|
"team-collections"
|
||||||
|
)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
type: "HOPP_EXPORT_COLLECTION",
|
||||||
@@ -512,7 +485,7 @@ const HoppGistCollectionsExporter: ImporterOrExporter = {
|
|||||||
const collectionJSON = await getCollectionJSON()
|
const collectionJSON = await getCollectionJSON()
|
||||||
const accessToken = currentUser.value?.accessToken
|
const accessToken = currentUser.value?.accessToken
|
||||||
|
|
||||||
if (!accessToken || E.isLeft(collectionJSON)) {
|
if (!accessToken) {
|
||||||
toast.error(t("error.something_went_wrong"))
|
toast.error(t("error.something_went_wrong"))
|
||||||
isHoppGistCollectionExporterInProgress.value = false
|
isHoppGistCollectionExporterInProgress.value = false
|
||||||
return
|
return
|
||||||
@@ -608,7 +581,6 @@ const selectedTeamID = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const getCollectionJSON = async () => {
|
const getCollectionJSON = async () => {
|
||||||
// TODO: Implement `getRESTCollectionJSONView` for team workspace
|
|
||||||
if (
|
if (
|
||||||
props.collectionsType.type === "team-collections" &&
|
props.collectionsType.type === "team-collections" &&
|
||||||
props.collectionsType.selectedTeam?.teamID
|
props.collectionsType.selectedTeam?.teamID
|
||||||
@@ -619,33 +591,11 @@ const getCollectionJSON = async () => {
|
|||||||
|
|
||||||
return E.isRight(res)
|
return E.isRight(res)
|
||||||
? E.right(res.right.exportCollectionsToJSON)
|
? E.right(res.right.exportCollectionsToJSON)
|
||||||
: E.left(res.left.error.toString())
|
: E.left(res.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.collectionsType.type === "my-collections") {
|
if (props.collectionsType.type === "my-collections") {
|
||||||
if (!activeWorkspaceHandle.value) {
|
return E.right(JSON.stringify(myCollections.value, null, 2))
|
||||||
return E.left("INVALID_WORKSPACE_HANDLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionJSONHandleResult =
|
|
||||||
await workspaceService.getRESTCollectionJSONView(
|
|
||||||
activeWorkspaceHandle.value
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(collectionJSONHandleResult)) {
|
|
||||||
return E.left(collectionJSONHandleResult.left.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionJSONHandle = collectionJSONHandleResult.right
|
|
||||||
|
|
||||||
const collectionJSONHandleRef = collectionJSONHandle.get()
|
|
||||||
|
|
||||||
if (collectionJSONHandleRef.value.type === "invalid") {
|
|
||||||
// WORKSPACE_INVALIDATED
|
|
||||||
return E.left("WORKSPACE_INVALIDATED")
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(collectionJSONHandleRef.value.data.content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return E.left("INVALID_SELECTED_TEAM_OR_INVALID_COLLECTION_TYPE")
|
return E.left("INVALID_SELECTED_TEAM_OR_INVALID_COLLECTION_TYPE")
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ import {
|
|||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { clone } from "lodash-es"
|
import { clone } from "lodash-es"
|
||||||
import { ref, toRefs, watch } from "vue"
|
import { ref, watch } from "vue"
|
||||||
|
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
import { PersistenceService } from "~/services/persistence"
|
import { PersistenceService } from "~/services/persistence"
|
||||||
@@ -100,15 +100,11 @@ const props = withDefaults(
|
|||||||
editingProperties: EditingProperties | null
|
editingProperties: EditingProperties | null
|
||||||
source: "REST" | "GraphQL"
|
source: "REST" | "GraphQL"
|
||||||
modelValue: string
|
modelValue: string
|
||||||
// TODO: Purpose of this prop is to maintain backwards compatibility
|
|
||||||
// To be removed after porting all usages of this component
|
|
||||||
emitWithFullCollection: boolean
|
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
show: false,
|
show: false,
|
||||||
loadingState: false,
|
loadingState: false,
|
||||||
editingProperties: null,
|
editingProperties: null,
|
||||||
emitWithFullCollection: true,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -182,24 +178,15 @@ watch(
|
|||||||
const saveEditedCollection = () => {
|
const saveEditedCollection = () => {
|
||||||
if (!props.editingProperties) return
|
if (!props.editingProperties) return
|
||||||
const finalCollection = clone(editableCollection.value)
|
const finalCollection = clone(editableCollection.value)
|
||||||
delete finalCollection.body
|
|
||||||
|
|
||||||
const { path } = toRefs(props.editingProperties)
|
|
||||||
|
|
||||||
const collection = {
|
const collection = {
|
||||||
path: path.value,
|
path: props.editingProperties.path,
|
||||||
collection: {
|
collection: {
|
||||||
...props.editingProperties.collection,
|
...props.editingProperties.collection,
|
||||||
...finalCollection,
|
...finalCollection,
|
||||||
},
|
},
|
||||||
isRootCollection: props.editingProperties.isRootCollection,
|
isRootCollection: props.editingProperties.isRootCollection,
|
||||||
}
|
}
|
||||||
|
emit("set-collection-properties", collection as EditingProperties)
|
||||||
const data = props.emitWithFullCollection
|
|
||||||
? collection
|
|
||||||
: { ...finalCollection, collIndexPath: path.value }
|
|
||||||
emit("set-collection-properties", data as EditingProperties)
|
|
||||||
|
|
||||||
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
persistenceService.removeLocalConfig("unsaved_collection_properties")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,25 +20,19 @@
|
|||||||
<label class="p-4">
|
<label class="p-4">
|
||||||
{{ t("collection.select_location") }}
|
{{ t("collection.select_location") }}
|
||||||
</label>
|
</label>
|
||||||
<!-- <CollectionsGraphql
|
<CollectionsGraphql
|
||||||
v-if="mode === 'graphql'"
|
v-if="mode === 'graphql'"
|
||||||
:picked="picked"
|
:picked="picked"
|
||||||
:save-request="true"
|
:save-request="true"
|
||||||
@select="onSelect"
|
@select="onSelect"
|
||||||
/> -->
|
/>
|
||||||
<!-- <Collections
|
<Collections
|
||||||
v-else
|
v-else
|
||||||
:picked="picked"
|
:picked="picked"
|
||||||
:save-request="true"
|
:save-request="true"
|
||||||
@select="onSelect"
|
@select="onSelect"
|
||||||
@update-team="updateTeam"
|
@update-team="updateTeam"
|
||||||
@update-collection-type="updateCollectionType"
|
@update-collection-type="updateCollectionType"
|
||||||
/> -->
|
|
||||||
<NewCollections
|
|
||||||
:picked="picked"
|
|
||||||
:save-request="true"
|
|
||||||
platform="rest"
|
|
||||||
@select="onSelect"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -71,31 +65,40 @@ import {
|
|||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { computedWithControl } from "@vueuse/core"
|
import { computedWithControl } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import * as E from "fp-ts/Either"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
|
import { pipe } from "fp-ts/function"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { computed, nextTick, reactive, ref, watch } from "vue"
|
import { computed, nextTick, reactive, ref, watch } from "vue"
|
||||||
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
|
import {
|
||||||
|
createRequestInCollection,
|
||||||
|
updateTeamRequest,
|
||||||
|
} from "~/helpers/backend/mutations/TeamRequest"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
import { cascadeParentCollectionForHeaderAuth } from "~/newstore/collections"
|
import {
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
cascadeParentCollectionForHeaderAuth,
|
||||||
|
editGraphqlRequest,
|
||||||
|
editRESTRequest,
|
||||||
|
saveGraphqlRequestAs,
|
||||||
|
saveRESTRequestAs,
|
||||||
|
} from "~/newstore/collections"
|
||||||
|
import { platform } from "~/platform"
|
||||||
import { GQLTabService } from "~/services/tab/graphql"
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { TeamWorkspace } from "~/services/workspace.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const RESTTabs = useService(RESTTabService)
|
const RESTTabs = useService(RESTTabService)
|
||||||
const GQLTabs = useService(GQLTabService)
|
const GQLTabs = useService(GQLTabService)
|
||||||
const workspaceService = useService(NewWorkspaceService)
|
|
||||||
|
|
||||||
// type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
type CollectionType =
|
||||||
|
| {
|
||||||
// type CollectionType =
|
type: "team-collections"
|
||||||
// | {
|
selectedTeam: TeamWorkspace
|
||||||
// type: "team-collections"
|
}
|
||||||
// selectedTeam: SelectedTeam
|
| { type: "my-collections"; selectedTeam: undefined }
|
||||||
// }
|
|
||||||
// | { type: "my-collections"; selectedTeam: undefined }
|
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -163,10 +166,10 @@ const requestData = reactive({
|
|||||||
requestIndex: undefined as number | undefined,
|
requestIndex: undefined as number | undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
// const collectionsType = ref<CollectionType>({
|
const collectionsType = ref<CollectionType>({
|
||||||
// type: "my-collections",
|
type: "my-collections",
|
||||||
// selectedTeam: undefined,
|
selectedTeam: undefined,
|
||||||
// })
|
})
|
||||||
|
|
||||||
const picked = ref<Picked | null>(null)
|
const picked = ref<Picked | null>(null)
|
||||||
|
|
||||||
@@ -187,14 +190,13 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: To be removed
|
const updateTeam = (newTeam: TeamWorkspace) => {
|
||||||
// const updateTeam = (newTeam: SelectedTeam) => {
|
collectionsType.value.selectedTeam = newTeam
|
||||||
// collectionsType.value.selectedTeam = newTeam
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// const updateCollectionType = (type: CollectionType["type"]) => {
|
const updateCollectionType = (type: CollectionType["type"]) => {
|
||||||
// collectionsType.value.type = type
|
collectionsType.value.type = type
|
||||||
// }
|
}
|
||||||
|
|
||||||
const onSelect = (pickedVal: Picked | null) => {
|
const onSelect = (pickedVal: Picked | null) => {
|
||||||
picked.value = pickedVal
|
picked.value = pickedVal
|
||||||
@@ -210,109 +212,66 @@ const saveRequestAs = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedRequest =
|
const requestUpdated =
|
||||||
props.mode === "rest"
|
props.mode === "rest"
|
||||||
? cloneDeep(RESTTabs.currentActiveTab.value.document.request)
|
? cloneDeep(RESTTabs.currentActiveTab.value.document.request)
|
||||||
: cloneDeep(GQLTabs.currentActiveTab.value.document.request)
|
: cloneDeep(GQLTabs.currentActiveTab.value.document.request)
|
||||||
|
|
||||||
updatedRequest.name = requestName.value
|
requestUpdated.name = requestName.value
|
||||||
|
|
||||||
if (!workspaceService.activeWorkspaceHandle.value) {
|
if (picked.value.pickedType === "my-collection") {
|
||||||
return
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
picked.value.pickedType === "my-collection" ||
|
|
||||||
picked.value.pickedType === "my-folder"
|
|
||||||
) {
|
|
||||||
if (!isHoppRESTRequest(updatedRequest))
|
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
const collectionPathIndex =
|
const insertionIndex = saveRESTRequestAs(
|
||||||
picked.value.pickedType === "my-collection"
|
`${picked.value.collectionIndex}`,
|
||||||
? picked.value.collectionIndex.toString()
|
requestUpdated
|
||||||
: picked.value.folderPath
|
|
||||||
|
|
||||||
const collectionHandleResult = await workspaceService.getCollectionHandle(
|
|
||||||
workspaceService.activeWorkspaceHandle.value,
|
|
||||||
collectionPathIndex
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (E.isLeft(collectionHandleResult)) {
|
|
||||||
// INVALID_WORKSPACE_HANDLE | INVALID_COLLECTION_ID | INVALID_PATH
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionHandle = collectionHandleResult.right
|
|
||||||
|
|
||||||
const requestHandleResult = await workspaceService.createRESTRequest(
|
|
||||||
collectionHandle,
|
|
||||||
updatedRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(requestHandleResult)) {
|
|
||||||
// WORKSPACE_INVALIDATED | INVALID_COLLECTION_HANDLE
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestHandle = requestHandleResult.right
|
|
||||||
|
|
||||||
const requestHandleRef = requestHandle.get()
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
// WORKSPACE_INVALIDATED | INVALID_COLLECTION_HANDLE
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
RESTTabs.currentActiveTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: updatedRequest,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "workspace-user-collection",
|
originLocation: "user-collection",
|
||||||
requestHandle,
|
folderPath: `${picked.value.collectionIndex}`,
|
||||||
|
requestIndex: insertionIndex,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
|
`${picked.value.collectionIndex}`,
|
||||||
|
"rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
RESTTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||||
|
auth,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: true,
|
||||||
|
platform: "rest",
|
||||||
|
workspaceType: "personal",
|
||||||
|
})
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
} else if (picked.value.pickedType === "my-request") {
|
} else if (picked.value.pickedType === "my-folder") {
|
||||||
if (!isHoppRESTRequest(updatedRequest))
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
const requestHandleResult = await workspaceService.getRequestHandle(
|
const insertionIndex = saveRESTRequestAs(
|
||||||
workspaceService.activeWorkspaceHandle.value,
|
picked.value.folderPath,
|
||||||
`${picked.value.folderPath}/${picked.value.requestIndex.toString()}`
|
requestUpdated
|
||||||
)
|
)
|
||||||
|
|
||||||
if (E.isLeft(requestHandleResult)) {
|
|
||||||
// INVALID_COLLECTION_HANDLE | INVALID_REQUEST_ID | REQUEST_NOT_FOUND
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestHandle = requestHandleResult.right
|
|
||||||
|
|
||||||
const requestHandleRef = requestHandle.get()
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
// WORKSPACE_INVALIDATED
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateRequestResult = await workspaceService.updateRESTRequest(
|
|
||||||
requestHandle,
|
|
||||||
updatedRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(updateRequestResult)) {
|
|
||||||
// WORKSPACE_INVALIDATED | INVALID_REQUEST_HANDLE
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
RESTTabs.currentActiveTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
request: updatedRequest,
|
request: requestUpdated,
|
||||||
isDirty: false,
|
isDirty: false,
|
||||||
saveContext: {
|
saveContext: {
|
||||||
originLocation: "workspace-user-collection",
|
originLocation: "user-collection",
|
||||||
requestHandle,
|
folderPath: picked.value.folderPath,
|
||||||
|
requestIndex: insertionIndex,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,147 +285,190 @@ const saveRequestAs = async () => {
|
|||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: true,
|
||||||
|
platform: "rest",
|
||||||
|
workspaceType: "personal",
|
||||||
|
})
|
||||||
|
|
||||||
|
requestSaved()
|
||||||
|
} else if (picked.value.pickedType === "my-request") {
|
||||||
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
|
editRESTRequest(
|
||||||
|
picked.value.folderPath,
|
||||||
|
picked.value.requestIndex,
|
||||||
|
requestUpdated
|
||||||
|
)
|
||||||
|
|
||||||
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
|
request: requestUpdated,
|
||||||
|
isDirty: false,
|
||||||
|
saveContext: {
|
||||||
|
originLocation: "user-collection",
|
||||||
|
folderPath: picked.value.folderPath,
|
||||||
|
requestIndex: picked.value.requestIndex,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
|
picked.value.folderPath,
|
||||||
|
"rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
RESTTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||||
|
auth,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: false,
|
||||||
|
platform: "rest",
|
||||||
|
workspaceType: "personal",
|
||||||
|
})
|
||||||
|
|
||||||
|
requestSaved()
|
||||||
|
} else if (picked.value.pickedType === "teams-collection") {
|
||||||
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
|
updateTeamCollectionOrFolder(picked.value.collectionID, requestUpdated)
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: true,
|
||||||
|
platform: "rest",
|
||||||
|
workspaceType: "team",
|
||||||
|
})
|
||||||
|
} else if (picked.value.pickedType === "teams-folder") {
|
||||||
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
|
updateTeamCollectionOrFolder(picked.value.folderID, requestUpdated)
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: true,
|
||||||
|
platform: "rest",
|
||||||
|
workspaceType: "team",
|
||||||
|
})
|
||||||
|
} else if (picked.value.pickedType === "teams-request") {
|
||||||
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
|
if (
|
||||||
|
collectionsType.value.type !== "team-collections" ||
|
||||||
|
!collectionsType.value.selectedTeam
|
||||||
|
)
|
||||||
|
throw new Error("Collections Type mismatch")
|
||||||
|
|
||||||
|
modalLoadingState.value = true
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
request: JSON.stringify(requestUpdated),
|
||||||
|
title: requestUpdated.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: false,
|
||||||
|
platform: "rest",
|
||||||
|
workspaceType: "team",
|
||||||
|
})
|
||||||
|
|
||||||
|
pipe(
|
||||||
|
updateTeamRequest(picked.value.requestID, data),
|
||||||
|
TE.match(
|
||||||
|
(err: GQLError<string>) => {
|
||||||
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
|
modalLoadingState.value = false
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
modalLoadingState.value = false
|
||||||
|
requestSaved()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
} else if (picked.value.pickedType === "gql-my-request") {
|
||||||
|
// TODO: Check for GQL request ?
|
||||||
|
editGraphqlRequest(
|
||||||
|
picked.value.folderPath,
|
||||||
|
picked.value.requestIndex,
|
||||||
|
requestUpdated as HoppGQLRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: false,
|
||||||
|
platform: "gql",
|
||||||
|
workspaceType: "team",
|
||||||
|
})
|
||||||
|
|
||||||
|
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
|
picked.value.folderPath,
|
||||||
|
"graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||||
|
auth,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
requestSaved()
|
||||||
|
} else if (picked.value.pickedType === "gql-my-folder") {
|
||||||
|
// TODO: Check for GQL request ?
|
||||||
|
saveGraphqlRequestAs(
|
||||||
|
picked.value.folderPath,
|
||||||
|
requestUpdated as HoppGQLRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: true,
|
||||||
|
platform: "gql",
|
||||||
|
workspaceType: "team",
|
||||||
|
})
|
||||||
|
|
||||||
|
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
|
picked.value.folderPath,
|
||||||
|
"graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||||
|
auth,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
requestSaved()
|
||||||
|
} else if (picked.value.pickedType === "gql-my-collection") {
|
||||||
|
// TODO: Check for GQL request ?
|
||||||
|
saveGraphqlRequestAs(
|
||||||
|
`${picked.value.collectionIndex}`,
|
||||||
|
requestUpdated as HoppGQLRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
platform.analytics?.logEvent({
|
||||||
|
type: "HOPP_SAVE_REQUEST",
|
||||||
|
createdNow: true,
|
||||||
|
platform: "gql",
|
||||||
|
workspaceType: "team",
|
||||||
|
})
|
||||||
|
|
||||||
|
const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
||||||
|
`${picked.value.collectionIndex}`,
|
||||||
|
"graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
||||||
|
auth,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
}
|
}
|
||||||
// TODO: To be removed
|
|
||||||
// else if (picked.value.pickedType === "teams-collection") {
|
|
||||||
// if (!isHoppRESTRequest(updatedRequest))
|
|
||||||
// throw new Error("requestUpdated is not a REST Request")
|
|
||||||
|
|
||||||
// updateTeamCollectionOrFolder(picked.value.collectionID, updatedRequest)
|
|
||||||
|
|
||||||
// platform.analytics?.logEvent({
|
|
||||||
// type: "HOPP_SAVE_REQUEST",
|
|
||||||
// createdNow: true,
|
|
||||||
// platform: "rest",
|
|
||||||
// workspaceType: "team",
|
|
||||||
// })
|
|
||||||
// } else if (picked.value.pickedType === "teams-folder") {
|
|
||||||
// if (!isHoppRESTRequest(updatedRequest))
|
|
||||||
// throw new Error("requestUpdated is not a REST Request")
|
|
||||||
|
|
||||||
// updateTeamCollectionOrFolder(picked.value.folderID, updatedRequest)
|
|
||||||
|
|
||||||
// platform.analytics?.logEvent({
|
|
||||||
// type: "HOPP_SAVE_REQUEST",
|
|
||||||
// createdNow: true,
|
|
||||||
// platform: "rest",
|
|
||||||
// workspaceType: "team",
|
|
||||||
// })
|
|
||||||
// } else if (picked.value.pickedType === "teams-request") {
|
|
||||||
// if (!isHoppRESTRequest(updatedRequest))
|
|
||||||
// throw new Error("requestUpdated is not a REST Request")
|
|
||||||
|
|
||||||
// if (
|
|
||||||
// collectionsType.value.type !== "team-collections" ||
|
|
||||||
// !collectionsType.value.selectedTeam
|
|
||||||
// )
|
|
||||||
// throw new Error("Collections Type mismatch")
|
|
||||||
|
|
||||||
// modalLoadingState.value = true
|
|
||||||
|
|
||||||
// const data = {
|
|
||||||
// request: JSON.stringify(updatedRequest),
|
|
||||||
// title: updatedRequest.name,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// platform.analytics?.logEvent({
|
|
||||||
// type: "HOPP_SAVE_REQUEST",
|
|
||||||
// createdNow: false,
|
|
||||||
// platform: "rest",
|
|
||||||
// workspaceType: "team",
|
|
||||||
// })
|
|
||||||
|
|
||||||
// pipe(
|
|
||||||
// updateTeamRequest(picked.value.requestID, data),
|
|
||||||
// TE.match(
|
|
||||||
// (err: GQLError<string>) => {
|
|
||||||
// toast.error(`${getErrorMessage(err)}`)
|
|
||||||
// modalLoadingState.value = false
|
|
||||||
// },
|
|
||||||
// () => {
|
|
||||||
// modalLoadingState.value = false
|
|
||||||
// requestSaved()
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// )()
|
|
||||||
// } else if (picked.value.pickedType === "gql-my-request") {
|
|
||||||
// // TODO: Check for GQL request ?
|
|
||||||
// editGraphqlRequest(
|
|
||||||
// picked.value.folderPath,
|
|
||||||
// picked.value.requestIndex,
|
|
||||||
// updatedRequest as HoppGQLRequest
|
|
||||||
// )
|
|
||||||
|
|
||||||
// platform.analytics?.logEvent({
|
|
||||||
// type: "HOPP_SAVE_REQUEST",
|
|
||||||
// createdNow: false,
|
|
||||||
// platform: "gql",
|
|
||||||
// workspaceType: "team",
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
|
||||||
// picked.value.folderPath,
|
|
||||||
// "graphql"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
|
||||||
// auth,
|
|
||||||
// headers,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// requestSaved()
|
|
||||||
// } else if (picked.value.pickedType === "gql-my-folder") {
|
|
||||||
// // TODO: Check for GQL request ?
|
|
||||||
// saveGraphqlRequestAs(
|
|
||||||
// picked.value.folderPath,
|
|
||||||
// updatedRequest as HoppGQLRequest
|
|
||||||
// )
|
|
||||||
|
|
||||||
// platform.analytics?.logEvent({
|
|
||||||
// type: "HOPP_SAVE_REQUEST",
|
|
||||||
// createdNow: true,
|
|
||||||
// platform: "gql",
|
|
||||||
// workspaceType: "team",
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
|
||||||
// picked.value.folderPath,
|
|
||||||
// "graphql"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
|
||||||
// auth,
|
|
||||||
// headers,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// requestSaved()
|
|
||||||
// } else if (picked.value.pickedType === "gql-my-collection") {
|
|
||||||
// // TODO: Check for GQL request ?
|
|
||||||
// saveGraphqlRequestAs(
|
|
||||||
// `${picked.value.collectionIndex}`,
|
|
||||||
// updatedRequest as HoppGQLRequest
|
|
||||||
// )
|
|
||||||
|
|
||||||
// platform.analytics?.logEvent({
|
|
||||||
// type: "HOPP_SAVE_REQUEST",
|
|
||||||
// createdNow: true,
|
|
||||||
// platform: "gql",
|
|
||||||
// workspaceType: "team",
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const { auth, headers } = cascadeParentCollectionForHeaderAuth(
|
|
||||||
// `${picked.value.collectionIndex}`,
|
|
||||||
// "graphql"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// GQLTabs.currentActiveTab.value.document.inheritedProperties = {
|
|
||||||
// auth,
|
|
||||||
// headers,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// requestSaved()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -474,50 +476,50 @@ const saveRequestAs = async () => {
|
|||||||
* @param collectionID - ID of the collection or folder
|
* @param collectionID - ID of the collection or folder
|
||||||
* @param requestUpdated - Updated request
|
* @param requestUpdated - Updated request
|
||||||
*/
|
*/
|
||||||
// const updateTeamCollectionOrFolder = (
|
const updateTeamCollectionOrFolder = (
|
||||||
// collectionID: string,
|
collectionID: string,
|
||||||
// requestUpdated: HoppRESTRequest
|
requestUpdated: HoppRESTRequest
|
||||||
// ) => {
|
) => {
|
||||||
// if (
|
if (
|
||||||
// collectionsType.value.type !== "team-collections" ||
|
collectionsType.value.type !== "team-collections" ||
|
||||||
// !collectionsType.value.selectedTeam
|
!collectionsType.value.selectedTeam
|
||||||
// )
|
)
|
||||||
// throw new Error("Collections Type mismatch")
|
throw new Error("Collections Type mismatch")
|
||||||
|
|
||||||
// modalLoadingState.value = true
|
modalLoadingState.value = true
|
||||||
|
|
||||||
// const data = {
|
const data = {
|
||||||
// title: requestUpdated.name,
|
title: requestUpdated.name,
|
||||||
// request: JSON.stringify(requestUpdated),
|
request: JSON.stringify(requestUpdated),
|
||||||
// teamID: collectionsType.value.selectedTeam.id,
|
teamID: collectionsType.value.selectedTeam.teamID,
|
||||||
// }
|
}
|
||||||
// pipe(
|
pipe(
|
||||||
// createRequestInCollection(collectionID, data),
|
createRequestInCollection(collectionID, data),
|
||||||
// TE.match(
|
TE.match(
|
||||||
// (err: GQLError<string>) => {
|
(err: GQLError<string>) => {
|
||||||
// toast.error(`${getErrorMessage(err)}`)
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
// modalLoadingState.value = false
|
modalLoadingState.value = false
|
||||||
// },
|
},
|
||||||
// (result) => {
|
(result) => {
|
||||||
// const { createRequestInCollection } = result
|
const { createRequestInCollection } = result
|
||||||
|
|
||||||
// RESTTabs.currentActiveTab.value.document = {
|
RESTTabs.currentActiveTab.value.document = {
|
||||||
// request: requestUpdated,
|
request: requestUpdated,
|
||||||
// isDirty: false,
|
isDirty: false,
|
||||||
// saveContext: {
|
saveContext: {
|
||||||
// originLocation: "team-collection",
|
originLocation: "team-collection",
|
||||||
// requestID: createRequestInCollection.id,
|
requestID: createRequestInCollection.id,
|
||||||
// collectionID: createRequestInCollection.collection.id,
|
collectionID: createRequestInCollection.collection.id,
|
||||||
// teamID: createRequestInCollection.collection.team.id,
|
teamID: createRequestInCollection.collection.team.id,
|
||||||
// },
|
},
|
||||||
// }
|
}
|
||||||
|
|
||||||
// modalLoadingState.value = false
|
modalLoadingState.value = false
|
||||||
// requestSaved()
|
requestSaved()
|
||||||
// }
|
}
|
||||||
// )
|
)
|
||||||
// )()
|
)()
|
||||||
// }
|
}
|
||||||
|
|
||||||
const requestSaved = () => {
|
const requestSaved = () => {
|
||||||
toast.success(`${t("request.added")}`)
|
toast.success(`${t("request.added")}`)
|
||||||
@@ -532,24 +534,24 @@ const hideModal = () => {
|
|||||||
emit("hide-modal")
|
emit("hide-modal")
|
||||||
}
|
}
|
||||||
|
|
||||||
// const getErrorMessage = (err: GQLError<string>) => {
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
// console.error(err)
|
console.error(err)
|
||||||
// if (err.type === "network_error") {
|
if (err.type === "network_error") {
|
||||||
// return t("error.network_error")
|
return t("error.network_error")
|
||||||
// }
|
}
|
||||||
// switch (err.error) {
|
switch (err.error) {
|
||||||
// case "team_coll/short_title":
|
case "team_coll/short_title":
|
||||||
// return t("collection.name_length_insufficient")
|
return t("collection.name_length_insufficient")
|
||||||
// case "team/invalid_coll_id":
|
case "team/invalid_coll_id":
|
||||||
// return t("team.invalid_id")
|
return t("team.invalid_id")
|
||||||
// case "team/not_required_role":
|
case "team/not_required_role":
|
||||||
// return t("profile.no_permission")
|
return t("profile.no_permission")
|
||||||
// case "team_req/not_required_role":
|
case "team_req/not_required_role":
|
||||||
// return t("profile.no_permission")
|
return t("profile.no_permission")
|
||||||
// case "Forbidden resource":
|
case "Forbidden resource":
|
||||||
// return t("profile.no_permission")
|
return t("profile.no_permission")
|
||||||
// default:
|
default:
|
||||||
// return t("error.something_went_wrong")
|
return t("error.something_went_wrong")
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { GistSource } from "~/helpers/import-export/import/import-sources/GistSo
|
|||||||
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
@@ -133,12 +133,12 @@ const GqlCollectionsHoppExporter: ImporterOrExporter = {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
applicableTo: ["personal-workspace", "team-workspace"],
|
applicableTo: ["personal-workspace", "team-workspace"],
|
||||||
},
|
},
|
||||||
action: async () => {
|
action: () => {
|
||||||
if (!gqlCollections.value.length) {
|
if (!gqlCollections.value.length) {
|
||||||
return toast.error(t("error.no_collections_to_export"))
|
return toast.error(t("error.no_collections_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = await initializeDownloadFile(
|
const message = initializeDownloadCollection(
|
||||||
gqlCollectionsExporter(gqlCollections.value),
|
gqlCollectionsExporter(gqlCollections.value),
|
||||||
"GQLCollections"
|
"GQLCollections"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import IconFolderPlus from "~icons/lucide/folder-plus"
|
|||||||
import IconPostman from "~icons/hopp/postman"
|
import IconPostman from "~icons/hopp/postman"
|
||||||
import IconInsomnia from "~icons/hopp/insomnia"
|
import IconInsomnia from "~icons/hopp/insomnia"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import { initializeDownloadFile } from "~/helpers/import-export/export"
|
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
import { environmentsExporter } from "~/helpers/import-export/export/environments"
|
import { environmentsExporter } from "~/helpers/import-export/export/environments"
|
||||||
@@ -230,12 +230,12 @@ const HoppEnvironmentsExport: ImporterOrExporter = {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
applicableTo: ["personal-workspace", "team-workspace"],
|
applicableTo: ["personal-workspace", "team-workspace"],
|
||||||
},
|
},
|
||||||
action: async () => {
|
action: () => {
|
||||||
if (!environmentJson.value.length) {
|
if (!environmentJson.value.length) {
|
||||||
return toast.error(t("error.no_environments_to_export"))
|
return toast.error(t("error.no_environments_to_export"))
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = await initializeDownloadFile(
|
const message = initializeDownloadCollection(
|
||||||
environmentsExporter(environmentJson.value),
|
environmentsExporter(environmentJson.value),
|
||||||
"Environments"
|
"Environments"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -236,28 +236,16 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useSetting } from "@composables/settings"
|
import { useSetting } from "@composables/settings"
|
||||||
import { useReadonlyStream, useStreamSubscriber } from "@composables/stream"
|
import { useReadonlyStream, useStreamSubscriber } from "@composables/stream"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { Ref, computed, onUnmounted, ref } from "vue"
|
import { Ref, computed, ref, onUnmounted } from "vue"
|
||||||
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
import { runMutation } from "~/helpers/backend/GQLClient"
|
import { runMutation } from "~/helpers/backend/GQLClient"
|
||||||
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
import { UpdateRequestDocument } from "~/helpers/backend/graphql"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
|
||||||
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
|
||||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
import { editRESTRequest } from "~/newstore/collections"
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { InspectionService } from "~/services/inspection"
|
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
|
||||||
import { HoppTab } from "~/services/tab"
|
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import IconChevronDown from "~icons/lucide/chevron-down"
|
import IconChevronDown from "~icons/lucide/chevron-down"
|
||||||
import IconCode2 from "~icons/lucide/code-2"
|
import IconCode2 from "~icons/lucide/code-2"
|
||||||
import IconFileCode from "~icons/lucide/file-code"
|
import IconFileCode from "~icons/lucide/file-code"
|
||||||
@@ -265,10 +253,21 @@ import IconFolderPlus from "~icons/lucide/folder-plus"
|
|||||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
||||||
import IconSave from "~icons/lucide/save"
|
import IconSave from "~icons/lucide/save"
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
|
import { RESTHistoryEntry, restHistory$ } from "~/newstore/history"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { InspectionService } from "~/services/inspection"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { getMethodLabelColor } from "~/helpers/rest/labelColoring"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const interceptorService = useService(InterceptorService)
|
const interceptorService = useService(InterceptorService)
|
||||||
const newWorkspaceService = useService(NewWorkspaceService)
|
|
||||||
|
|
||||||
const methods = [
|
const methods = [
|
||||||
"GET",
|
"GET",
|
||||||
@@ -507,61 +506,34 @@ const cycleDownMethod = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveRequest = async () => {
|
const saveRequest = () => {
|
||||||
const { saveContext } = tab.value.document
|
const saveCtx = tab.value.document.saveContext
|
||||||
|
|
||||||
if (!saveContext) {
|
if (!saveCtx) {
|
||||||
showSaveRequestModal.value = true
|
showSaveRequestModal.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (saveCtx.originLocation === "user-collection") {
|
||||||
|
const req = tab.value.document.request
|
||||||
|
|
||||||
if (saveContext.originLocation === "workspace-user-collection") {
|
try {
|
||||||
const updatedRequest = tab.value.document.request
|
editRESTRequest(saveCtx.folderPath, saveCtx.requestIndex, req)
|
||||||
|
|
||||||
if (
|
tab.value.document.isDirty = false
|
||||||
!newWorkspaceService.activeWorkspaceHandle.value ||
|
|
||||||
!saveContext.requestHandle
|
platform.analytics?.logEvent({
|
||||||
) {
|
type: "HOPP_SAVE_REQUEST",
|
||||||
return
|
platform: "rest",
|
||||||
|
createdNow: false,
|
||||||
|
workspaceType: "personal",
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.success(`${t("request.saved")}`)
|
||||||
|
} catch (e) {
|
||||||
|
tab.value.document.saveContext = undefined
|
||||||
|
saveRequest()
|
||||||
}
|
}
|
||||||
|
} else if (saveCtx.originLocation === "team-collection") {
|
||||||
const { requestHandle } = saveContext
|
|
||||||
|
|
||||||
const requestHandleRef = requestHandle.get()
|
|
||||||
|
|
||||||
if (!requestHandleRef.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
showSaveRequestModal.value = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateRequestResult = await newWorkspaceService.updateRESTRequest(
|
|
||||||
requestHandle,
|
|
||||||
updatedRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(updateRequestResult)) {
|
|
||||||
// INVALID_REQUEST_HANDLE
|
|
||||||
showSaveRequestModal.value = true
|
|
||||||
|
|
||||||
if (!tab.value.document.isDirty) {
|
|
||||||
tab.value.document.isDirty = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tab.value.document.isDirty = false
|
|
||||||
|
|
||||||
tab.value.document.saveContext = {
|
|
||||||
...saveContext,
|
|
||||||
requestHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success(`${t("request.saved")}`)
|
|
||||||
} else if (saveContext.originLocation === "team-collection") {
|
|
||||||
const req = tab.value.document.request
|
const req = tab.value.document.request
|
||||||
|
|
||||||
// TODO: handle error case (NOTE: overwriteRequestTeams is async)
|
// TODO: handle error case (NOTE: overwriteRequestTeams is async)
|
||||||
@@ -574,7 +546,7 @@ const saveRequest = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
runMutation(UpdateRequestDocument, {
|
runMutation(UpdateRequestDocument, {
|
||||||
requestID: saveContext.requestID,
|
requestID: saveCtx.requestID,
|
||||||
data: {
|
data: {
|
||||||
title: req.name,
|
title: req.name,
|
||||||
request: JSON.stringify(req),
|
request: JSON.stringify(req),
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { watch } from "vue"
|
import { watch } from "vue"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { cloneDeep, isEqual } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
|
import { isEqualHoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { HoppTab } from "~/services/tab"
|
import { HoppTab } from "~/services/tab"
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
@@ -31,42 +32,15 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const tab = useVModel(props, "modelValue", emit)
|
const tab = useVModel(props, "modelValue", emit)
|
||||||
|
|
||||||
|
// TODO: Come up with a better dirty check
|
||||||
let oldRequest = cloneDeep(tab.value.document.request)
|
let oldRequest = cloneDeep(tab.value.document.request)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => tab.value.document.request,
|
() => tab.value.document.request,
|
||||||
(updatedValue) => {
|
(updatedValue) => {
|
||||||
// Request from the collection tree
|
|
||||||
if (
|
if (
|
||||||
tab.value.document.saveContext?.originLocation ===
|
!tab.value.document.isDirty &&
|
||||||
"workspace-user-collection"
|
!isEqualHoppRESTRequest(oldRequest, updatedValue)
|
||||||
) {
|
) {
|
||||||
const requestHandleRef =
|
|
||||||
tab.value.document.saveContext.requestHandle?.get()
|
|
||||||
|
|
||||||
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!tab.value.document.isDirty &&
|
|
||||||
!isEqual(oldRequest, requestHandleRef?.value.data.request)
|
|
||||||
) {
|
|
||||||
tab.value.document.isDirty = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
tab.value.document.isDirty &&
|
|
||||||
isEqual(oldRequest, requestHandleRef?.value.data.request)
|
|
||||||
) {
|
|
||||||
tab.value.document.isDirty = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsaved request
|
|
||||||
if (!tab.value.document.isDirty && !isEqual(oldRequest, updatedValue)) {
|
|
||||||
tab.value.document.isDirty = true
|
tab.value.document.isDirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,7 @@
|
|||||||
:icon="IconFolder"
|
:icon="IconFolder"
|
||||||
:label="`${t('tab.collections')}`"
|
:label="`${t('tab.collections')}`"
|
||||||
>
|
>
|
||||||
<!-- <Collections /> -->
|
<Collections />
|
||||||
<NewCollections :platform="'rest'" />
|
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
:id="'env'"
|
:id="'env'"
|
||||||
@@ -38,13 +37,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { ref } from "vue"
|
|
||||||
|
|
||||||
import IconClock from "~icons/lucide/clock"
|
import IconClock from "~icons/lucide/clock"
|
||||||
import IconFolder from "~icons/lucide/folder"
|
|
||||||
import IconLayers from "~icons/lucide/layers"
|
import IconLayers from "~icons/lucide/layers"
|
||||||
|
import IconFolder from "~icons/lucide/folder"
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
|||||||
@@ -106,15 +106,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { useI18n } from "~/composables/i18n"
|
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
|
||||||
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
||||||
import { HoppTab } from "~/services/tab"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import IconCopy from "~icons/lucide/copy"
|
|
||||||
import IconFileEdit from "~icons/lucide/file-edit"
|
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
|
||||||
import IconXCircle from "~icons/lucide/x-circle"
|
import IconXCircle from "~icons/lucide/x-circle"
|
||||||
import IconXSquare from "~icons/lucide/x-square"
|
import IconXSquare from "~icons/lucide/x-square"
|
||||||
|
import IconFileEdit from "~icons/lucide/file-edit"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="!activeWorkspaceHandle">No Workspace Selected.</div>
|
|
||||||
|
|
||||||
<NewCollectionsRest
|
|
||||||
v-else-if="platform === 'rest'"
|
|
||||||
:picked="picked"
|
|
||||||
:save-request="saveRequest"
|
|
||||||
:workspace-handle="activeWorkspaceHandle"
|
|
||||||
@select="(payload) => emit('select', payload)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
|
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
picked?: Picked | null
|
|
||||||
platform: "rest" | "gql"
|
|
||||||
saveRequest?: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(event: "select", payload: Picked | null): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const workspaceService = useService(NewWorkspaceService)
|
|
||||||
|
|
||||||
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle
|
|
||||||
</script>
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div
|
|
||||||
class="h-1 w-full transition"
|
|
||||||
:class="[
|
|
||||||
{
|
|
||||||
'bg-accentDark': isReorderable,
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
@drop="orderUpdateCollectionEvent"
|
|
||||||
@dragover.prevent="ordering = true"
|
|
||||||
@dragleave="ordering = false"
|
|
||||||
@dragend="resetDragState"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div class="relative flex flex-col">
|
|
||||||
<div
|
|
||||||
class="z-[1] pointer-events-none absolute inset-0 bg-accent opacity-0 transition"
|
|
||||||
:class="{
|
|
||||||
'opacity-25':
|
|
||||||
dragging && notSameDestination && notSameParentDestination,
|
|
||||||
}"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="z-[3] group pointer-events-auto relative flex cursor-pointer items-stretch"
|
|
||||||
:draggable="true"
|
|
||||||
@dragstart="dragStart"
|
|
||||||
@dragover="handleDragOver($event)"
|
|
||||||
@dragleave="resetDragState"
|
|
||||||
@dragend="
|
|
||||||
() => {
|
|
||||||
resetDragState()
|
|
||||||
dropItemID = ''
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@drop="handleDrop($event)"
|
|
||||||
@contextmenu.prevent="options?.tippy?.show()"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex min-w-0 flex-1 items-center justify-center"
|
|
||||||
@click="emit('toggle-children')"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="pointer-events-none flex items-center justify-center px-4"
|
|
||||||
>
|
|
||||||
<component
|
|
||||||
:is="collectionIcon"
|
|
||||||
class="svg-icons"
|
|
||||||
:class="{ 'text-accent': isSelected }"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="pointer-events-none flex min-w-0 flex-1 py-2 pr-2 transition group-hover:text-secondaryDark"
|
|
||||||
>
|
|
||||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
|
||||||
{{ collectionView.name }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:icon="IconFilePlus"
|
|
||||||
:title="t('request.new')"
|
|
||||||
class="hidden group-hover:inline-flex"
|
|
||||||
@click="addRequest"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:icon="IconFolderPlus"
|
|
||||||
:title="t('folder.new')"
|
|
||||||
class="hidden group-hover:inline-flex"
|
|
||||||
@click="addChildCollection"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<tippy
|
|
||||||
ref="options"
|
|
||||||
interactive
|
|
||||||
trigger="click"
|
|
||||||
theme="popover"
|
|
||||||
:on-shown="() => tippyActions!.focus()"
|
|
||||||
>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.more')"
|
|
||||||
:icon="IconMoreVertical"
|
|
||||||
/>
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
ref="tippyActions"
|
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.r="requestAction?.$el.click()"
|
|
||||||
@keyup.n="folderAction?.$el.click()"
|
|
||||||
@keyup.e="edit?.$el.click()"
|
|
||||||
@keyup.delete="deleteAction?.$el.click()"
|
|
||||||
@keyup.x="exportAction?.$el.click()"
|
|
||||||
@keyup.p="propertiesAction?.$el.click()"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="requestAction"
|
|
||||||
:icon="IconFilePlus"
|
|
||||||
:label="t('request.new')"
|
|
||||||
:shortcut="['R']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
addRequest()
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="folderAction"
|
|
||||||
:icon="IconFolderPlus"
|
|
||||||
:label="t('folder.new')"
|
|
||||||
:shortcut="['N']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
addChildCollection()
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="edit"
|
|
||||||
:icon="IconEdit"
|
|
||||||
:label="t('action.edit')"
|
|
||||||
:shortcut="['E']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
editCollection()
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="exportAction"
|
|
||||||
:icon="IconDownload"
|
|
||||||
:label="t('export.title')"
|
|
||||||
:shortcut="['X']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
emit('export-collection', collectionView.collectionID)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="deleteAction"
|
|
||||||
:icon="IconTrash2"
|
|
||||||
:label="t('action.delete')"
|
|
||||||
:shortcut="['⌫']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
removeCollection()
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="propertiesAction"
|
|
||||||
:icon="IconSettings2"
|
|
||||||
:label="t('action.properties')"
|
|
||||||
:shortcut="['P']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
emit(
|
|
||||||
'edit-collection-properties',
|
|
||||||
collectionView.collectionID
|
|
||||||
)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="collectionView.isLastItem"
|
|
||||||
class="w-full transition"
|
|
||||||
:class="[
|
|
||||||
{
|
|
||||||
'bg-accentDark': isLastItemReorderable,
|
|
||||||
'h-1 ': collectionView.isLastItem,
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
@drop="updateLastItemOrder"
|
|
||||||
@dragover.prevent="orderingLastItem = true"
|
|
||||||
@dragleave="orderingLastItem = false"
|
|
||||||
@dragend="resetDragState"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref, watch } from "vue"
|
|
||||||
import { TippyComponent } from "vue-tippy"
|
|
||||||
|
|
||||||
import { useI18n } from "~/composables/i18n"
|
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
|
||||||
import {
|
|
||||||
currentReorderingStatus$,
|
|
||||||
changeCurrentReorderStatus,
|
|
||||||
} from "~/newstore/reordering"
|
|
||||||
import { RESTCollectionViewCollection } from "~/services/new-workspace/view"
|
|
||||||
|
|
||||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
|
||||||
import IconDownload from "~icons/lucide/download"
|
|
||||||
import IconEdit from "~icons/lucide/edit"
|
|
||||||
import IconFilePlus from "~icons/lucide/file-plus"
|
|
||||||
import IconFolder from "~icons/lucide/folder"
|
|
||||||
import IconFolderOpen from "~icons/lucide/folder-open"
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
|
||||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
|
||||||
import IconSettings2 from "~icons/lucide/settings-2"
|
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
|
||||||
|
|
||||||
const t = useI18n()
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
collectionView: RESTCollectionViewCollection
|
|
||||||
isOpen: boolean
|
|
||||||
isSelected?: boolean | null
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(event: "add-child-collection", parentCollectionIndexPath: string): void
|
|
||||||
(event: "add-request", parentCollectionIndexPath: string): void
|
|
||||||
(event: "dragging", payload: boolean): void
|
|
||||||
(event: "drop-event", payload: DataTransfer): void
|
|
||||||
(event: "drag-event", payload: DataTransfer): void
|
|
||||||
(
|
|
||||||
event: "edit-child-collection",
|
|
||||||
payload: { collectionIndexPath: string; collectionName: string }
|
|
||||||
): void
|
|
||||||
(event: "edit-collection-properties", collectionIndexPath: string): void
|
|
||||||
(
|
|
||||||
event: "edit-root-collection",
|
|
||||||
payload: { collectionIndexPath: string; collectionName: string }
|
|
||||||
): void
|
|
||||||
(event: "export-collection", collectionIndexPath: string): void
|
|
||||||
(event: "remove-child-collection", collectionIndexPath: string): void
|
|
||||||
(event: "remove-root-collection", collectionIndexPath: string): void
|
|
||||||
(event: "toggle-children"): void
|
|
||||||
(event: "update-collection-order", payload: DataTransfer): void
|
|
||||||
(event: "update-last-collection-order", payload: DataTransfer): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
|
||||||
const requestAction = ref<HTMLButtonElement | null>(null)
|
|
||||||
const folderAction = ref<HTMLButtonElement | null>(null)
|
|
||||||
const edit = ref<HTMLButtonElement | null>(null)
|
|
||||||
const deleteAction = ref<HTMLButtonElement | null>(null)
|
|
||||||
const exportAction = ref<HTMLButtonElement | null>(null)
|
|
||||||
const propertiesAction = ref<TippyComponent | null>(null)
|
|
||||||
const options = ref<TippyComponent | null>(null)
|
|
||||||
|
|
||||||
const dragging = ref(false)
|
|
||||||
const ordering = ref(false)
|
|
||||||
const orderingLastItem = ref(false)
|
|
||||||
const dropItemID = ref("")
|
|
||||||
|
|
||||||
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
|
|
||||||
type: "collection",
|
|
||||||
id: "",
|
|
||||||
parentID: "",
|
|
||||||
})
|
|
||||||
|
|
||||||
// Used to determine if the collection is being dragged to a different destination
|
|
||||||
// This is used to make the highlight effect work
|
|
||||||
watch(
|
|
||||||
() => dragging.value,
|
|
||||||
(val) => {
|
|
||||||
if (val && notSameDestination.value && notSameParentDestination.value) {
|
|
||||||
emit("dragging", true)
|
|
||||||
} else {
|
|
||||||
emit("dragging", false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const collectionIcon = computed(() => {
|
|
||||||
if (props.isSelected) {
|
|
||||||
return IconCheckCircle
|
|
||||||
}
|
|
||||||
return props.isOpen ? IconFolderOpen : IconFolder
|
|
||||||
})
|
|
||||||
|
|
||||||
const notSameParentDestination = computed(() => {
|
|
||||||
return (
|
|
||||||
currentReorderingStatus.value.parentID !== props.collectionView.collectionID
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const isRequestDragging = computed(() => {
|
|
||||||
return currentReorderingStatus.value.type === "request"
|
|
||||||
})
|
|
||||||
|
|
||||||
const isSameParent = computed(() => {
|
|
||||||
return (
|
|
||||||
currentReorderingStatus.value.parentID ===
|
|
||||||
props.collectionView.parentCollectionID
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const isReorderable = computed(() => {
|
|
||||||
return (
|
|
||||||
ordering.value &&
|
|
||||||
notSameDestination.value &&
|
|
||||||
!isRequestDragging.value &&
|
|
||||||
isSameParent.value
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const isLastItemReorderable = computed(() => {
|
|
||||||
return (
|
|
||||||
orderingLastItem.value &&
|
|
||||||
notSameDestination.value &&
|
|
||||||
!isRequestDragging.value &&
|
|
||||||
isSameParent.value
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const addChildCollection = () => {
|
|
||||||
emit("add-child-collection", props.collectionView.collectionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addRequest = () => {
|
|
||||||
emit("add-request", props.collectionView.collectionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
const editCollection = () => {
|
|
||||||
const { collectionID: collectionIndexPath, name: collectionName } =
|
|
||||||
props.collectionView
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
collectionIndexPath,
|
|
||||||
collectionName,
|
|
||||||
}
|
|
||||||
|
|
||||||
collectionIndexPath.split("/").length > 1
|
|
||||||
? emit("edit-child-collection", data)
|
|
||||||
: emit("edit-root-collection", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragStart = ({ dataTransfer }: DragEvent) => {
|
|
||||||
if (dataTransfer) {
|
|
||||||
emit("drag-event", dataTransfer)
|
|
||||||
|
|
||||||
dropItemID.value = dataTransfer.getData("collectionIndex")
|
|
||||||
dragging.value = !dragging.value
|
|
||||||
|
|
||||||
changeCurrentReorderStatus({
|
|
||||||
type: "collection",
|
|
||||||
id: props.collectionView.collectionID,
|
|
||||||
parentID: props.collectionView.parentCollectionID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger the re-ordering event when a collection is dragged over another collection's top section
|
|
||||||
const handleDragOver = (e: DragEvent) => {
|
|
||||||
dragging.value = true
|
|
||||||
if (
|
|
||||||
e.offsetY < 10 &&
|
|
||||||
notSameDestination.value &&
|
|
||||||
!isRequestDragging.value &&
|
|
||||||
isSameParent.value
|
|
||||||
) {
|
|
||||||
ordering.value = true
|
|
||||||
dragging.value = false
|
|
||||||
orderingLastItem.value = false
|
|
||||||
} else if (
|
|
||||||
e.offsetY > 18 &&
|
|
||||||
notSameDestination.value &&
|
|
||||||
!isRequestDragging.value &&
|
|
||||||
isSameParent.value &&
|
|
||||||
props.collectionView.isLastItem
|
|
||||||
) {
|
|
||||||
orderingLastItem.value = true
|
|
||||||
dragging.value = false
|
|
||||||
ordering.value = false
|
|
||||||
} else {
|
|
||||||
ordering.value = false
|
|
||||||
orderingLastItem.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDrop = (e: DragEvent) => {
|
|
||||||
if (ordering.value) {
|
|
||||||
orderUpdateCollectionEvent(e)
|
|
||||||
} else if (orderingLastItem.value) {
|
|
||||||
updateLastItemOrder(e)
|
|
||||||
} else {
|
|
||||||
notSameParentDestination.value ? dropEvent(e) : e.stopPropagation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dropEvent = (e: DragEvent) => {
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.stopPropagation()
|
|
||||||
emit("drop-event", e.dataTransfer)
|
|
||||||
resetDragState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderUpdateCollectionEvent = (e: DragEvent) => {
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.stopPropagation()
|
|
||||||
emit("update-collection-order", e.dataTransfer)
|
|
||||||
resetDragState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateLastItemOrder = (e: DragEvent) => {
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.stopPropagation()
|
|
||||||
emit("update-last-collection-order", e.dataTransfer)
|
|
||||||
resetDragState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const notSameDestination = computed(() => {
|
|
||||||
return dropItemID.value !== props.collectionView.collectionID
|
|
||||||
})
|
|
||||||
|
|
||||||
const removeCollection = () => {
|
|
||||||
const { collectionID } = props.collectionView
|
|
||||||
|
|
||||||
collectionID.split("/").length > 1
|
|
||||||
? emit("remove-child-collection", collectionID)
|
|
||||||
: emit("remove-root-collection", collectionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetDragState = () => {
|
|
||||||
dragging.value = false
|
|
||||||
ordering.value = false
|
|
||||||
orderingLastItem.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,334 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div
|
|
||||||
class="h-1 w-full transition"
|
|
||||||
:class="[
|
|
||||||
{
|
|
||||||
'bg-accentDark': isReorderable,
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
@drop="updateRequestOrder"
|
|
||||||
@dragover.prevent="ordering = true"
|
|
||||||
@dragleave="resetDragState"
|
|
||||||
@dragend="resetDragState"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="group flex items-stretch"
|
|
||||||
:draggable="true"
|
|
||||||
@dragstart="dragStart"
|
|
||||||
@dragover="handleDragOver($event)"
|
|
||||||
@dragleave="resetDragState"
|
|
||||||
@dragend="resetDragState"
|
|
||||||
@drop="handleDrop"
|
|
||||||
@contextmenu.prevent="options?.tippy?.show()"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="pointer-events-auto flex min-w-0 flex-1 cursor-pointer items-center justify-center"
|
|
||||||
@click="selectRequest"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="pointer-events-none flex w-16 items-center justify-center truncate px-2"
|
|
||||||
:class="requestLabelColor"
|
|
||||||
:style="{ color: requestLabelColor }"
|
|
||||||
>
|
|
||||||
<component
|
|
||||||
:is="IconCheckCircle"
|
|
||||||
v-if="isSelected"
|
|
||||||
class="svg-icons"
|
|
||||||
:class="{ 'text-accent': isSelected }"
|
|
||||||
/>
|
|
||||||
<span v-else class="truncate text-tiny font-semibold">
|
|
||||||
{{ requestView.request.method }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="pointer-events-none flex min-w-0 flex-1 items-center py-2 pr-2 transition group-hover:text-secondaryDark"
|
|
||||||
>
|
|
||||||
<span class="truncate" :class="{ 'text-accent': isSelected }">
|
|
||||||
{{ requestView.request.name }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="props.isActive"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
class="relative mx-3 flex h-1.5 w-1.5 flex-shrink-0"
|
|
||||||
:title="`${t('collection.request_in_use')}`"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="absolute inline-flex h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500"
|
|
||||||
></span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:icon="IconRotateCCW"
|
|
||||||
:title="t('action.restore')"
|
|
||||||
class="hidden group-hover:inline-flex"
|
|
||||||
@click="selectRequest"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
<tippy
|
|
||||||
ref="options"
|
|
||||||
interactive
|
|
||||||
trigger="click"
|
|
||||||
theme="popover"
|
|
||||||
:on-shown="() => tippyActions?.focus()"
|
|
||||||
>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.more')"
|
|
||||||
:icon="IconMoreVertical"
|
|
||||||
/>
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
ref="tippyActions"
|
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.e="edit?.$el.click()"
|
|
||||||
@keyup.d="duplicate?.$el.click()"
|
|
||||||
@keyup.delete="deleteAction?.$el.click()"
|
|
||||||
@keyup.s="shareAction?.$el.click()"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="edit"
|
|
||||||
:icon="IconEdit"
|
|
||||||
:label="t('action.edit')"
|
|
||||||
:shortcut="['E']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
emit('edit-request', {
|
|
||||||
requestIndexPath: requestView.requestID,
|
|
||||||
requestName: requestView.request.name,
|
|
||||||
})
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="duplicate"
|
|
||||||
:icon="IconCopy"
|
|
||||||
:label="t('action.duplicate')"
|
|
||||||
:shortcut="['D']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
emit('duplicate-request', requestView.requestID)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="deleteAction"
|
|
||||||
:icon="IconTrash2"
|
|
||||||
:label="t('action.delete')"
|
|
||||||
:shortcut="['⌫']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
emit('remove-request', requestView.requestID)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
ref="shareAction"
|
|
||||||
:icon="IconShare2"
|
|
||||||
:label="t('action.share')"
|
|
||||||
:shortcut="['S']"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
emit('share-request', requestView.request)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="w-full transition"
|
|
||||||
:class="[
|
|
||||||
{
|
|
||||||
'bg-accentDark': isLastItemReorderable,
|
|
||||||
'h-1 ': props.requestView.isLastItem,
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
@drop="handleDrop"
|
|
||||||
@dragover.prevent="orderingLastItem = true"
|
|
||||||
@dragleave="resetDragState"
|
|
||||||
@dragend="resetDragState"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { computed, ref } from "vue"
|
|
||||||
import { TippyComponent } from "vue-tippy"
|
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
|
||||||
|
|
||||||
import { getMethodLabelColorClassOf } from "~/helpers/rest/labelColoring"
|
|
||||||
import {
|
|
||||||
currentReorderingStatus$,
|
|
||||||
changeCurrentReorderStatus,
|
|
||||||
} from "~/newstore/reordering"
|
|
||||||
import { RESTCollectionViewRequest } from "~/services/new-workspace/view"
|
|
||||||
|
|
||||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
|
||||||
import IconCopy from "~icons/lucide/copy"
|
|
||||||
import IconEdit from "~icons/lucide/edit"
|
|
||||||
import IconMoreVertical from "~icons/lucide/more-vertical"
|
|
||||||
import IconRotateCCW from "~icons/lucide/rotate-ccw"
|
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
|
||||||
|
|
||||||
const t = useI18n()
|
|
||||||
|
|
||||||
const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, {
|
|
||||||
type: "collection",
|
|
||||||
id: "",
|
|
||||||
parentID: "",
|
|
||||||
})
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
isActive: boolean
|
|
||||||
requestView: RESTCollectionViewRequest
|
|
||||||
isSelected: boolean | null | undefined
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(event: "duplicate-request", requestIndexPath: string): void
|
|
||||||
(
|
|
||||||
event: "edit-request",
|
|
||||||
payload: {
|
|
||||||
requestIndexPath: string
|
|
||||||
requestName: string
|
|
||||||
}
|
|
||||||
): void
|
|
||||||
(event: "remove-request", requestIndexPath: string): void
|
|
||||||
(event: "select-request", requestIndexPath: string): void
|
|
||||||
(event: "share-request", request: HoppRESTRequest): void
|
|
||||||
(event: "drag-request", payload: DataTransfer): void
|
|
||||||
(event: "update-request-order", payload: DataTransfer): void
|
|
||||||
(event: "update-last-request-order", payload: DataTransfer): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
|
||||||
const edit = ref<HTMLButtonElement | null>(null)
|
|
||||||
const deleteAction = ref<HTMLButtonElement | null>(null)
|
|
||||||
const options = ref<TippyComponent | null>(null)
|
|
||||||
const duplicate = ref<HTMLButtonElement | null>(null)
|
|
||||||
const shareAction = ref<HTMLButtonElement | null>(null)
|
|
||||||
|
|
||||||
const dragging = ref(false)
|
|
||||||
const ordering = ref(false)
|
|
||||||
const orderingLastItem = ref(false)
|
|
||||||
|
|
||||||
const isCollectionDragging = computed(() => {
|
|
||||||
return currentReorderingStatus.value.type === "collection"
|
|
||||||
})
|
|
||||||
|
|
||||||
const isLastItemReorderable = computed(() => {
|
|
||||||
return (
|
|
||||||
orderingLastItem.value && isSameParent.value && !isCollectionDragging.value
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const isReorderable = computed(() => {
|
|
||||||
return (
|
|
||||||
ordering.value &&
|
|
||||||
!isCollectionDragging.value &&
|
|
||||||
isSameParent.value &&
|
|
||||||
!isSameRequest.value
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const isSameParent = computed(() => {
|
|
||||||
return (
|
|
||||||
currentReorderingStatus.value.parentID === props.requestView.collectionID
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const isSameRequest = computed(() => {
|
|
||||||
return currentReorderingStatus.value.id === props.requestView.requestID
|
|
||||||
})
|
|
||||||
|
|
||||||
const requestLabelColor = computed(() =>
|
|
||||||
getMethodLabelColorClassOf(props.requestView.request)
|
|
||||||
)
|
|
||||||
|
|
||||||
const dragStart = ({ dataTransfer }: DragEvent) => {
|
|
||||||
if (dataTransfer) {
|
|
||||||
emit("drag-request", dataTransfer)
|
|
||||||
dragging.value = !dragging.value
|
|
||||||
|
|
||||||
changeCurrentReorderStatus({
|
|
||||||
type: "request",
|
|
||||||
id: props.requestView.requestID,
|
|
||||||
parentID: props.requestView.collectionID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDrop = (e: DragEvent) => {
|
|
||||||
if (ordering.value) {
|
|
||||||
updateRequestOrder(e)
|
|
||||||
} else if (orderingLastItem.value) {
|
|
||||||
updateLastItemOrder(e)
|
|
||||||
} else {
|
|
||||||
updateRequestOrder(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger the re-ordering event when a request is dragged over another request's top section
|
|
||||||
const handleDragOver = (e: DragEvent) => {
|
|
||||||
dragging.value = true
|
|
||||||
if (e.offsetY < 10) {
|
|
||||||
ordering.value = true
|
|
||||||
dragging.value = false
|
|
||||||
orderingLastItem.value = false
|
|
||||||
} else if (e.offsetY > 18) {
|
|
||||||
orderingLastItem.value = true
|
|
||||||
dragging.value = false
|
|
||||||
ordering.value = false
|
|
||||||
} else {
|
|
||||||
ordering.value = false
|
|
||||||
orderingLastItem.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetDragState = () => {
|
|
||||||
dragging.value = false
|
|
||||||
ordering.value = false
|
|
||||||
orderingLastItem.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectRequest = () => emit("select-request", props.requestView.requestID)
|
|
||||||
|
|
||||||
const updateRequestOrder = (e: DragEvent) => {
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.stopPropagation()
|
|
||||||
resetDragState()
|
|
||||||
emit("update-request-order", e.dataTransfer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateLastItemOrder = (e: DragEvent) => {
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.stopPropagation()
|
|
||||||
resetDragState()
|
|
||||||
emit("update-last-request-order", e.dataTransfer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
class="flex items-center overflow-x-auto whitespace-nowrap border-b border-dividerLight px-4 py-2 text-tiny text-secondaryLight"
|
class="flex items-center overflow-x-auto whitespace-nowrap border-b border-dividerLight px-4 py-2 text-tiny text-secondaryLight"
|
||||||
>
|
>
|
||||||
<span class="truncate">
|
<span class="truncate">
|
||||||
{{ workspaceName ?? t("workspace.no_workspace") }}
|
{{ currentWorkspace }}
|
||||||
</span>
|
</span>
|
||||||
<icon-lucide-chevron-right v-if="section" class="mx-2" />
|
<icon-lucide-chevron-right v-if="section" class="mx-2" />
|
||||||
{{ section }}
|
{{ section }}
|
||||||
@@ -14,24 +14,29 @@
|
|||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
section?: string
|
section?: string
|
||||||
|
isOnlyPersonal?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const workspaceService = useService(NewWorkspaceService)
|
const workspaceService = useService(WorkspaceService)
|
||||||
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
const workspaceName = computed(() => {
|
const currentWorkspace = computed(() => {
|
||||||
const activeWorkspaceHandleRef = activeWorkspaceHandle.value?.get()
|
if (props.isOnlyPersonal || workspace.value.type === "personal") {
|
||||||
|
return t("workspace.personal")
|
||||||
if (activeWorkspaceHandleRef?.value.type === "ok") {
|
|
||||||
return activeWorkspaceHandleRef.value.data.name
|
|
||||||
}
|
}
|
||||||
|
return teamWorkspaceName.value
|
||||||
|
})
|
||||||
|
|
||||||
return undefined
|
const teamWorkspaceName = computed(() => {
|
||||||
|
if (workspace.value.type === "team" && workspace.value.teamName) {
|
||||||
|
return workspace.value.teamName
|
||||||
|
}
|
||||||
|
return `${t("workspace.team")}`
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<HoppSmartItem
|
|
||||||
:label="'Personal Workspace'"
|
|
||||||
:info-icon="
|
|
||||||
activeWorkspaceInfo?.provider ===
|
|
||||||
personalWorkspaceProviderService.providerID &&
|
|
||||||
activeWorkspaceInfo.workspaceID === 'personal'
|
|
||||||
? IconCheck
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
:active-info-icon="
|
|
||||||
activeWorkspaceInfo?.provider ===
|
|
||||||
personalWorkspaceProviderService.providerID &&
|
|
||||||
activeWorkspaceInfo.workspaceID === 'personal'
|
|
||||||
"
|
|
||||||
@click="selectWorkspace"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
|
||||||
import { computed } from "vue"
|
|
||||||
import { PersonalWorkspaceProviderService } from "~/services/new-workspace/providers/personal.workspace"
|
|
||||||
import IconCheck from "~icons/lucide/check"
|
|
||||||
|
|
||||||
const workspaceService = useService(NewWorkspaceService)
|
|
||||||
const personalWorkspaceProviderService = useService(
|
|
||||||
PersonalWorkspaceProviderService
|
|
||||||
)
|
|
||||||
|
|
||||||
const activeWorkspaceInfo = computed(() => {
|
|
||||||
const activeWorkspaceHandleRef =
|
|
||||||
workspaceService.activeWorkspaceHandle.value?.get()
|
|
||||||
|
|
||||||
if (activeWorkspaceHandleRef?.value.type === "ok") {
|
|
||||||
return {
|
|
||||||
provider: activeWorkspaceHandleRef.value.data.providerID,
|
|
||||||
workspaceID: activeWorkspaceHandleRef.value.data.workspaceID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
function selectWorkspace() {
|
|
||||||
workspaceService.activeWorkspaceHandle.value =
|
|
||||||
personalWorkspaceProviderService.getPersonalWorkspaceHandle()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,36 +1,198 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="rootEl">
|
<div ref="rootEl">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div
|
<div class="flex flex-col">
|
||||||
v-for="(selectorComponent, index) in workspaceSelectorComponents"
|
<HoppSmartItem
|
||||||
:key="index"
|
:label="t('workspace.personal')"
|
||||||
class="flex flex-col"
|
:icon="IconUser"
|
||||||
>
|
:info-icon="workspace.type === 'personal' ? IconDone : undefined"
|
||||||
<component :is="selectorComponent" />
|
:active-info-icon="workspace.type === 'personal'"
|
||||||
|
@click="switchToPersonalWorkspace"
|
||||||
|
/>
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="loading" class="flex flex-col items-center justify-center p-4">
|
||||||
|
<HoppSmartSpinner class="mb-4" />
|
||||||
|
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||||
|
</div>
|
||||||
|
<HoppSmartPlaceholder
|
||||||
|
v-if="!loading && myTeams.length === 0"
|
||||||
|
:src="`/images/states/${colorMode.value}/add_group.svg`"
|
||||||
|
:alt="`${t('empty.teams')}`"
|
||||||
|
:text="`${t('empty.teams')}`"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('team.create_new')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
:icon="IconPlus"
|
||||||
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</HoppSmartPlaceholder>
|
||||||
|
<div v-else-if="!loading" class="flex flex-col">
|
||||||
|
<div
|
||||||
|
class="sticky top-0 z-10 mb-2 flex items-center justify-between bg-popover py-2 pl-2"
|
||||||
|
>
|
||||||
|
<div class="flex items-center px-2 font-semibold text-secondaryLight">
|
||||||
|
{{ t("workspace.other_workspaces") }}
|
||||||
|
</div>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconPlus"
|
||||||
|
:title="`${t('team.create_new')}`"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
class="ml-8 rounded !p-0.75"
|
||||||
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-for="(team, index) in myTeams"
|
||||||
|
:key="`team-${String(index)}`"
|
||||||
|
:icon="IconUsers"
|
||||||
|
:label="team.name"
|
||||||
|
:info-icon="isActiveWorkspace(team.id) ? IconDone : undefined"
|
||||||
|
:active-info-icon="isActiveWorkspace(team.id)"
|
||||||
|
@click="switchToTeamWorkspace(team)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="teamListAdapterError"
|
||||||
|
class="flex flex-col items-center py-4"
|
||||||
|
>
|
||||||
|
<icon-lucide-help-circle class="svg-icons mb-4" />
|
||||||
|
{{ t("error.something_went_wrong") }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<TeamsAdd
|
||||||
|
:show="showModalAdd"
|
||||||
|
:switch-workspace-after-creation="true"
|
||||||
|
@hide-modal="displayModalAdd(false)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from "vue"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import IconUser from "~icons/lucide/user"
|
||||||
|
import IconUsers from "~icons/lucide/users"
|
||||||
|
import IconPlus from "~icons/lucide/plus"
|
||||||
|
import { useColorMode } from "@composables/theming"
|
||||||
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
|
import IconDone from "~icons/lucide/check"
|
||||||
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
import { useElementVisibility, useIntervalFn } from "@vueuse/core"
|
||||||
import { TestWorkspaceProviderService } from "~/services/new-workspace/providers/test.workspace"
|
|
||||||
|
|
||||||
useService(TestWorkspaceProviderService)
|
const t = useI18n()
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const newWorkspaceService = useService(NewWorkspaceService)
|
const showModalAdd = ref(false)
|
||||||
const workspaceSelectorComponents =
|
|
||||||
newWorkspaceService.workspaceSelectorComponents
|
|
||||||
|
|
||||||
// TODO: Handle the updates to these actions
|
const currentUser = useReadonlyStream(
|
||||||
// defineActionHandler("modals.team.new", () => {
|
platform.auth.getProbableUserStream(),
|
||||||
// displayModalAdd(true)
|
platform.auth.getProbableUser()
|
||||||
// })
|
)
|
||||||
//
|
|
||||||
// defineActionHandler("workspace.switch.personal", switchToPersonalWorkspace)
|
const workspaceService = useService(WorkspaceService)
|
||||||
// defineActionHandler("workspace.switch", ({ teamId }) => {
|
const teamListadapter = workspaceService.acquireTeamListAdapter(null)
|
||||||
// const team = myTeams.value.find((t) => t.id === teamId)
|
const myTeams = useReadonlyStream(teamListadapter.teamList$, [])
|
||||||
// if (team) switchToTeamWorkspace(team)
|
const isTeamListLoading = useReadonlyStream(teamListadapter.loading$, false)
|
||||||
// })
|
const teamListAdapterError = useReadonlyStream(teamListadapter.error$, null)
|
||||||
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
|
const teamListFetched = ref(false)
|
||||||
|
|
||||||
|
const rootEl = ref<HTMLElement>()
|
||||||
|
const elVisible = useElementVisibility(rootEl)
|
||||||
|
|
||||||
|
const { pause: pauseListPoll, resume: resumeListPoll } = useIntervalFn(() => {
|
||||||
|
if (teamListadapter.isInitialized) {
|
||||||
|
teamListadapter.fetchList()
|
||||||
|
}
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
elVisible,
|
||||||
|
() => {
|
||||||
|
if (elVisible.value) {
|
||||||
|
teamListadapter.fetchList()
|
||||||
|
|
||||||
|
resumeListPoll()
|
||||||
|
} else {
|
||||||
|
pauseListPoll()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(myTeams, (teams) => {
|
||||||
|
if (teams && !teamListFetched.value) {
|
||||||
|
teamListFetched.value = true
|
||||||
|
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
||||||
|
const team = teams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||||
|
if (team) switchToTeamWorkspace(team)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = computed(
|
||||||
|
() => isTeamListLoading.value && myTeams.value.length === 0
|
||||||
|
)
|
||||||
|
|
||||||
|
const workspace = workspaceService.currentWorkspace
|
||||||
|
|
||||||
|
const isActiveWorkspace = computed(() => (id: string) => {
|
||||||
|
if (workspace.value.type === "personal") return false
|
||||||
|
return workspace.value.teamID === id
|
||||||
|
})
|
||||||
|
|
||||||
|
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
||||||
|
REMEMBERED_TEAM_ID.value = team.id
|
||||||
|
workspaceService.changeWorkspace({
|
||||||
|
teamID: team.id,
|
||||||
|
teamName: team.name,
|
||||||
|
type: "team",
|
||||||
|
role: team.myRole,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchToPersonalWorkspace = () => {
|
||||||
|
REMEMBERED_TEAM_ID.value = undefined
|
||||||
|
workspaceService.changeWorkspace({
|
||||||
|
type: "personal",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => currentUser.value,
|
||||||
|
(user) => {
|
||||||
|
if (!user) {
|
||||||
|
switchToPersonalWorkspace()
|
||||||
|
teamListadapter.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
|
if (!currentUser.value) return invokeAction("modals.login.toggle")
|
||||||
|
|
||||||
|
showModalAdd.value = shouldDisplay
|
||||||
|
teamListadapter.fetchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineActionHandler("modals.team.new", () => {
|
||||||
|
displayModalAdd(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
defineActionHandler("workspace.switch.personal", switchToPersonalWorkspace)
|
||||||
|
defineActionHandler("workspace.switch", ({ teamId }) => {
|
||||||
|
const team = myTeams.value.find((t) => t.id === teamId)
|
||||||
|
if (team) switchToTeamWorkspace(team)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<HoppSmartItem
|
|
||||||
v-for="candidate in candidates"
|
|
||||||
:key="candidate.id"
|
|
||||||
:label="candidate.name"
|
|
||||||
:info-icon="
|
|
||||||
activeWorkspaceInfo?.provider ===
|
|
||||||
testWorkspaceProviderService.providerID &&
|
|
||||||
activeWorkspaceInfo.workspaceID === candidate.id
|
|
||||||
? IconCheck
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
:active-info-icon="
|
|
||||||
activeWorkspaceInfo?.provider ===
|
|
||||||
testWorkspaceProviderService.providerID &&
|
|
||||||
activeWorkspaceInfo.workspaceID === candidate.id
|
|
||||||
"
|
|
||||||
@click="selectWorkspace(candidate.id)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { computed } from "vue"
|
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
|
||||||
import { TestWorkspaceProviderService } from "~/services/new-workspace/providers/test.workspace"
|
|
||||||
import IconCheck from "~icons/lucide/check"
|
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
|
|
||||||
const workspaceService = useService(NewWorkspaceService)
|
|
||||||
const testWorkspaceProviderService = useService(TestWorkspaceProviderService)
|
|
||||||
|
|
||||||
const candidates = testWorkspaceProviderService.getWorkspaceCandidates()
|
|
||||||
|
|
||||||
const activeWorkspaceInfo = computed(() => {
|
|
||||||
const activeWorkspaceHandle = workspaceService.activeWorkspaceHandle.value
|
|
||||||
const activeWorkspaceHandleRef = activeWorkspaceHandle?.get()
|
|
||||||
|
|
||||||
if (activeWorkspaceHandleRef?.value.type === "ok") {
|
|
||||||
return {
|
|
||||||
provider: activeWorkspaceHandleRef.value.data.providerID,
|
|
||||||
workspaceID: activeWorkspaceHandleRef.value.data.workspaceID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
async function selectWorkspace(workspaceID: string) {
|
|
||||||
const result =
|
|
||||||
await testWorkspaceProviderService.getWorkspaceHandle(workspaceID)
|
|
||||||
|
|
||||||
// TODO: Re-evaluate this ?
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
console.error(result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
workspaceService.activeWorkspaceHandle.value = result.right
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import { HoppCollection } from "@hoppscotch/data"
|
|
||||||
import { ChildrenResult, SmartTreeAdapter } from "@hoppscotch/ui"
|
|
||||||
import { Ref, computed, ref } from "vue"
|
|
||||||
import { navigateToFolderWithIndexPath } from "~/newstore/collections"
|
|
||||||
import { RESTCollectionViewItem } from "~/services/new-workspace/view"
|
|
||||||
|
|
||||||
export class WorkspaceRESTSearchCollectionTreeAdapter
|
|
||||||
implements SmartTreeAdapter<RESTCollectionViewItem>
|
|
||||||
{
|
|
||||||
constructor(public data: Ref<HoppCollection[]>) {}
|
|
||||||
|
|
||||||
getChildren(
|
|
||||||
nodeID: string | null
|
|
||||||
): Ref<ChildrenResult<RESTCollectionViewItem>> {
|
|
||||||
const result = ref<ChildrenResult<RESTCollectionViewItem>>({
|
|
||||||
status: "loading",
|
|
||||||
})
|
|
||||||
|
|
||||||
return computed(() => {
|
|
||||||
if (nodeID === null) {
|
|
||||||
result.value = {
|
|
||||||
status: "loaded",
|
|
||||||
data: this.data.value.map((item, index) => ({
|
|
||||||
id: index.toString(),
|
|
||||||
|
|
||||||
data: <RESTCollectionViewItem>{
|
|
||||||
type: "collection",
|
|
||||||
value: {
|
|
||||||
collectionID: index.toString(),
|
|
||||||
isLastItem: index === this.data.value.length - 1,
|
|
||||||
name: item.name,
|
|
||||||
parentCollectionID: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const indexPath = nodeID.split("/").map((x) => parseInt(x))
|
|
||||||
|
|
||||||
const item = navigateToFolderWithIndexPath(this.data.value, indexPath)
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
const collections = item.folders.map(
|
|
||||||
(childCollection, childCollectionID) => {
|
|
||||||
return {
|
|
||||||
id: `${nodeID}/${childCollectionID}`,
|
|
||||||
data: <RESTCollectionViewItem>{
|
|
||||||
type: "collection",
|
|
||||||
value: {
|
|
||||||
isLastItem:
|
|
||||||
item.folders?.length > 1
|
|
||||||
? childCollectionID === item.folders.length - 1
|
|
||||||
: false,
|
|
||||||
collectionID: `${nodeID}/${childCollectionID}`,
|
|
||||||
name: childCollection.name,
|
|
||||||
parentCollectionID: nodeID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const requests = item.requests.map((request, requestID) => {
|
|
||||||
return {
|
|
||||||
id: `${nodeID}/${requestID}`,
|
|
||||||
data: <RESTCollectionViewItem>{
|
|
||||||
type: "request",
|
|
||||||
value: {
|
|
||||||
isLastItem:
|
|
||||||
item.requests?.length > 1
|
|
||||||
? requestID === item.requests.length - 1
|
|
||||||
: false,
|
|
||||||
parentCollectionID: nodeID,
|
|
||||||
collectionID: nodeID,
|
|
||||||
requestID: `${nodeID}/${requestID}`,
|
|
||||||
request,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
result.value = {
|
|
||||||
status: "loaded",
|
|
||||||
data: [...collections, ...requests],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import {
|
|
||||||
ChildrenResult,
|
|
||||||
SmartTreeAdapter,
|
|
||||||
} from "@hoppscotch/ui/dist/src/helpers/treeAdapter"
|
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
import { Ref, ref, watchEffect } from "vue"
|
|
||||||
import { NewWorkspaceService } from "~/services/new-workspace"
|
|
||||||
import { Handle } from "~/services/new-workspace/handle"
|
|
||||||
import { RESTCollectionViewItem } from "~/services/new-workspace/view"
|
|
||||||
import { Workspace } from "~/services/new-workspace/workspace"
|
|
||||||
|
|
||||||
export class WorkspaceRESTCollectionTreeAdapter
|
|
||||||
implements SmartTreeAdapter<RESTCollectionViewItem>
|
|
||||||
{
|
|
||||||
constructor(
|
|
||||||
private workspaceHandle: Handle<Workspace>,
|
|
||||||
private workspaceService: NewWorkspaceService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public getChildren(
|
|
||||||
nodeID: string | null,
|
|
||||||
nodeType?: string
|
|
||||||
): Ref<ChildrenResult<RESTCollectionViewItem>> {
|
|
||||||
const workspaceHandleRef = this.workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type !== "ok") {
|
|
||||||
throw new Error("Cannot issue children with invalid workspace handle")
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = ref<ChildrenResult<RESTCollectionViewItem>>({
|
|
||||||
status: "loading",
|
|
||||||
})
|
|
||||||
|
|
||||||
if (nodeID !== null) {
|
|
||||||
;(async () => {
|
|
||||||
if (nodeType === "request") {
|
|
||||||
result.value = {
|
|
||||||
status: "loaded",
|
|
||||||
data: [],
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionHandleResult =
|
|
||||||
await this.workspaceService.getCollectionHandle(
|
|
||||||
this.workspaceHandle,
|
|
||||||
nodeID
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Better error handling
|
|
||||||
if (E.isLeft(collectionHandleResult)) {
|
|
||||||
throw new Error(JSON.stringify(collectionHandleResult.left.error))
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionHandle = collectionHandleResult.right
|
|
||||||
|
|
||||||
const collectionChildrenResult =
|
|
||||||
await this.workspaceService.getRESTCollectionChildrenView(
|
|
||||||
collectionHandle
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Better error handling
|
|
||||||
if (E.isLeft(collectionChildrenResult)) {
|
|
||||||
throw new Error(JSON.stringify(collectionChildrenResult.left.error))
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionChildrenViewHandle =
|
|
||||||
collectionChildrenResult.right.get()
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (collectionChildrenViewHandle.value.type !== "ok") return
|
|
||||||
|
|
||||||
if (collectionChildrenViewHandle.value.data.loading.value) {
|
|
||||||
result.value = {
|
|
||||||
status: "loading",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.value = {
|
|
||||||
status: "loaded",
|
|
||||||
data: collectionChildrenViewHandle.value.data.content.value.map(
|
|
||||||
(item) => ({
|
|
||||||
id:
|
|
||||||
item.type === "request"
|
|
||||||
? item.value.requestID
|
|
||||||
: item.value.collectionID,
|
|
||||||
|
|
||||||
data: item,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
} else {
|
|
||||||
;(async () => {
|
|
||||||
const viewResult =
|
|
||||||
await this.workspaceService.getRESTRootCollectionView(
|
|
||||||
this.workspaceHandle
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Better error handling
|
|
||||||
if (E.isLeft(viewResult)) {
|
|
||||||
throw new Error(JSON.stringify(viewResult.left.error))
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewHandle = viewResult.right.get()
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (viewHandle.value.type !== "ok") return
|
|
||||||
|
|
||||||
if (viewHandle.value.data.loading.value) {
|
|
||||||
result.value = {
|
|
||||||
status: "loading",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.value = {
|
|
||||||
status: "loaded",
|
|
||||||
data: viewHandle.value.data.collections.value.map((coll) => ({
|
|
||||||
id: coll.collectionID,
|
|
||||||
|
|
||||||
data: {
|
|
||||||
type: "collection",
|
|
||||||
value: coll,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import { HoppCollection } from "@hoppscotch/data"
|
import { HoppCollection } from "@hoppscotch/data"
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
|
|
||||||
import { getService } from "~/modules/dioc"
|
|
||||||
import { GQLTabService } from "~/services/tab/graphql"
|
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
|
||||||
import { runGQLQuery } from "../backend/GQLClient"
|
|
||||||
import { GetSingleRequestDocument } from "../backend/graphql"
|
|
||||||
import { HoppGQLSaveContext } from "../graphql/document"
|
|
||||||
import { HoppRESTSaveContext } from "../rest/document"
|
|
||||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
|
||||||
import { getAffectedIndexes } from "./affectedIndex"
|
import { getAffectedIndexes } from "./affectedIndex"
|
||||||
|
import { GetSingleRequestDocument } from "../backend/graphql"
|
||||||
|
import { runGQLQuery } from "../backend/GQLClient"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||||
|
import { GQLTabService } from "~/services/tab/graphql"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve save context on reorder
|
* Resolve save context on reorder
|
||||||
@@ -21,12 +18,15 @@ import { getAffectedIndexes } from "./affectedIndex"
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function resolveSaveContextOnCollectionReorder(payload: {
|
export function resolveSaveContextOnCollectionReorder(
|
||||||
lastIndex: number
|
payload: {
|
||||||
newIndex: number
|
lastIndex: number
|
||||||
folderPath: string
|
newIndex: number
|
||||||
length?: number // better way to do this? now it could be undefined
|
folderPath: string
|
||||||
}) {
|
length?: number // better way to do this? now it could be undefined
|
||||||
|
},
|
||||||
|
type: "remove" | "drop" = "remove"
|
||||||
|
) {
|
||||||
const { lastIndex, folderPath, length } = payload
|
const { lastIndex, folderPath, length } = payload
|
||||||
let { newIndex } = payload
|
let { newIndex } = payload
|
||||||
|
|
||||||
@@ -41,6 +41,12 @@ export function resolveSaveContextOnCollectionReorder(payload: {
|
|||||||
if (newIndex === -1) {
|
if (newIndex === -1) {
|
||||||
// if (newIndex === -1) remove it from the map because it will be deleted
|
// if (newIndex === -1) remove it from the map because it will be deleted
|
||||||
affectedIndexes.delete(lastIndex)
|
affectedIndexes.delete(lastIndex)
|
||||||
|
// when collection deleted opended requests from that collection be affected
|
||||||
|
if (type === "remove") {
|
||||||
|
resetSaveContextForAffectedRequests(
|
||||||
|
folderPath ? `${folderPath}/${lastIndex}` : lastIndex.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add folder path as prefix to the affected indexes
|
// add folder path as prefix to the affected indexes
|
||||||
@@ -56,27 +62,10 @@ export function resolveSaveContextOnCollectionReorder(payload: {
|
|||||||
const tabService = getService(RESTTabService)
|
const tabService = getService(RESTTabService)
|
||||||
|
|
||||||
const tabs = tabService.getTabsRefTo((tab) => {
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
if (tab.document.saveContext?.originLocation === "user-collection") {
|
return (
|
||||||
return affectedPaths.has(tab.document.saveContext.folderPath)
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
}
|
affectedPaths.has(tab.document.saveContext.folderPath)
|
||||||
|
)
|
||||||
if (
|
|
||||||
tab.document.saveContext?.originLocation !== "workspace-user-collection"
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestHandleRef = tab.document.saveContext.requestHandle?.get()
|
|
||||||
|
|
||||||
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const { requestID } = requestHandleRef.value.data
|
|
||||||
|
|
||||||
const collectionID = requestID.split("/").slice(0, -1).join("/")
|
|
||||||
|
|
||||||
return affectedPaths.has(collectionID)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
@@ -86,34 +75,6 @@ export function resolveSaveContextOnCollectionReorder(payload: {
|
|||||||
)!
|
)!
|
||||||
tab.value.document.saveContext.folderPath = newPath
|
tab.value.document.saveContext.folderPath = newPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
tab.value.document.saveContext?.originLocation !==
|
|
||||||
"workspace-user-collection"
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestHandleRef = tab.value.document.saveContext.requestHandle?.get()
|
|
||||||
|
|
||||||
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const { requestID } = requestHandleRef.value.data
|
|
||||||
|
|
||||||
const collectionID = requestID.split("/").slice(0, -1).join("/")
|
|
||||||
|
|
||||||
const newCollectionID = affectedPaths.get(collectionID)
|
|
||||||
const newRequestID = `${newCollectionID}/${
|
|
||||||
requestID.split("/").slice(-1)[0]
|
|
||||||
}`
|
|
||||||
|
|
||||||
requestHandleRef.value.data = {
|
|
||||||
...requestHandleRef.value.data,
|
|
||||||
collectionID: newCollectionID!,
|
|
||||||
requestID: newRequestID,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,63 +86,25 @@ export function resolveSaveContextOnCollectionReorder(payload: {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function updateSaveContextForAffectedRequests(
|
export function updateSaveContextForAffectedRequests(
|
||||||
draggedCollectionIndex: string,
|
oldFolderPath: string,
|
||||||
destinationCollectionIndex: string
|
newFolderPath: string
|
||||||
) {
|
) {
|
||||||
const tabService = getService(RESTTabService)
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
|
return (
|
||||||
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
|
tab.document.saveContext.folderPath.startsWith(oldFolderPath)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const activeTabs = tabService.getActiveTabs()
|
for (const tab of tabs) {
|
||||||
|
if (tab.value.document.saveContext?.originLocation === "user-collection") {
|
||||||
for (const tab of activeTabs.value) {
|
tab.value.document.saveContext = {
|
||||||
if (tab.document.saveContext?.originLocation === "user-collection") {
|
...tab.value.document.saveContext,
|
||||||
const { folderPath } = tab.document.saveContext
|
folderPath: tab.value.document.saveContext.folderPath.replace(
|
||||||
|
oldFolderPath,
|
||||||
if (folderPath.startsWith(draggedCollectionIndex)) {
|
newFolderPath
|
||||||
const newFolderPath = folderPath.replace(
|
),
|
||||||
draggedCollectionIndex,
|
|
||||||
destinationCollectionIndex
|
|
||||||
)
|
|
||||||
|
|
||||||
tab.document.saveContext = {
|
|
||||||
...tab.document.saveContext,
|
|
||||||
folderPath: newFolderPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
tab.document.saveContext?.originLocation === "workspace-user-collection"
|
|
||||||
) {
|
|
||||||
const requestHandleRef = tab.document.saveContext.requestHandle?.get()
|
|
||||||
|
|
||||||
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const { requestID } = requestHandleRef.value.data
|
|
||||||
|
|
||||||
const collectionID = requestID.split("/").slice(0, -1).join("/")
|
|
||||||
const requestIndex = requestID.split("/").slice(-1)[0]
|
|
||||||
|
|
||||||
if (collectionID.startsWith(draggedCollectionIndex)) {
|
|
||||||
const newCollectionID = collectionID.replace(
|
|
||||||
draggedCollectionIndex,
|
|
||||||
destinationCollectionIndex
|
|
||||||
)
|
|
||||||
const newRequestID = `${newCollectionID}/${requestIndex}`
|
|
||||||
|
|
||||||
tab.document.saveContext = {
|
|
||||||
...tab.document.saveContext,
|
|
||||||
requestID: newRequestID,
|
|
||||||
}
|
|
||||||
|
|
||||||
requestHandleRef.value.data = {
|
|
||||||
...requestHandleRef.value.data,
|
|
||||||
collectionID: newCollectionID,
|
|
||||||
requestID: newRequestID,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,33 +166,6 @@ function removeDuplicatesAndKeepLast(arr: HoppInheritedProperty["headers"]) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSaveContextCollectionID(
|
|
||||||
saveContext: HoppRESTSaveContext | HoppGQLSaveContext | undefined
|
|
||||||
): string | undefined {
|
|
||||||
if (!saveContext) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { originLocation } = saveContext
|
|
||||||
|
|
||||||
if (originLocation === "team-collection") {
|
|
||||||
return saveContext.collectionID
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originLocation === "user-collection") {
|
|
||||||
return saveContext.folderPath
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestHandleRef = saveContext.requestHandle?.get()
|
|
||||||
|
|
||||||
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove `collectionID` and obtain it from `requestID`
|
|
||||||
return requestHandleRef.value.data.collectionID
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateInheritedPropertiesForAffectedRequests(
|
export function updateInheritedPropertiesForAffectedRequests(
|
||||||
path: string,
|
path: string,
|
||||||
inheritedProperties: HoppInheritedProperty,
|
inheritedProperties: HoppInheritedProperty,
|
||||||
@@ -281,17 +177,22 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||||||
const effectedTabs = tabService.getTabsRefTo((tab) => {
|
const effectedTabs = tabService.getTabsRefTo((tab) => {
|
||||||
const saveContext = tab.document.saveContext
|
const saveContext = tab.document.saveContext
|
||||||
|
|
||||||
const collectionID = getSaveContextCollectionID(saveContext)
|
const saveContextPath =
|
||||||
return collectionID?.startsWith(path) ?? false
|
saveContext?.originLocation === "team-collection"
|
||||||
|
? saveContext.collectionID
|
||||||
|
: saveContext?.folderPath
|
||||||
|
|
||||||
|
return saveContextPath?.startsWith(path) ?? false
|
||||||
})
|
})
|
||||||
|
|
||||||
effectedTabs.map((tab) => {
|
effectedTabs.map((tab) => {
|
||||||
const inheritedParentID =
|
const inheritedParentID =
|
||||||
tab.value.document.inheritedProperties?.auth.parentID
|
tab.value.document.inheritedProperties?.auth.parentID
|
||||||
|
|
||||||
const contextPath = getSaveContextCollectionID(
|
const contextPath =
|
||||||
tab.value.document.saveContext
|
tab.value.document.saveContext?.originLocation === "team-collection"
|
||||||
)
|
? tab.value.document.saveContext.collectionID
|
||||||
|
: tab.value.document.saveContext?.folderPath
|
||||||
|
|
||||||
const effectedPath = folderPathCloseToSaveContext(
|
const effectedPath = folderPathCloseToSaveContext(
|
||||||
inheritedParentID,
|
inheritedParentID,
|
||||||
@@ -326,6 +227,21 @@ export function updateInheritedPropertiesForAffectedRequests(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetSaveContextForAffectedRequests(folderPath: string) {
|
||||||
|
const tabService = getService(RESTTabService)
|
||||||
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
|
return (
|
||||||
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
|
tab.document.saveContext.folderPath.startsWith(folderPath)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const tab of tabs) {
|
||||||
|
tab.value.document.saveContext = null
|
||||||
|
tab.value.document.isDirty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset save context to null if requests are deleted from the team collection or its folder
|
* Reset save context to null if requests are deleted from the team collection or its folder
|
||||||
* only runs when collection or folder is deleted
|
* only runs when collection or folder is deleted
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import {
|
|||||||
HoppGQLRequest,
|
HoppGQLRequest,
|
||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
|
|
||||||
import { getService } from "~/modules/dioc"
|
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
|
||||||
import { getAffectedIndexes } from "./affectedIndex"
|
import { getAffectedIndexes } from "./affectedIndex"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { getService } from "~/modules/dioc"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve save context on reorder
|
* Resolve save context on reorder
|
||||||
@@ -30,76 +29,30 @@ export function resolveSaveContextOnRequestReorder(payload: {
|
|||||||
if (newIndex > lastIndex) newIndex-- // there is a issue when going down? better way to resolve this?
|
if (newIndex > lastIndex) newIndex-- // there is a issue when going down? better way to resolve this?
|
||||||
if (lastIndex === newIndex) return
|
if (lastIndex === newIndex) return
|
||||||
|
|
||||||
const affectedIndices = getAffectedIndexes(
|
const affectedIndexes = getAffectedIndexes(
|
||||||
lastIndex,
|
lastIndex,
|
||||||
newIndex === -1 ? length! : newIndex
|
newIndex === -1 ? length! : newIndex
|
||||||
)
|
)
|
||||||
|
|
||||||
// if (newIndex === -1) remove it from the map because it will be deleted
|
// if (newIndex === -1) remove it from the map because it will be deleted
|
||||||
if (newIndex === -1) affectedIndices.delete(lastIndex)
|
if (newIndex === -1) affectedIndexes.delete(lastIndex)
|
||||||
|
|
||||||
const tabService = getService(RESTTabService)
|
const tabService = getService(RESTTabService)
|
||||||
const tabs = tabService.getTabsRefTo((tab) => {
|
const tabs = tabService.getTabsRefTo((tab) => {
|
||||||
if (tab.document.saveContext?.originLocation === "user-collection") {
|
return (
|
||||||
return (
|
tab.document.saveContext?.originLocation === "user-collection" &&
|
||||||
tab.document.saveContext.folderPath === folderPath &&
|
tab.document.saveContext.folderPath === folderPath &&
|
||||||
affectedIndices.has(tab.document.saveContext.requestIndex)
|
affectedIndexes.has(tab.document.saveContext.requestIndex)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
tab.document.saveContext?.originLocation !== "workspace-user-collection"
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestHandleRef = tab.document.saveContext.requestHandle?.get()
|
|
||||||
|
|
||||||
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const { requestID } = requestHandleRef.value.data
|
|
||||||
const collectionID = requestID.split("/").slice(0, -1).join("/")
|
|
||||||
const requestIndex = parseInt(requestID.split("/").slice(-1)[0])
|
|
||||||
|
|
||||||
return collectionID === folderPath && affectedIndices.has(requestIndex)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
if (tab.value.document.saveContext?.originLocation === "user-collection") {
|
if (tab.value.document.saveContext?.originLocation === "user-collection") {
|
||||||
const newIndex = affectedIndices.get(
|
const newIndex = affectedIndexes.get(
|
||||||
tab.value.document.saveContext?.requestIndex
|
tab.value.document.saveContext?.requestIndex
|
||||||
)!
|
)!
|
||||||
tab.value.document.saveContext.requestIndex = newIndex
|
tab.value.document.saveContext.requestIndex = newIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
tab.value.document.saveContext?.originLocation !==
|
|
||||||
"workspace-user-collection"
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestHandleRef = tab.value.document.saveContext.requestHandle?.get()
|
|
||||||
|
|
||||||
if (!requestHandleRef || requestHandleRef.value.type === "invalid") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { requestID } = requestHandleRef.value.data
|
|
||||||
|
|
||||||
const requestIDArr = requestID.split("/")
|
|
||||||
const requestIndex = affectedIndices.get(
|
|
||||||
parseInt(requestIDArr[requestIDArr.length - 1])
|
|
||||||
)!
|
|
||||||
|
|
||||||
requestIDArr[requestIDArr.length - 1] = requestIndex.toString()
|
|
||||||
|
|
||||||
requestHandleRef.value.data.requestID = requestIDArr.join("/")
|
|
||||||
requestHandleRef.value.data.collectionID = requestIDArr
|
|
||||||
.slice(0, -1)
|
|
||||||
.join("/")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +67,7 @@ export function getRequestsByPath(
|
|||||||
|
|
||||||
if (pathArray.length === 1) {
|
if (pathArray.length === 1) {
|
||||||
const latestVersionedRequests = currentCollection.requests.filter(
|
const latestVersionedRequests = currentCollection.requests.filter(
|
||||||
(req): req is HoppRESTRequest => req.v === "4"
|
(req): req is HoppRESTRequest => req.v === "3"
|
||||||
)
|
)
|
||||||
|
|
||||||
return latestVersionedRequests
|
return latestVersionedRequests
|
||||||
@@ -125,7 +78,7 @@ export function getRequestsByPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const latestVersionedRequests = currentCollection.requests.filter(
|
const latestVersionedRequests = currentCollection.requests.filter(
|
||||||
(req): req is HoppRESTRequest => req.v === "4"
|
(req): req is HoppRESTRequest => req.v === "3"
|
||||||
)
|
)
|
||||||
|
|
||||||
return latestVersionedRequests
|
return latestVersionedRequests
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a downloadable file from a collection and prompts the user to download it.
|
* Create a downloadable file from a collection and prompts the user to download it.
|
||||||
* @param collectionJSON - JSON string of the collection
|
* @param collectionJSON - JSON string of the collection
|
||||||
* @param name - Name of the collection set as the file name
|
* @param name - Name of the collection set as the file name
|
||||||
*/
|
*/
|
||||||
export const initializeDownloadFile = async (
|
export const initializeDownloadCollection = (
|
||||||
collectionJSON: string,
|
collectionJSON: string,
|
||||||
name: string | null
|
name: string | null
|
||||||
) => {
|
) => {
|
||||||
const result = await platform.io.saveFileWithDialog({
|
const file = new Blob([collectionJSON], { type: "application/json" })
|
||||||
data: collectionJSON,
|
const a = document.createElement("a")
|
||||||
contentType: "application/json",
|
const url = URL.createObjectURL(file)
|
||||||
suggestedFilename: `${name ?? "collection"}.json`,
|
a.href = url
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "Hoppscotch Collection/Environment JSON file",
|
|
||||||
extensions: ["json"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.type === "unknown" || result.type === "saved") {
|
if (name) {
|
||||||
return E.right("state.download_started")
|
a.download = `${name}.json`
|
||||||
|
} else {
|
||||||
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
}
|
}
|
||||||
|
|
||||||
return E.left("state.download_failed")
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return E.right("state.download_started")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { HoppCollection } from "@hoppscotch/data"
|
||||||
|
|
||||||
|
export const myCollectionsExporter = (myCollections: HoppCollection[]) => {
|
||||||
|
return JSON.stringify(myCollections, null, 2)
|
||||||
|
}
|
||||||
@@ -3,37 +3,8 @@ import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
|||||||
import { HoppTestResult } from "../types/HoppTestResult"
|
import { HoppTestResult } from "../types/HoppTestResult"
|
||||||
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||||
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
|
||||||
import { Handle } from "~/services/new-workspace/handle"
|
|
||||||
import { WorkspaceRequest } from "~/services/new-workspace/workspace"
|
|
||||||
|
|
||||||
export type HoppRESTSaveContext =
|
export type HoppRESTSaveContext =
|
||||||
| {
|
|
||||||
/**
|
|
||||||
* The origin source of the request
|
|
||||||
*/
|
|
||||||
// TODO: Make this `user-collection` after porting all usages
|
|
||||||
// Future TODO: Keep separate types for the IDs (specific to persistence) & `requestHandle` (only existing at runtime)
|
|
||||||
originLocation: "workspace-user-collection"
|
|
||||||
/**
|
|
||||||
* ID of the workspace
|
|
||||||
*/
|
|
||||||
workspaceID?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ID of the provider
|
|
||||||
*/
|
|
||||||
providerID?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Path to the request in the collection tree
|
|
||||||
*/
|
|
||||||
requestID?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle to the request open in the tab
|
|
||||||
*/
|
|
||||||
requestHandle?: Handle<WorkspaceRequest>
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
/**
|
/**
|
||||||
* The origin source of the request
|
* The origin source of the request
|
||||||
|
|||||||
@@ -17,24 +17,3 @@ export type HoppInheritedProperty = {
|
|||||||
inheritedHeader: HoppRESTHeader | GQLHeader
|
inheritedHeader: HoppRESTHeader | GQLHeader
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModifiedAuth<T, AuthType> = {
|
|
||||||
[K in keyof T]: K extends "inheritedAuth" ? Extract<T[K], AuthType> : T[K]
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModifiedHeaders<T, HeaderType> = {
|
|
||||||
[K in keyof T]: K extends "inheritedHeader" ? Extract<T[K], HeaderType> : T[K]
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModifiedHoppInheritedProperty<AuthType, HeaderType> = {
|
|
||||||
auth: ModifiedAuth<HoppInheritedProperty["auth"], AuthType>
|
|
||||||
headers: ModifiedHeaders<
|
|
||||||
HoppInheritedProperty["headers"][number],
|
|
||||||
HeaderType
|
|
||||||
>[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HoppInheritedRESTProperty = ModifiedHoppInheritedProperty<
|
|
||||||
HoppRESTAuth,
|
|
||||||
HoppRESTHeader
|
|
||||||
>
|
|
||||||
|
|||||||
@@ -20,29 +20,28 @@ export type Picked =
|
|||||||
pickedType: "my-collection"
|
pickedType: "my-collection"
|
||||||
collectionIndex: number
|
collectionIndex: number
|
||||||
}
|
}
|
||||||
// TODO: Enable this when rest of the implementation is in place
|
| {
|
||||||
// | {
|
pickedType: "teams-request"
|
||||||
// pickedType: "teams-request"
|
requestID: string
|
||||||
// requestID: string
|
}
|
||||||
// }
|
| {
|
||||||
// | {
|
pickedType: "teams-folder"
|
||||||
// pickedType: "teams-folder"
|
folderID: string
|
||||||
// folderID: string
|
}
|
||||||
// }
|
| {
|
||||||
// | {
|
pickedType: "teams-collection"
|
||||||
// pickedType: "teams-collection"
|
collectionID: string
|
||||||
// collectionID: string
|
}
|
||||||
// }
|
| {
|
||||||
// | {
|
pickedType: "gql-my-request"
|
||||||
// pickedType: "gql-my-request"
|
folderPath: string
|
||||||
// folderPath: string
|
requestIndex: number
|
||||||
// requestIndex: number
|
}
|
||||||
// }
|
| {
|
||||||
// | {
|
pickedType: "gql-my-folder"
|
||||||
// pickedType: "gql-my-folder"
|
folderPath: string
|
||||||
// folderPath: string
|
}
|
||||||
// }
|
| {
|
||||||
// | {
|
pickedType: "gql-my-collection"
|
||||||
// pickedType: "gql-my-collection"
|
collectionIndex: number
|
||||||
// collectionIndex: number
|
}
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* Create a function that will only run the given function once and caches the result.
|
|
||||||
*/
|
|
||||||
export function lazy<T>(fn: () => T): () => T {
|
|
||||||
let funcRan = false
|
|
||||||
let result: T | null = null
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (!funcRan) {
|
|
||||||
result = fn()
|
|
||||||
funcRan = true
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
return result!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,40 +3,36 @@ import { createApp } from "vue"
|
|||||||
import { initializeApp } from "./helpers/app"
|
import { initializeApp } from "./helpers/app"
|
||||||
import { initBackendGQLClient } from "./helpers/backend/GQLClient"
|
import { initBackendGQLClient } from "./helpers/backend/GQLClient"
|
||||||
import { performMigrations } from "./helpers/migrations"
|
import { performMigrations } from "./helpers/migrations"
|
||||||
import { getService } from "./modules/dioc"
|
|
||||||
import { PlatformDef, setPlatformDef } from "./platform"
|
import { PlatformDef, setPlatformDef } from "./platform"
|
||||||
import { PersonalWorkspaceProviderService } from "./services/new-workspace/providers/personal.workspace"
|
|
||||||
import { TestWorkspaceProviderService } from "./services/new-workspace/providers/test.workspace"
|
|
||||||
|
|
||||||
import { PersistenceService } from "./services/persistence"
|
|
||||||
|
|
||||||
import "nprogress/nprogress.css"
|
|
||||||
import "../assets/scss/styles.scss"
|
|
||||||
import "../assets/scss/tailwind.scss"
|
import "../assets/scss/tailwind.scss"
|
||||||
import "../assets/themes/themes.scss"
|
import "../assets/themes/themes.scss"
|
||||||
|
import "../assets/scss/styles.scss"
|
||||||
|
import "nprogress/nprogress.css"
|
||||||
|
|
||||||
|
import "unfonts.css"
|
||||||
|
|
||||||
import App from "./App.vue"
|
import App from "./App.vue"
|
||||||
|
import { getService } from "./modules/dioc"
|
||||||
|
import { PersistenceService } from "./services/persistence"
|
||||||
|
|
||||||
export function createHoppApp(el: string | Element, platformDef: PlatformDef) {
|
export function createHoppApp(el: string | Element, platformDef: PlatformDef) {
|
||||||
setPlatformDef(platformDef)
|
setPlatformDef(platformDef)
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app))
|
|
||||||
platformDef.addedHoppModules?.forEach((mod) => mod.onVueAppInit?.(app))
|
|
||||||
|
|
||||||
// Some basic work that needs to be done before module inits even
|
// Some basic work that needs to be done before module inits even
|
||||||
initBackendGQLClient()
|
initBackendGQLClient()
|
||||||
initializeApp()
|
initializeApp()
|
||||||
|
|
||||||
|
HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app))
|
||||||
|
platformDef.addedHoppModules?.forEach((mod) => mod.onVueAppInit?.(app))
|
||||||
|
|
||||||
// TODO: Explore possibilities of moving this invocation to the service constructor
|
// TODO: Explore possibilities of moving this invocation to the service constructor
|
||||||
// `toast` was coming up as `null` in the previous attempts
|
// `toast` was coming up as `null` in the previous attempts
|
||||||
getService(PersistenceService).setupLocalPersistence()
|
getService(PersistenceService).setupLocalPersistence()
|
||||||
performMigrations()
|
performMigrations()
|
||||||
|
|
||||||
// TODO: Remove this
|
|
||||||
getService(TestWorkspaceProviderService)
|
|
||||||
getService(PersonalWorkspaceProviderService)
|
|
||||||
|
|
||||||
app.mount(el)
|
app.mount(el)
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
|
|||||||
@@ -319,7 +319,6 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
)
|
)
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We get the index path to the folder itself,
|
// We get the index path to the folder itself,
|
||||||
// we have to find the folder containing the target folder,
|
// we have to find the folder containing the target folder,
|
||||||
// so we pop the last path index
|
// so we pop the last path index
|
||||||
@@ -691,11 +690,17 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
|
|
||||||
// if the destination is null, we are moving to the end of the list
|
// if the destination is null, we are moving to the end of the list
|
||||||
if (destinationRequestIndex === null) {
|
if (destinationRequestIndex === null) {
|
||||||
// TODO: Verify if this can be safely removed
|
// move to the end of the list
|
||||||
targetLocation.requests.push(
|
targetLocation.requests.push(
|
||||||
targetLocation.requests.splice(requestIndex, 1)[0]
|
targetLocation.requests.splice(requestIndex, 1)[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
resolveSaveContextOnRequestReorder({
|
||||||
|
lastIndex: requestIndex,
|
||||||
|
newIndex: targetLocation.requests.length,
|
||||||
|
folderPath: destinationCollectionPath,
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: newState,
|
state: newState,
|
||||||
}
|
}
|
||||||
@@ -703,6 +708,12 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
|
|
||||||
reorderItems(targetLocation.requests, requestIndex, destinationRequestIndex)
|
reorderItems(targetLocation.requests, requestIndex, destinationRequestIndex)
|
||||||
|
|
||||||
|
resolveSaveContextOnRequestReorder({
|
||||||
|
lastIndex: requestIndex,
|
||||||
|
newIndex: destinationRequestIndex,
|
||||||
|
folderPath: destinationCollectionPath,
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: newState,
|
state: newState,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<span
|
<span
|
||||||
v-if="getTabDirtyStatus(tab)"
|
v-if="tab.document.isDirty"
|
||||||
class="flex w-4 items-center justify-center text-secondary group-hover:hidden"
|
class="flex w-4 items-center justify-center text-secondary group-hover:hidden"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -64,13 +64,6 @@
|
|||||||
@submit="renameReqName"
|
@submit="renameReqName"
|
||||||
@hide-modal="showRenamingReqNameModal = false"
|
@hide-modal="showRenamingReqNameModal = false"
|
||||||
/>
|
/>
|
||||||
<HoppSmartConfirmModal
|
|
||||||
:show="confirmingCloseForTabID !== null"
|
|
||||||
:confirm="t('modal.close_unsaved_tab')"
|
|
||||||
:title="t('confirm.save_unsaved_tab')"
|
|
||||||
@hide-modal="onCloseConfirmSaveTab"
|
|
||||||
@resolve="onResolveConfirmSaveTab"
|
|
||||||
/>
|
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmingCloseAllTabs"
|
:show="confirmingCloseAllTabs"
|
||||||
:confirm="t('modal.close_unsaved_tab')"
|
:confirm="t('modal.close_unsaved_tab')"
|
||||||
@@ -78,6 +71,36 @@
|
|||||||
@hide-modal="confirmingCloseAllTabs = false"
|
@hide-modal="confirmingCloseAllTabs = false"
|
||||||
@resolve="onResolveConfirmCloseAllTabs"
|
@resolve="onResolveConfirmCloseAllTabs"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartModal
|
||||||
|
v-if="confirmingCloseForTabID !== null"
|
||||||
|
dialog
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
:title="t('modal.close_unsaved_tab')"
|
||||||
|
@close="confirmingCloseForTabID = null"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<div class="text-center">
|
||||||
|
{{ t("confirm.save_unsaved_tab") }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<span class="flex space-x-2">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
v-focus
|
||||||
|
:label="t?.('action.yes')"
|
||||||
|
outline
|
||||||
|
@click="onResolveConfirmSaveTab"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t?.('action.no')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="onCloseConfirmSaveTab"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</HoppSmartModal>
|
||||||
<CollectionsSaveRequest
|
<CollectionsSaveRequest
|
||||||
v-if="savingRequest"
|
v-if="savingRequest"
|
||||||
mode="rest"
|
mode="rest"
|
||||||
@@ -95,24 +118,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from "@composables/i18n"
|
import { ref, onMounted } from "vue"
|
||||||
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
import { safelyExtractRESTRequest } from "@hoppscotch/data"
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { cloneDeep } from "lodash-es"
|
|
||||||
import { onMounted, ref } from "vue"
|
|
||||||
import { useRoute } from "vue-router"
|
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
|
||||||
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
|
||||||
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
import { useRoute } from "vue-router"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
import { defineActionHandler, invokeAction } from "~/helpers/actions"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
|
||||||
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
|
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
|
||||||
|
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
||||||
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
||||||
import { HoppTab } from "~/services/tab"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { HoppTab } from "~/services/tab"
|
||||||
|
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||||
|
|
||||||
const savingRequest = ref(false)
|
const savingRequest = ref(false)
|
||||||
const confirmingCloseForTabID = ref<string | null>(null)
|
const confirmingCloseForTabID = ref<string | null>(null)
|
||||||
@@ -193,7 +216,7 @@ const inspectionService = useService(InspectionService)
|
|||||||
const removeTab = (tabID: string) => {
|
const removeTab = (tabID: string) => {
|
||||||
const tabState = tabs.getTabRef(tabID).value
|
const tabState = tabs.getTabRef(tabID).value
|
||||||
|
|
||||||
if (getTabDirtyStatus(tabState)) {
|
if (tabState.document.isDirty) {
|
||||||
confirmingCloseForTabID.value = tabID
|
confirmingCloseForTabID.value = tabID
|
||||||
} else {
|
} else {
|
||||||
tabs.closeTab(tabState.id)
|
tabs.closeTab(tabState.id)
|
||||||
@@ -202,10 +225,8 @@ const removeTab = (tabID: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closeOtherTabsAction = (tabID: string) => {
|
const closeOtherTabsAction = (tabID: string) => {
|
||||||
|
const isTabDirty = tabs.getTabRef(tabID).value?.document.isDirty
|
||||||
const dirtyTabCount = tabs.getDirtyTabsCount()
|
const dirtyTabCount = tabs.getDirtyTabsCount()
|
||||||
|
|
||||||
const isTabDirty = getTabDirtyStatus(tabs.getTabRef(tabID).value)
|
|
||||||
|
|
||||||
// If current tab is dirty, so we need to subtract 1 from the dirty tab count
|
// If current tab is dirty, so we need to subtract 1 from the dirty tab count
|
||||||
const balanceDirtyTabCount = isTabDirty ? dirtyTabCount - 1 : dirtyTabCount
|
const balanceDirtyTabCount = isTabDirty ? dirtyTabCount - 1 : dirtyTabCount
|
||||||
|
|
||||||
@@ -270,24 +291,15 @@ const onCloseConfirmSaveTab = () => {
|
|||||||
* Called when the user confirms they want to save the tab
|
* Called when the user confirms they want to save the tab
|
||||||
*/
|
*/
|
||||||
const onResolveConfirmSaveTab = () => {
|
const onResolveConfirmSaveTab = () => {
|
||||||
const { saveContext } = tabs.currentActiveTab.value.document
|
if (tabs.currentActiveTab.value.document.saveContext) {
|
||||||
|
invokeAction("request.save")
|
||||||
|
|
||||||
// There're two cases where the save request under a collection modal should open
|
if (confirmingCloseForTabID.value) {
|
||||||
// 1. Attempting to save a request that is not under a collection (When the save context is not available)
|
tabs.closeTab(confirmingCloseForTabID.value)
|
||||||
// 2. Deleting a request from the collection tree and attempting to save it while closing the respective tab (When the request handle is invalid)
|
confirmingCloseForTabID.value = null
|
||||||
if (
|
}
|
||||||
!saveContext ||
|
} else {
|
||||||
(saveContext.originLocation === "workspace-user-collection" &&
|
savingRequest.value = true
|
||||||
saveContext.requestHandle?.get().value.type === "invalid")
|
|
||||||
) {
|
|
||||||
return (savingRequest.value = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
invokeAction("request.save")
|
|
||||||
|
|
||||||
if (confirmingCloseForTabID.value) {
|
|
||||||
tabs.closeTab(confirmingCloseForTabID.value)
|
|
||||||
confirmingCloseForTabID.value = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,17 +327,6 @@ const shareTabRequest = (tabID: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTabDirtyStatus = (tab: HoppTab<HoppRESTDocument>) => {
|
|
||||||
if (tab.document.isDirty) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
tab.document.saveContext?.originLocation === "workspace-user-collection" &&
|
|
||||||
tab.document.saveContext.requestHandle?.get().value.type === "invalid"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
defineActionHandler("contextmenu.open", ({ position, text }) => {
|
defineActionHandler("contextmenu.open", ({ position, text }) => {
|
||||||
if (text) {
|
if (text) {
|
||||||
contextMenu.value = {
|
contextMenu.value = {
|
||||||
|
|||||||
@@ -13,17 +13,15 @@ export const browserIODef: IOPlatformDef = {
|
|||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
|
||||||
a.href = url
|
a.href = url
|
||||||
a.download =
|
a.download = pipe(
|
||||||
opts.suggestedFilename ??
|
url,
|
||||||
pipe(
|
S.split("/"),
|
||||||
url,
|
RNEA.last,
|
||||||
S.split("/"),
|
S.split("#"),
|
||||||
RNEA.last,
|
RNEA.head,
|
||||||
S.split("#"),
|
S.split("?"),
|
||||||
RNEA.head,
|
RNEA.head
|
||||||
S.split("?"),
|
)
|
||||||
RNEA.head
|
|
||||||
)
|
|
||||||
|
|
||||||
document.body.appendChild(a)
|
document.body.appendChild(a)
|
||||||
a.click()
|
a.click()
|
||||||
|
|||||||
@@ -121,11 +121,6 @@ export class InspectionService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initializeListeners() {
|
private initializeListeners() {
|
||||||
console.log(
|
|
||||||
`Current active tab from inspection service is `,
|
|
||||||
this.restTab.currentActiveTab.value
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [this.inspectors.entries(), this.restTab.currentActiveTab.value.id],
|
() => [this.inspectors.entries(), this.restTab.currentActiveTab.value.id],
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Ref, WritableComputedRef } from "vue"
|
|
||||||
|
|
||||||
export type Handle<T, InvalidateReason = unknown> = {
|
|
||||||
get: () => HandleRef<T, InvalidateReason>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HandleRef<T, InvalidateReason = unknown> = Ref<
|
|
||||||
HandleState<T, InvalidateReason>
|
|
||||||
>
|
|
||||||
|
|
||||||
export type HandleState<T, InvalidateReason> =
|
|
||||||
| { type: "ok"; data: T }
|
|
||||||
| { type: "invalid"; reason: InvalidateReason }
|
|
||||||
|
|
||||||
export type WritableHandleRef<
|
|
||||||
T,
|
|
||||||
InvalidateReason = unknown,
|
|
||||||
> = WritableComputedRef<HandleState<T, InvalidateReason>>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { Ref } from "vue"
|
|
||||||
import { HandleRef } from "./handle"
|
|
||||||
import { Workspace, WorkspaceCollection, WorkspaceRequest } from "./workspace"
|
|
||||||
|
|
||||||
export const isValidWorkspaceHandle = (
|
|
||||||
workspaceHandle: HandleRef<Workspace>,
|
|
||||||
providerID: string,
|
|
||||||
workspaceID: string
|
|
||||||
): workspaceHandle is Ref<{
|
|
||||||
data: Workspace
|
|
||||||
type: "ok"
|
|
||||||
}> => {
|
|
||||||
return (
|
|
||||||
workspaceHandle.value.type === "ok" &&
|
|
||||||
workspaceHandle.value.data.providerID === providerID &&
|
|
||||||
workspaceHandle.value.data.workspaceID === workspaceID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isValidCollectionHandle = (
|
|
||||||
collectionHandle: HandleRef<WorkspaceCollection>,
|
|
||||||
providerID: string,
|
|
||||||
workspaceID: string
|
|
||||||
): collectionHandle is Ref<{
|
|
||||||
data: WorkspaceCollection
|
|
||||||
type: "ok"
|
|
||||||
}> => {
|
|
||||||
return (
|
|
||||||
collectionHandle.value.type === "ok" &&
|
|
||||||
collectionHandle.value.data.providerID === providerID &&
|
|
||||||
collectionHandle.value.data.workspaceID === workspaceID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isValidRequestHandle = (
|
|
||||||
requestHandle: HandleRef<WorkspaceRequest>,
|
|
||||||
providerID: string,
|
|
||||||
workspaceID: string
|
|
||||||
): requestHandle is Ref<{
|
|
||||||
data: WorkspaceRequest
|
|
||||||
type: "ok"
|
|
||||||
}> => {
|
|
||||||
return (
|
|
||||||
requestHandle.value.type === "ok" &&
|
|
||||||
requestHandle.value.data.providerID === providerID &&
|
|
||||||
requestHandle.value.data.workspaceID === workspaceID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,807 +0,0 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { Service } from "dioc"
|
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
import {
|
|
||||||
Component,
|
|
||||||
Ref,
|
|
||||||
computed,
|
|
||||||
markRaw,
|
|
||||||
shallowReactive,
|
|
||||||
shallowRef,
|
|
||||||
watch,
|
|
||||||
} from "vue"
|
|
||||||
import { Handle } from "./handle"
|
|
||||||
import { WorkspaceProvider } from "./provider"
|
|
||||||
import {
|
|
||||||
RESTCollectionChildrenView,
|
|
||||||
RESTCollectionJSONView,
|
|
||||||
RESTCollectionLevelAuthHeadersView,
|
|
||||||
RESTSearchResultsView,
|
|
||||||
RootRESTCollectionView,
|
|
||||||
} from "./view"
|
|
||||||
import { Workspace, WorkspaceCollection, WorkspaceRequest } from "./workspace"
|
|
||||||
|
|
||||||
export type WorkspaceError<ServiceErr> =
|
|
||||||
| { type: "SERVICE_ERROR"; error: ServiceErr }
|
|
||||||
| { type: "PROVIDER_ERROR"; error: unknown }
|
|
||||||
|
|
||||||
export class NewWorkspaceService extends Service {
|
|
||||||
public static readonly ID = "NEW_WORKSPACE_SERVICE"
|
|
||||||
|
|
||||||
private registeredProviders = shallowReactive(
|
|
||||||
new Map<string, WorkspaceProvider>()
|
|
||||||
)
|
|
||||||
|
|
||||||
public activeWorkspaceHandle: Ref<Handle<Workspace> | undefined> =
|
|
||||||
shallowRef()
|
|
||||||
|
|
||||||
public activeWorkspaceDecor = computed(() => {
|
|
||||||
const activeWorkspaceHandleRef = this.activeWorkspaceHandle.value?.get()
|
|
||||||
|
|
||||||
if (activeWorkspaceHandleRef?.value.type !== "ok") {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.registeredProviders.get(
|
|
||||||
activeWorkspaceHandleRef.value.data.providerID
|
|
||||||
)!.workspaceDecor
|
|
||||||
})
|
|
||||||
|
|
||||||
public workspaceSelectorComponents = computed(() => {
|
|
||||||
const items: Component[] = []
|
|
||||||
|
|
||||||
const sortedProviders = Array.from(this.registeredProviders.values()).sort(
|
|
||||||
(a, b) =>
|
|
||||||
(b.workspaceDecor?.value.workspaceSelectorPriority ?? 0) -
|
|
||||||
(a.workspaceDecor?.value.workspaceSelectorPriority ?? 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const workspace of sortedProviders) {
|
|
||||||
if (workspace.workspaceDecor?.value?.workspaceSelectorComponent) {
|
|
||||||
items.push(workspace.workspaceDecor.value.workspaceSelectorComponent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
})
|
|
||||||
|
|
||||||
override onServiceInit() {
|
|
||||||
// Watch for situations where the handle is invalidated
|
|
||||||
// so the active workspace handle definition can be invalidated
|
|
||||||
watch(
|
|
||||||
() => {
|
|
||||||
return this.activeWorkspaceHandle.value
|
|
||||||
? [
|
|
||||||
this.activeWorkspaceHandle.value,
|
|
||||||
this.activeWorkspaceHandle.value?.get(),
|
|
||||||
]
|
|
||||||
: [this.activeWorkspaceHandle.value]
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (!this.activeWorkspaceHandle.value) return
|
|
||||||
|
|
||||||
const activeWorkspaceHandleRef = this.activeWorkspaceHandle.value.get()
|
|
||||||
|
|
||||||
if (activeWorkspaceHandleRef.value.type === "invalid") {
|
|
||||||
this.activeWorkspaceHandle.value = undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getWorkspaceHandle(
|
|
||||||
providerID: string,
|
|
||||||
workspaceID: string
|
|
||||||
): Promise<E.Either<WorkspaceError<"INVALID_PROVIDER">, Handle<Workspace>>> {
|
|
||||||
const provider = this.registeredProviders.get(providerID)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return Promise.resolve(
|
|
||||||
E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" as const })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleResult = await provider.getWorkspaceHandle(workspaceID)
|
|
||||||
|
|
||||||
if (E.isLeft(handleResult)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: handleResult.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(handleResult.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCollectionHandle(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
collectionID: string
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<WorkspaceCollection>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const workspaceHandleRef = workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
workspaceHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.getCollectionHandle(
|
|
||||||
workspaceHandle,
|
|
||||||
collectionID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRequestHandle(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
requestID: string
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<WorkspaceRequest>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const workspaceHandleRef = workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
workspaceHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.getRequestHandle(workspaceHandle, requestID)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createRESTRootCollection(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
newCollection: Partial<Exclude<HoppCollection, "id">> & { name: string }
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<WorkspaceCollection>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const workspaceHandleRef = workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
workspaceHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.createRESTRootCollection(
|
|
||||||
workspaceHandle,
|
|
||||||
newCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createRESTChildCollection(
|
|
||||||
parentCollectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
newChildCollection: Partial<HoppCollection> & { name: string }
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<WorkspaceCollection>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const parentCollectionHandleRef = parentCollectionHandle.get()
|
|
||||||
|
|
||||||
if (parentCollectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
parentCollectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.createRESTChildCollection(
|
|
||||||
parentCollectionHandle,
|
|
||||||
newChildCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
updatedCollection: Partial<HoppCollection>
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const collectionHandleRef = collectionHandle.get()
|
|
||||||
|
|
||||||
if (collectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
collectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.updateRESTCollection(
|
|
||||||
collectionHandle,
|
|
||||||
updatedCollection
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async removeRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const collectionHandleRef = collectionHandle.get()
|
|
||||||
|
|
||||||
if (collectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
collectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.removeRESTCollection(collectionHandle)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createRESTRequest(
|
|
||||||
parentCollectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
newRequest: HoppRESTRequest
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<WorkspaceRequest>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const parentCollectionHandleRef = parentCollectionHandle.get()
|
|
||||||
|
|
||||||
if (parentCollectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
parentCollectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.createRESTRequest(
|
|
||||||
parentCollectionHandle,
|
|
||||||
newRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async removeRESTRequest(
|
|
||||||
requestHandle: Handle<WorkspaceRequest>
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const requestHandleRef = requestHandle.get()
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
requestHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.removeRESTRequest(requestHandle)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateRESTRequest(
|
|
||||||
requestHandle: Handle<WorkspaceRequest>,
|
|
||||||
updatedRequest: Partial<HoppRESTRequest>
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const requestHandleRef = requestHandle.get()
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
requestHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.updateRESTRequest(
|
|
||||||
requestHandle,
|
|
||||||
updatedRequest
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async importRESTCollections(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
collections: HoppCollection[]
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<WorkspaceCollection>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const workspaceHandleRef = workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
workspaceHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.importRESTCollections(
|
|
||||||
workspaceHandle,
|
|
||||||
collections
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async exportRESTCollections(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
collections: HoppCollection[]
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const workspaceHandleRef = workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
workspaceHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.exportRESTCollections(
|
|
||||||
workspaceHandle,
|
|
||||||
collections
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async exportRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
collection: HoppCollection
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const collectionHandleRef = collectionHandle.get()
|
|
||||||
|
|
||||||
if (collectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
collectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.exportRESTCollection(
|
|
||||||
collectionHandle,
|
|
||||||
collection
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async reorderRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
destinationCollectionID: string | null
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const collectionHandleRef = collectionHandle.get()
|
|
||||||
|
|
||||||
if (collectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
collectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.reorderRESTCollection(
|
|
||||||
collectionHandle,
|
|
||||||
destinationCollectionID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async moveRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
destinationCollectionID: string | null
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const collectionHandleRef = collectionHandle.get()
|
|
||||||
|
|
||||||
if (collectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
collectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.moveRESTCollection(
|
|
||||||
collectionHandle,
|
|
||||||
destinationCollectionID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async reorderRESTRequest(
|
|
||||||
requestHandle: Handle<WorkspaceRequest>,
|
|
||||||
destinationCollectionID: string,
|
|
||||||
destinationRequestID: string | null
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const requestHandleRef = requestHandle.get()
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
requestHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.reorderRESTRequest(
|
|
||||||
requestHandle,
|
|
||||||
destinationCollectionID,
|
|
||||||
destinationRequestID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async moveRESTRequest(
|
|
||||||
requestHandle: Handle<WorkspaceRequest>,
|
|
||||||
destinationCollectionID: string
|
|
||||||
): Promise<
|
|
||||||
E.Either<WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">, void>
|
|
||||||
> {
|
|
||||||
const requestHandleRef = requestHandle.get()
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
requestHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.moveRESTRequest(
|
|
||||||
requestHandle,
|
|
||||||
destinationCollectionID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRESTCollectionChildrenView(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<RESTCollectionChildrenView>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const collectionHandleRef = collectionHandle.get()
|
|
||||||
|
|
||||||
if (collectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
collectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result =
|
|
||||||
await provider.getRESTCollectionChildrenView(collectionHandle)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRESTRootCollectionView(
|
|
||||||
workspaceHandle: Handle<Workspace>
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<RootRESTCollectionView>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const workspaceHandleRef = workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
workspaceHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.getRESTRootCollectionView(workspaceHandle)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRESTCollectionLevelAuthHeadersView(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<RESTCollectionLevelAuthHeadersView>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const collectionHandleRef = collectionHandle.get()
|
|
||||||
|
|
||||||
if (collectionHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
collectionHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result =
|
|
||||||
await provider.getRESTCollectionLevelAuthHeadersView(collectionHandle)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRESTSearchResultsView(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
searchQuery: Ref<string>
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<RESTSearchResultsView>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const workspaceHandleRef = workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
workspaceHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.getRESTSearchResultsView(
|
|
||||||
workspaceHandle,
|
|
||||||
searchQuery
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRESTCollectionJSONView(
|
|
||||||
workspaceHandle: Handle<Workspace>
|
|
||||||
): Promise<
|
|
||||||
E.Either<
|
|
||||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
|
||||||
Handle<RESTCollectionJSONView>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const workspaceHandleRef = workspaceHandle.get()
|
|
||||||
|
|
||||||
if (workspaceHandleRef.value.type === "invalid") {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const provider = this.registeredProviders.get(
|
|
||||||
workspaceHandleRef.value.data.providerID
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await provider.getRESTCollectionJSONView(workspaceHandle)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(result.right)
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerWorkspaceProvider(provider: WorkspaceProvider) {
|
|
||||||
if (this.registeredProviders.has(provider.providerID)) {
|
|
||||||
console.warn(
|
|
||||||
"Ignoring attempt to re-register workspace provider that is already existing:",
|
|
||||||
provider
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.registeredProviders.set(provider.providerID, markRaw(provider))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import { Ref } from "vue"
|
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
|
|
||||||
import { Handle } from "./handle"
|
|
||||||
import {
|
|
||||||
Workspace,
|
|
||||||
WorkspaceCollection,
|
|
||||||
WorkspaceDecor,
|
|
||||||
WorkspaceRequest,
|
|
||||||
} from "./workspace"
|
|
||||||
import {
|
|
||||||
RESTCollectionLevelAuthHeadersView,
|
|
||||||
RESTCollectionChildrenView,
|
|
||||||
RootRESTCollectionView,
|
|
||||||
RESTSearchResultsView,
|
|
||||||
RESTCollectionJSONView,
|
|
||||||
} from "./view"
|
|
||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
|
|
||||||
export interface WorkspaceProvider {
|
|
||||||
providerID: string
|
|
||||||
|
|
||||||
workspaceDecor?: Ref<WorkspaceDecor>
|
|
||||||
|
|
||||||
getWorkspaceHandle(
|
|
||||||
workspaceID: string
|
|
||||||
): Promise<E.Either<unknown, Handle<Workspace>>>
|
|
||||||
getCollectionHandle(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
collectionID: string
|
|
||||||
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>>
|
|
||||||
getRequestHandle(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
requestID: string
|
|
||||||
): Promise<E.Either<unknown, Handle<WorkspaceRequest>>>
|
|
||||||
|
|
||||||
getRESTRootCollectionView(
|
|
||||||
workspaceHandle: Handle<Workspace>
|
|
||||||
): Promise<E.Either<never, Handle<RootRESTCollectionView>>>
|
|
||||||
getRESTCollectionChildrenView(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>
|
|
||||||
): Promise<E.Either<never, Handle<RESTCollectionChildrenView>>>
|
|
||||||
getRESTCollectionLevelAuthHeadersView(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>
|
|
||||||
): Promise<E.Either<never, Handle<RESTCollectionLevelAuthHeadersView>>>
|
|
||||||
getRESTSearchResultsView(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
searchQuery: Ref<string>
|
|
||||||
): Promise<E.Either<never, Handle<RESTSearchResultsView>>>
|
|
||||||
getRESTCollectionJSONView(
|
|
||||||
workspaceHandle: Handle<Workspace>
|
|
||||||
): Promise<E.Either<never, Handle<RESTCollectionJSONView>>>
|
|
||||||
|
|
||||||
createRESTRootCollection(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
newCollection: Partial<Exclude<HoppCollection, "id">> & { name: string }
|
|
||||||
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>>
|
|
||||||
createRESTChildCollection(
|
|
||||||
parentCollectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
newChildCollection: Partial<HoppCollection> & { name: string }
|
|
||||||
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>>
|
|
||||||
updateRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
updatedCollection: Partial<HoppCollection>
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
removeRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
createRESTRequest(
|
|
||||||
parentCollectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
newRequest: HoppRESTRequest
|
|
||||||
): Promise<E.Either<unknown, Handle<WorkspaceRequest>>>
|
|
||||||
updateRESTRequest(
|
|
||||||
requestHandle: Handle<WorkspaceRequest>,
|
|
||||||
updatedRequest: Partial<HoppRESTRequest>
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
removeRESTRequest(
|
|
||||||
requestHandle: Handle<WorkspaceRequest>
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
|
|
||||||
importRESTCollections(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
collections: HoppCollection[]
|
|
||||||
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>>
|
|
||||||
exportRESTCollections(
|
|
||||||
workspaceHandle: Handle<Workspace>,
|
|
||||||
collections: HoppCollection[]
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
exportRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
collection: HoppCollection
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
|
|
||||||
reorderRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
destinationCollectionID: string | null
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
moveRESTCollection(
|
|
||||||
collectionHandle: Handle<WorkspaceCollection>,
|
|
||||||
destinationCollectionID: string | null
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
reorderRESTRequest(
|
|
||||||
requestHandle: Handle<WorkspaceRequest>,
|
|
||||||
destinationCollectionID: string,
|
|
||||||
destinationRequestID: string | null
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
moveRESTRequest(
|
|
||||||
requestHandle: Handle<WorkspaceRequest>,
|
|
||||||
destinationCollectionID: string
|
|
||||||
): Promise<E.Either<unknown, void>>
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
|||||||
import { getDefaultRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { ref, computed } from "vue"
|
|
||||||
|
|
||||||
import { HandleState } from "../../handle"
|
|
||||||
import { WorkspaceRequest } from "../../workspace"
|
|
||||||
|
|
||||||
export const generateIssuedHandleValues = (
|
|
||||||
collectionsAndRequests: { collectionID: string; requestCount: number }[]
|
|
||||||
) => {
|
|
||||||
const providerID = "PERSONAL_WORKSPACE_PROVIDER"
|
|
||||||
const workspaceID = "personal"
|
|
||||||
const issuedHandleValues: HandleState<WorkspaceRequest, unknown>[] = []
|
|
||||||
|
|
||||||
collectionsAndRequests.forEach(({ collectionID, requestCount }) => {
|
|
||||||
for (let i = 0; i < requestCount; i++) {
|
|
||||||
const requestID = `${collectionID}/${i}`
|
|
||||||
|
|
||||||
issuedHandleValues.push({
|
|
||||||
type: "ok" as const,
|
|
||||||
data: {
|
|
||||||
providerID: providerID,
|
|
||||||
workspaceID: workspaceID,
|
|
||||||
collectionID,
|
|
||||||
requestID,
|
|
||||||
request: {
|
|
||||||
...getDefaultRESTRequest(),
|
|
||||||
name: `req-${requestID}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return issuedHandleValues
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getWritableHandle = (
|
|
||||||
value: HandleState<WorkspaceRequest, unknown>
|
|
||||||
) => {
|
|
||||||
const handleRefData = ref(value)
|
|
||||||
|
|
||||||
const writableHandle = computed({
|
|
||||||
get() {
|
|
||||||
return handleRefData.value
|
|
||||||
},
|
|
||||||
set(newValue) {
|
|
||||||
handleRefData.value = newValue
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return writableHandle
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,309 +0,0 @@
|
|||||||
import { computed, markRaw, reactive, ref } from "vue"
|
|
||||||
import { useTimestamp } from "@vueuse/core"
|
|
||||||
import { Service } from "dioc"
|
|
||||||
import { WorkspaceProvider } from "../provider"
|
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
import { Handle, HandleRef } from "../handle"
|
|
||||||
import { Workspace, WorkspaceCollection } from "../workspace"
|
|
||||||
import { NewWorkspaceService } from ".."
|
|
||||||
import TestWorkspaceSelector from "~/components/workspace/TestWorkspaceSelector.vue"
|
|
||||||
import { RESTCollectionChildrenView, RootRESTCollectionView } from "../view"
|
|
||||||
import IconUser from "~icons/lucide/user"
|
|
||||||
import { get } from "lodash-es"
|
|
||||||
|
|
||||||
type TestReqDef = {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestCollDef = {
|
|
||||||
name: string
|
|
||||||
collections: TestCollDef[]
|
|
||||||
requests: TestReqDef[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestamp = useTimestamp({ interval: 3000 })
|
|
||||||
// const timestamp = ref(Date.now())
|
|
||||||
|
|
||||||
const testData = reactive({
|
|
||||||
workspaceA: {
|
|
||||||
name: computed(() => `Workspace A: ${timestamp.value}`),
|
|
||||||
collections: [
|
|
||||||
<TestCollDef>{
|
|
||||||
name: "Collection A",
|
|
||||||
collections: [
|
|
||||||
{
|
|
||||||
name: "Collection B",
|
|
||||||
collections: [
|
|
||||||
{ name: "Collection C", collections: [], requests: [] },
|
|
||||||
],
|
|
||||||
requests: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
requests: [{ name: "Request C" }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
workspaceB: {
|
|
||||||
name: "Workspace B",
|
|
||||||
collections: [
|
|
||||||
<TestCollDef>{
|
|
||||||
name: "Collection D",
|
|
||||||
collections: [{ name: "Collection E", collections: [], requests: [] }],
|
|
||||||
requests: [{ name: "Request F" }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
;(window as any).testData = testData
|
|
||||||
|
|
||||||
export class TestWorkspaceProviderService
|
|
||||||
extends Service
|
|
||||||
implements WorkspaceProvider
|
|
||||||
{
|
|
||||||
public static readonly ID = "TEST_WORKSPACE_PROVIDER_SERVICE"
|
|
||||||
|
|
||||||
public providerID = "TEST_WORKSPACE_PROVIDER"
|
|
||||||
|
|
||||||
public workspaceDecor = ref({
|
|
||||||
workspaceSelectorComponent: markRaw(TestWorkspaceSelector),
|
|
||||||
headerCurrentIcon: markRaw(IconUser),
|
|
||||||
workspaceSelectorPriority: 10,
|
|
||||||
})
|
|
||||||
|
|
||||||
private readonly workspaceService = this.bind(NewWorkspaceService)
|
|
||||||
|
|
||||||
override onServiceInit() {
|
|
||||||
this.workspaceService.registerWorkspaceProvider(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
public createRESTRootCollection(
|
|
||||||
workspaceHandle: HandleRef<Workspace>,
|
|
||||||
collectionName: string
|
|
||||||
): Promise<
|
|
||||||
E.Either<"INVALID_WORKSPACE_HANDLE", Handle<WorkspaceCollection>>
|
|
||||||
> {
|
|
||||||
if (workspaceHandle.value.type !== "ok") {
|
|
||||||
return Promise.resolve(E.left("INVALID_WORKSPACE_HANDLE" as const))
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceID = workspaceHandle.value.data.workspaceID
|
|
||||||
|
|
||||||
const newCollID =
|
|
||||||
testData[workspaceID as keyof typeof testData].collections.length
|
|
||||||
|
|
||||||
testData[workspaceID as keyof typeof testData].collections.push({
|
|
||||||
name: collectionName,
|
|
||||||
collections: [],
|
|
||||||
requests: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
return this.getCollectionHandle(workspaceHandle, newCollID.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
public createRESTChildCollection(
|
|
||||||
parentCollHandle: HandleRef<WorkspaceCollection>,
|
|
||||||
collectionName: string
|
|
||||||
): Promise<E.Either<unknown, Handle<WorkspaceCollection>>> {
|
|
||||||
// TODO: Implement
|
|
||||||
throw new Error("Method not implemented.")
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWorkspaceHandle(
|
|
||||||
workspaceID: string
|
|
||||||
): Promise<E.Either<never, Handle<Workspace>>> {
|
|
||||||
return Promise.resolve(
|
|
||||||
E.right(
|
|
||||||
computed(() => {
|
|
||||||
if (!(workspaceID in testData)) {
|
|
||||||
return {
|
|
||||||
type: "invalid",
|
|
||||||
reason: "WORKSPACE_WENT_OUT" as const,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "ok",
|
|
||||||
data: {
|
|
||||||
providerID: this.providerID,
|
|
||||||
workspaceID,
|
|
||||||
name: testData[workspaceID as keyof typeof testData].name,
|
|
||||||
collectionsAreReadonly: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCollectionHandle(
|
|
||||||
workspaceHandle: HandleRef<Workspace>,
|
|
||||||
collectionID: string
|
|
||||||
): Promise<
|
|
||||||
E.Either<"INVALID_WORKSPACE_HANDLE", Handle<WorkspaceCollection>>
|
|
||||||
> {
|
|
||||||
return Promise.resolve(
|
|
||||||
E.right(
|
|
||||||
computed(() => {
|
|
||||||
if (workspaceHandle.value.type !== "ok") {
|
|
||||||
return {
|
|
||||||
type: "invalid",
|
|
||||||
reason: "WORKSPACE_INVALIDATED" as const,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceID = workspaceHandle.value.data.workspaceID
|
|
||||||
const collectionPath = collectionID
|
|
||||||
.split("/")
|
|
||||||
.flatMap((x) => ["collections", x])
|
|
||||||
|
|
||||||
const result: TestCollDef | undefined = get(
|
|
||||||
testData[workspaceID as keyof typeof testData],
|
|
||||||
collectionPath
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return {
|
|
||||||
type: "invalid",
|
|
||||||
reason: "INVALID_COLL_ID",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "ok",
|
|
||||||
data: {
|
|
||||||
providerID: this.providerID,
|
|
||||||
workspaceID,
|
|
||||||
collectionID,
|
|
||||||
name: result.name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRESTCollectionChildrenView(
|
|
||||||
collectionHandle: HandleRef<WorkspaceCollection>
|
|
||||||
): Promise<E.Either<never, Handle<RESTCollectionChildrenView>>> {
|
|
||||||
return Promise.resolve(
|
|
||||||
E.right(
|
|
||||||
computed(() => {
|
|
||||||
if (collectionHandle.value.type === "invalid") {
|
|
||||||
return {
|
|
||||||
type: "invalid",
|
|
||||||
reason: "COLL_HANDLE_IS_INVALID" as const,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceID = collectionHandle.value.data.workspaceID
|
|
||||||
const collectionID = collectionHandle.value.data.collectionID
|
|
||||||
|
|
||||||
if (!(workspaceID in testData)) {
|
|
||||||
return {
|
|
||||||
type: "invalid",
|
|
||||||
reason: "WORKSPACE_NOT_PRESENT" as const,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionPath = collectionID
|
|
||||||
.split("/")
|
|
||||||
.flatMap((x) => ["collections", x])
|
|
||||||
|
|
||||||
return markRaw({
|
|
||||||
type: "ok",
|
|
||||||
data: {
|
|
||||||
providerID: this.providerID,
|
|
||||||
workspaceID,
|
|
||||||
collectionID,
|
|
||||||
|
|
||||||
loading: ref(false),
|
|
||||||
|
|
||||||
content: computed(() => [
|
|
||||||
...(
|
|
||||||
get(testData[workspaceID as keyof typeof testData], [
|
|
||||||
...collectionPath,
|
|
||||||
"collections",
|
|
||||||
]) as TestCollDef[]
|
|
||||||
).map((item, i) => ({
|
|
||||||
type: "collection" as const,
|
|
||||||
value: {
|
|
||||||
collectionID: `${collectionID}/${i}`,
|
|
||||||
name: item.name,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
...(
|
|
||||||
get(testData[workspaceID as keyof typeof testData], [
|
|
||||||
...collectionPath,
|
|
||||||
"requests",
|
|
||||||
]) as TestReqDef[]
|
|
||||||
).map((item, i) => ({
|
|
||||||
type: "request" as const,
|
|
||||||
value: {
|
|
||||||
requestID: `${collectionID}/${i}`,
|
|
||||||
name: item.name,
|
|
||||||
method: "get",
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRESTRootCollectionView(
|
|
||||||
workspaceHandle: HandleRef<Workspace>
|
|
||||||
): Promise<E.Either<never, Handle<RootRESTCollectionView>>> {
|
|
||||||
return Promise.resolve(
|
|
||||||
E.right(
|
|
||||||
computed(() => {
|
|
||||||
if (workspaceHandle.value.type === "invalid") {
|
|
||||||
return {
|
|
||||||
type: "invalid",
|
|
||||||
reason: "WORKSPACE_IS_INVALID" as const,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceID = workspaceHandle.value.data.workspaceID
|
|
||||||
|
|
||||||
if (!(workspaceID in testData)) {
|
|
||||||
return {
|
|
||||||
type: "invalid",
|
|
||||||
reason: "WORKSPACE_NOT_PRESENT" as const,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return markRaw({
|
|
||||||
type: "ok",
|
|
||||||
data: {
|
|
||||||
providerID: this.providerID,
|
|
||||||
workspaceID,
|
|
||||||
|
|
||||||
loading: ref(false),
|
|
||||||
|
|
||||||
collections: computed(() => {
|
|
||||||
return testData[
|
|
||||||
workspaceID as keyof typeof testData
|
|
||||||
].collections.map((x, i) => ({
|
|
||||||
collectionID: i.toString(),
|
|
||||||
name: x.name,
|
|
||||||
}))
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWorkspaceCandidates() {
|
|
||||||
return computed(() =>
|
|
||||||
Object.keys(testData).map((workspaceID) => ({
|
|
||||||
id: workspaceID,
|
|
||||||
name: testData[workspaceID as keyof typeof testData].name,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { Ref } from "vue"
|
|
||||||
import { HoppInheritedRESTProperty } from "~/helpers/types/HoppInheritedProperties"
|
|
||||||
|
|
||||||
export type RESTCollectionLevelAuthHeadersView = {
|
|
||||||
auth: HoppInheritedRESTProperty["auth"]
|
|
||||||
headers: HoppInheritedRESTProperty["headers"]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RESTCollectionViewCollection = {
|
|
||||||
collectionID: string
|
|
||||||
|
|
||||||
isLastItem: boolean
|
|
||||||
name: string
|
|
||||||
parentCollectionID: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RESTCollectionViewRequest = {
|
|
||||||
collectionID: string
|
|
||||||
requestID: string
|
|
||||||
|
|
||||||
request: HoppRESTRequest
|
|
||||||
isLastItem: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RESTCollectionViewItem =
|
|
||||||
| { type: "collection"; value: RESTCollectionViewCollection }
|
|
||||||
| { type: "request"; value: RESTCollectionViewRequest }
|
|
||||||
|
|
||||||
export interface RootRESTCollectionView {
|
|
||||||
providerID: string
|
|
||||||
workspaceID: string
|
|
||||||
|
|
||||||
loading: Ref<boolean>
|
|
||||||
|
|
||||||
collections: Ref<RESTCollectionViewCollection[]>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RESTCollectionChildrenView {
|
|
||||||
providerID: string
|
|
||||||
workspaceID: string
|
|
||||||
collectionID: string
|
|
||||||
|
|
||||||
loading: Ref<boolean>
|
|
||||||
|
|
||||||
content: Ref<RESTCollectionViewItem[]>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RESTSearchResultsView {
|
|
||||||
providerID: string
|
|
||||||
workspaceID: string
|
|
||||||
|
|
||||||
loading: Ref<boolean>
|
|
||||||
|
|
||||||
results: Ref<HoppCollection[]>
|
|
||||||
onSessionEnd: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RESTCollectionJSONView {
|
|
||||||
providerID: string
|
|
||||||
workspaceID: string
|
|
||||||
|
|
||||||
content: string
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
|
||||||
import { Component } from "vue"
|
|
||||||
|
|
||||||
export type Workspace = {
|
|
||||||
providerID: string
|
|
||||||
workspaceID: string
|
|
||||||
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WorkspaceCollection = {
|
|
||||||
providerID: string
|
|
||||||
workspaceID: string
|
|
||||||
collectionID: string
|
|
||||||
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WorkspaceRequest = {
|
|
||||||
providerID: string
|
|
||||||
workspaceID: string
|
|
||||||
collectionID: string
|
|
||||||
requestID: string
|
|
||||||
|
|
||||||
request: HoppRESTRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WorkspaceDecor = {
|
|
||||||
headerComponent?: Component
|
|
||||||
|
|
||||||
headerCurrentIcon?: Component | object
|
|
||||||
|
|
||||||
workspaceSelectorComponent?: Component
|
|
||||||
workspaceSelectorPriority?: number
|
|
||||||
}
|
|
||||||
@@ -580,6 +580,8 @@ describe("PersistenceService", () => {
|
|||||||
|
|
||||||
invokeSetupLocalPersistence()
|
invokeSetupLocalPersistence()
|
||||||
|
|
||||||
|
// toastErrorFn = vi.fn()
|
||||||
|
|
||||||
expect(getItemSpy).toHaveBeenCalledWith(settingsKey)
|
expect(getItemSpy).toHaveBeenCalledWith(settingsKey)
|
||||||
|
|
||||||
expect(toastErrorFn).not.toHaveBeenCalledWith(settingsKey)
|
expect(toastErrorFn).not.toHaveBeenCalledWith(settingsKey)
|
||||||
|
|||||||
@@ -492,15 +492,6 @@ const HoppRESTResponseSchema = z.discriminatedUnion("type", [
|
|||||||
|
|
||||||
const HoppRESTSaveContextSchema = z.nullable(
|
const HoppRESTSaveContextSchema = z.nullable(
|
||||||
z.discriminatedUnion("originLocation", [
|
z.discriminatedUnion("originLocation", [
|
||||||
z
|
|
||||||
.object({
|
|
||||||
originLocation: z.literal("workspace-user-collection"),
|
|
||||||
workspaceID: z.optional(z.string()),
|
|
||||||
providerID: z.optional(z.string()),
|
|
||||||
requestID: z.optional(z.string()),
|
|
||||||
requestHandle: z.optional(z.record(z.unknown())),
|
|
||||||
})
|
|
||||||
.strict(),
|
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
originLocation: z.literal("user-collection"),
|
originLocation: z.literal("user-collection"),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Container } from "dioc"
|
|
||||||
import { isEqual } from "lodash-es"
|
import { isEqual } from "lodash-es"
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { HoppRESTDocument, HoppRESTSaveContext } from "~/helpers/rest/document"
|
import { HoppRESTDocument, HoppRESTSaveContext } from "~/helpers/rest/document"
|
||||||
import { TabService } from "./tab"
|
import { TabService } from "./tab"
|
||||||
|
import { Container } from "dioc"
|
||||||
|
|
||||||
export class RESTTabService extends TabService<HoppRESTDocument> {
|
export class RESTTabService extends TabService<HoppRESTDocument> {
|
||||||
public static readonly ID = "REST_TAB_SERVICE"
|
public static readonly ID = "REST_TAB_SERVICE"
|
||||||
@@ -30,18 +30,17 @@ export class RESTTabService extends TabService<HoppRESTDocument> {
|
|||||||
lastActiveTabID: this.currentTabID.value,
|
lastActiveTabID: this.currentTabID.value,
|
||||||
orderedDocs: this.tabOrdering.value.map((tabID) => {
|
orderedDocs: this.tabOrdering.value.map((tabID) => {
|
||||||
const tab = this.tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
|
const tab = this.tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tabID: tab.id,
|
tabID: tab.id,
|
||||||
doc: {
|
doc: {
|
||||||
...this.getPersistedDocument(tab.document),
|
...tab.document,
|
||||||
response: null,
|
response: null,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
public getTabRefWithSaveContext(ctx: Partial<HoppRESTSaveContext>) {
|
public getTabRefWithSaveContext(ctx: HoppRESTSaveContext) {
|
||||||
for (const tab of this.tabMap.values()) {
|
for (const tab of this.tabMap.values()) {
|
||||||
// For `team-collection` request id can be considered unique
|
// For `team-collection` request id can be considered unique
|
||||||
if (ctx?.originLocation === "team-collection") {
|
if (ctx?.originLocation === "team-collection") {
|
||||||
@@ -51,40 +50,8 @@ export class RESTTabService extends TabService<HoppRESTDocument> {
|
|||||||
) {
|
) {
|
||||||
return this.getTabRef(tab.id)
|
return this.getTabRef(tab.id)
|
||||||
}
|
}
|
||||||
} else if (ctx?.originLocation === "user-collection") {
|
} else if (isEqual(ctx, tab.document.saveContext)) {
|
||||||
if (isEqual(ctx, tab.document.saveContext)) {
|
return this.getTabRef(tab.id)
|
||||||
return this.getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
ctx?.originLocation === "workspace-user-collection" &&
|
|
||||||
tab.document.saveContext?.originLocation === "workspace-user-collection"
|
|
||||||
) {
|
|
||||||
const requestHandle = tab.document.saveContext.requestHandle
|
|
||||||
|
|
||||||
if (!ctx.requestHandle || !requestHandle) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabRequestHandleRef = requestHandle.get()
|
|
||||||
const requestHandleRef = ctx.requestHandle.get()
|
|
||||||
|
|
||||||
if (
|
|
||||||
requestHandleRef.value.type === "invalid" ||
|
|
||||||
tabRequestHandleRef.value.type === "invalid"
|
|
||||||
) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
requestHandleRef.value.data.providerID ===
|
|
||||||
tabRequestHandleRef.value.data.providerID &&
|
|
||||||
requestHandleRef.value.data.workspaceID ===
|
|
||||||
tabRequestHandleRef.value.data.workspaceID &&
|
|
||||||
requestHandleRef.value.data.requestID ===
|
|
||||||
tabRequestHandleRef.value.data.requestID
|
|
||||||
) {
|
|
||||||
return this.getTabRef(tab.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,20 +62,7 @@ export class RESTTabService extends TabService<HoppRESTDocument> {
|
|||||||
let count = 0
|
let count = 0
|
||||||
|
|
||||||
for (const tab of this.tabMap.values()) {
|
for (const tab of this.tabMap.values()) {
|
||||||
if (tab.document.isDirty) {
|
if (tab.document.isDirty) count++
|
||||||
count++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
tab.document.saveContext?.originLocation === "workspace-user-collection"
|
|
||||||
) {
|
|
||||||
const requestHandle = tab.document.saveContext.requestHandle
|
|
||||||
|
|
||||||
if (requestHandle?.get().value.type === "invalid") {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return count
|
return count
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { refWithControl } from "@vueuse/core"
|
import { refWithControl } from "@vueuse/core"
|
||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
import { v4 as uuidV4 } from "uuid"
|
import { v4 as uuidV4 } from "uuid"
|
||||||
import {
|
import {
|
||||||
ComputedRef,
|
ComputedRef,
|
||||||
@@ -11,23 +10,16 @@ import {
|
|||||||
shallowReadonly,
|
shallowReadonly,
|
||||||
watch,
|
watch,
|
||||||
} from "vue"
|
} from "vue"
|
||||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
|
||||||
import {
|
import {
|
||||||
HoppTab,
|
HoppTab,
|
||||||
PersistableTabState,
|
PersistableTabState,
|
||||||
TabService as TabServiceInterface,
|
TabService as TabServiceInterface,
|
||||||
} from "."
|
} from "."
|
||||||
|
|
||||||
import { NewWorkspaceService } from "../new-workspace"
|
|
||||||
import { Handle } from "../new-workspace/handle"
|
|
||||||
import { WorkspaceRequest } from "../new-workspace/workspace"
|
|
||||||
|
|
||||||
export abstract class TabService<Doc>
|
export abstract class TabService<Doc>
|
||||||
extends Service
|
extends Service
|
||||||
implements TabServiceInterface<Doc>
|
implements TabServiceInterface<Doc>
|
||||||
{
|
{
|
||||||
private workspaceService = this.bind(NewWorkspaceService)
|
|
||||||
|
|
||||||
protected tabMap = reactive(new Map<string, HoppTab<Doc>>())
|
protected tabMap = reactive(new Map<string, HoppTab<Doc>>())
|
||||||
protected tabOrdering = ref<string[]>(["test"])
|
protected tabOrdering = ref<string[]>(["test"])
|
||||||
|
|
||||||
@@ -44,9 +36,9 @@ export abstract class TabService<Doc>
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
public currentActiveTab = computed(() => {
|
public currentActiveTab = computed(
|
||||||
return this.tabMap.get(this.currentTabID.value)!
|
() => this.tabMap.get(this.currentTabID.value)!
|
||||||
}) // Guaranteed to not be undefined
|
) // Guaranteed to not be undefined
|
||||||
|
|
||||||
protected watchCurrentTabID() {
|
protected watchCurrentTabID() {
|
||||||
watch(
|
watch(
|
||||||
@@ -90,63 +82,15 @@ export abstract class TabService<Doc>
|
|||||||
this.currentTabID.value = tabID
|
this.currentTabID.value = tabID
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadTabsFromPersistedState(
|
public loadTabsFromPersistedState(data: PersistableTabState<Doc>): void {
|
||||||
data: PersistableTabState<Doc>
|
|
||||||
): Promise<void> {
|
|
||||||
if (data) {
|
if (data) {
|
||||||
this.tabMap.clear()
|
this.tabMap.clear()
|
||||||
this.tabOrdering.value = []
|
this.tabOrdering.value = []
|
||||||
|
|
||||||
for (const doc of data.orderedDocs) {
|
for (const doc of data.orderedDocs) {
|
||||||
let requestHandle: Handle<WorkspaceRequest> | null = null
|
|
||||||
let resolvedTabDoc = doc.doc
|
|
||||||
|
|
||||||
// TODO: Account for GQL
|
|
||||||
const { saveContext } = doc.doc as HoppRESTDocument
|
|
||||||
|
|
||||||
if (saveContext?.originLocation === "workspace-user-collection") {
|
|
||||||
const { providerID, requestID, workspaceID } = saveContext
|
|
||||||
|
|
||||||
if (!providerID || !workspaceID || !requestID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceHandleResult =
|
|
||||||
await this.workspaceService.getWorkspaceHandle(
|
|
||||||
providerID!,
|
|
||||||
workspaceID!
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isLeft(workspaceHandleResult)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceHandle = workspaceHandleResult.right
|
|
||||||
|
|
||||||
const requestHandleResult =
|
|
||||||
await this.workspaceService.getRequestHandle(
|
|
||||||
workspaceHandle,
|
|
||||||
requestID!
|
|
||||||
)
|
|
||||||
|
|
||||||
if (E.isRight(requestHandleResult)) {
|
|
||||||
requestHandle = requestHandleResult.right
|
|
||||||
|
|
||||||
const { originLocation } = saveContext
|
|
||||||
|
|
||||||
resolvedTabDoc = {
|
|
||||||
...resolvedTabDoc,
|
|
||||||
saveContext: {
|
|
||||||
originLocation,
|
|
||||||
requestHandle,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tabMap.set(doc.tabID, {
|
this.tabMap.set(doc.tabID, {
|
||||||
id: doc.tabID,
|
id: doc.tabID,
|
||||||
document: resolvedTabDoc,
|
document: doc.doc,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.tabOrdering.value.push(doc.tabID)
|
this.tabOrdering.value.push(doc.tabID)
|
||||||
@@ -155,11 +99,10 @@ export abstract class TabService<Doc>
|
|||||||
this.setActiveTab(data.lastActiveTabID)
|
this.setActiveTab(data.lastActiveTabID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getActiveTabs(): Readonly<ComputedRef<HoppTab<Doc>[]>> {
|
public getActiveTabs(): Readonly<ComputedRef<HoppTab<Doc>[]>> {
|
||||||
return shallowReadonly(
|
return shallowReadonly(
|
||||||
computed(() =>
|
computed(() => this.tabOrdering.value.map((x) => this.tabMap.get(x)!))
|
||||||
this.tabOrdering.value.map((x) => this.tabMap.get(x) as HoppTab<Doc>)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,55 +180,13 @@ export abstract class TabService<Doc>
|
|||||||
this.currentTabID.value = tabID
|
this.currentTabID.value = tabID
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPersistedDocument(tabDoc: Doc): Doc {
|
|
||||||
const { saveContext } = tabDoc as HoppRESTDocument
|
|
||||||
|
|
||||||
if (saveContext?.originLocation !== "workspace-user-collection") {
|
|
||||||
return tabDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Investigate why requestHandle is available unwrapped here
|
|
||||||
const requestHandle = saveContext.requestHandle
|
|
||||||
|
|
||||||
if (!requestHandle) {
|
|
||||||
return tabDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestHandleRef = requestHandle.get()
|
|
||||||
|
|
||||||
if (requestHandleRef.value.type === "invalid") {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { requestHandle, ...rest } = saveContext
|
|
||||||
|
|
||||||
// Return the document without the handle
|
|
||||||
return {
|
|
||||||
...tabDoc,
|
|
||||||
saveContext: rest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { providerID, workspaceID, requestID } = requestHandleRef.value.data
|
|
||||||
|
|
||||||
// Return the document without the handle
|
|
||||||
return {
|
|
||||||
...tabDoc,
|
|
||||||
saveContext: {
|
|
||||||
originLocation: "workspace-user-collection",
|
|
||||||
requestID,
|
|
||||||
providerID,
|
|
||||||
workspaceID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public persistableTabState = computed<PersistableTabState<Doc>>(() => ({
|
public persistableTabState = computed<PersistableTabState<Doc>>(() => ({
|
||||||
lastActiveTabID: this.currentTabID.value,
|
lastActiveTabID: this.currentTabID.value,
|
||||||
orderedDocs: this.tabOrdering.value.map((tabID) => {
|
orderedDocs: this.tabOrdering.value.map((tabID) => {
|
||||||
const tab = this.tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
|
const tab = this.tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tabID: tab.id,
|
tabID: tab.id,
|
||||||
doc: this.getPersistedDocument(tab.document),
|
doc: tab.document,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -14,3 +14,21 @@ Since TypeScript cannot handle type information for `.vue` imports, they are shi
|
|||||||
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
|
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
|
||||||
|
|
||||||
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
|
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Prequisites
|
||||||
|
- Install Rust: `curl https://sh.rustup.rs -sSf | sh`
|
||||||
|
- If you're on Mac, remove the installation from brew first if any (brew uninstall rust && brew cleanup)
|
||||||
|
- libsoup2.4-dev installed if Linux: `sudo apt install libsoup2.4-dev`
|
||||||
|
- Node v18.20 installed and currently active if you're using nvm
|
||||||
|
|
||||||
|
### Build Instruction
|
||||||
|
|
||||||
|
1. Install latest pnpm `curl -fsSL https://get.pnpm.io/install.sh | sh -` or upgrade `pnpm add -g pnpm`
|
||||||
|
2. Setup the .env of the root project folder, you should deploy the self hosted backend first
|
||||||
|
3. Run `pnpm install` on root project folder
|
||||||
|
4. Run `pnpm dev:gql-codegen` on this folder
|
||||||
|
5. Run `pnpm tauri dev` to run debug mode (optional)
|
||||||
|
6. Run `pnpm tauri build` to build release mode
|
||||||
|
- `pnpm tauri build --target universal-apple-darwin` for Mac
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"@fontsource-variable/material-symbols-rounded": "5.0.16",
|
"@fontsource-variable/material-symbols-rounded": "5.0.16",
|
||||||
"@fontsource-variable/roboto-mono": "5.0.16",
|
"@fontsource-variable/roboto-mono": "5.0.16",
|
||||||
"@hoppscotch/common": "workspace:^",
|
"@hoppscotch/common": "workspace:^",
|
||||||
|
"@hoppscotch/data": "workspace:^",
|
||||||
"@platform/auth": "0.1.106",
|
"@platform/auth": "0.1.106",
|
||||||
"@tauri-apps/api": "1.5.1",
|
"@tauri-apps/api": "1.5.1",
|
||||||
"@tauri-apps/cli": "1.5.6",
|
"@tauri-apps/cli": "1.5.6",
|
||||||
@@ -36,7 +37,13 @@
|
|||||||
"tauri-plugin-store-api": "0.0.0",
|
"tauri-plugin-store-api": "0.0.0",
|
||||||
"util": "0.12.5",
|
"util": "0.12.5",
|
||||||
"vue": "3.3.9",
|
"vue": "3.3.9",
|
||||||
"workbox-window": "6.6.0"
|
"workbox-window": "6.6.0",
|
||||||
|
"zod": "3.22.4",
|
||||||
|
"@urql/core": "^4.1.1",
|
||||||
|
"cookie": "^0.5.0",
|
||||||
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
|
"tauri-plugin-websocket-api": "github:tauri-apps/tauri-plugin-websocket#v1",
|
||||||
|
"wonka": "^6.3.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/add": "5.0.0",
|
"@graphql-codegen/add": "5.0.0",
|
||||||
@@ -76,6 +83,8 @@
|
|||||||
"vite-plugin-pwa": "0.13.1",
|
"vite-plugin-pwa": "0.13.1",
|
||||||
"vite-plugin-static-copy": "0.12.0",
|
"vite-plugin-static-copy": "0.12.0",
|
||||||
"vite-plugin-vue-layouts": "0.7.0",
|
"vite-plugin-vue-layouts": "0.7.0",
|
||||||
"vue-tsc": "1.8.8"
|
"vue-tsc": "1.8.8",
|
||||||
|
"@types/cookie": "^0.5.1"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,13 @@ tauri = { version = "1.5.3", features = [
|
|||||||
] }
|
] }
|
||||||
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tauri-plugin-deep-link = { git = "https://github.com/FabianLars/tauri-plugin-deep-link", branch = "main" }
|
tauri-plugin-deep-link = { git = "https://github.com/FabianLars/tauri-plugin-deep-link", branch = "main" }
|
||||||
|
tauri-plugin-websocket = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tauri-plugin-window-state = "0.1.0"
|
tauri-plugin-window-state = "0.1.0"
|
||||||
reqwest = "0.11.22"
|
reqwest = "0.11.22"
|
||||||
serde_json = "1.0.108"
|
serde_json = "1.0.108"
|
||||||
url = "2.5.0"
|
url = "2.5.0"
|
||||||
hex_color = "3.0.0"
|
hex_color = "3.0.0"
|
||||||
|
time = "0.3.36"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
cocoa = "0.25.0"
|
cocoa = "0.25.0"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ fn main() {
|
|||||||
tauri_plugin_deep_link::prepare("io.hoppscotch.desktop");
|
tauri_plugin_deep_link::prepare("io.hoppscotch.desktop");
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_websocket::init())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||||
.plugin(tauri_plugin_store::Builder::default().build())
|
.plugin(tauri_plugin_store::Builder::default().build())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
"targets": "all"
|
"targets": "all"
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "none"
|
"csp": null
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"active": false
|
"active": false
|
||||||
|
|||||||
168
packages/hoppscotch-selfhost-desktop/src/helpers/GQLClient.ts
Normal file
168
packages/hoppscotch-selfhost-desktop/src/helpers/GQLClient.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { ref } from "vue"
|
||||||
|
import { makeResult, makeErrorResult, Exchange, Operation } from "@urql/core"
|
||||||
|
import { makeFetchBody, makeFetchOptions } from "@urql/core/internal"
|
||||||
|
import { filter, make, merge, mergeMap, pipe, takeUntil, map } from "wonka"
|
||||||
|
import { gqlClientError$ } from "@hoppscotch/common/helpers/backend/GQLClient"
|
||||||
|
import { Store } from "tauri-plugin-store-api"
|
||||||
|
import { Body, getClient } from "@tauri-apps/api/http"
|
||||||
|
import { parse, serialize } from "cookie"
|
||||||
|
import { SubscriptionClient } from "subscriptions-transport-ws"
|
||||||
|
import { platform } from "@hoppscotch/common/platform"
|
||||||
|
import WSWrapper from "./ws_wrapper"
|
||||||
|
|
||||||
|
const APP_DATA_PATH = "~/.hopp-desktop-app-data.dat"
|
||||||
|
|
||||||
|
export async function addCookieToFetchHeaders(
|
||||||
|
store: Store,
|
||||||
|
headers: HeadersInit = {}
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const accessToken = await store.get<{ value: string }>("access_token")
|
||||||
|
const refreshToken = await store.get<{ value: string }>("refresh_token")
|
||||||
|
|
||||||
|
if (accessToken?.value && refreshToken?.value) {
|
||||||
|
// Assert headers as an indexable type
|
||||||
|
const headersIndexable = headers as { [key: string]: string }
|
||||||
|
const existingCookies = parse(headersIndexable["Cookie"] || "")
|
||||||
|
|
||||||
|
if (!existingCookies.access_token) {
|
||||||
|
existingCookies.access_token = accessToken.value
|
||||||
|
}
|
||||||
|
if (!existingCookies.refresh_token) {
|
||||||
|
existingCookies.refresh_token = refreshToken.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the cookies back into the headers
|
||||||
|
const serializedCookies = Object.entries(existingCookies)
|
||||||
|
.map(([name, value]) => serialize(name, value))
|
||||||
|
.join("; ")
|
||||||
|
headersIndexable["Cookie"] = serializedCookies
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
|
} catch (error) {
|
||||||
|
console.error("error while injecting cookie")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHttpSource(operation: Operation, store: Store) {
|
||||||
|
return make(({ next, complete }) => {
|
||||||
|
getClient().then(async (httpClient) => {
|
||||||
|
const fetchOptions = makeFetchOptions(operation)
|
||||||
|
let headers = fetchOptions.headers
|
||||||
|
headers = await addCookieToFetchHeaders(store, headers)
|
||||||
|
|
||||||
|
const fetchBody = makeFetchBody(operation)
|
||||||
|
httpClient
|
||||||
|
.post(operation.context.url, Body.json(fetchBody), {
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
next(result.data)
|
||||||
|
complete()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
next(makeErrorResult(operation, error))
|
||||||
|
complete()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return () => {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tauriGQLFetchExchange =
|
||||||
|
(store: Store): Exchange =>
|
||||||
|
({ forward }) =>
|
||||||
|
(ops$) => {
|
||||||
|
const subscriptionResults$ = pipe(
|
||||||
|
ops$,
|
||||||
|
filter((op) => op.kind === "query" || op.kind === "mutation"),
|
||||||
|
mergeMap((operation) => {
|
||||||
|
const { key, context } = operation
|
||||||
|
|
||||||
|
const teardown$ = pipe(
|
||||||
|
ops$,
|
||||||
|
filter((op: Operation) => op.kind === "teardown" && op.key === key)
|
||||||
|
)
|
||||||
|
|
||||||
|
const source = createHttpSource(operation, store)
|
||||||
|
|
||||||
|
return pipe(
|
||||||
|
source,
|
||||||
|
takeUntil(teardown$),
|
||||||
|
map((result) => makeResult(operation, result as any))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const forward$ = pipe(
|
||||||
|
ops$,
|
||||||
|
filter(
|
||||||
|
(op: Operation) => op.kind === "teardown" || op.kind != "subscription"
|
||||||
|
),
|
||||||
|
forward
|
||||||
|
)
|
||||||
|
|
||||||
|
return merge([subscriptionResults$, forward$])
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSubscriptionClient = () => {
|
||||||
|
return new SubscriptionClient(
|
||||||
|
import.meta.env.VITE_BACKEND_WS_URL,
|
||||||
|
{
|
||||||
|
reconnect: true,
|
||||||
|
connectionParams: () => platform.auth.getBackendHeaders(),
|
||||||
|
connectionCallback(error) {
|
||||||
|
if (error?.length > 0) {
|
||||||
|
gqlClientError$.next({
|
||||||
|
type: "SUBSCRIPTION_CONN_CALLBACK_ERR_REPORT",
|
||||||
|
errors: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wsOptionArguments: [
|
||||||
|
{
|
||||||
|
store: new Store(APP_DATA_PATH),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
WSWrapper
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let subscriptionClient: SubscriptionClient | null
|
||||||
|
|
||||||
|
const isBackendGQLEventAdded = ref(false)
|
||||||
|
|
||||||
|
const resetSubscriptionClient = () => {
|
||||||
|
if (subscriptionClient) {
|
||||||
|
subscriptionClient.close()
|
||||||
|
}
|
||||||
|
subscriptionClient = createSubscriptionClient()
|
||||||
|
if (!isBackendGQLEventAdded.value) {
|
||||||
|
subscriptionClient.onConnected(() => {
|
||||||
|
platform.auth.onBackendGQLClientShouldReconnect(() => {
|
||||||
|
const currentUser = platform.auth.getCurrentUser()
|
||||||
|
|
||||||
|
if (currentUser && subscriptionClient) {
|
||||||
|
subscriptionClient?.client?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUser && !subscriptionClient) {
|
||||||
|
resetSubscriptionClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentUser && subscriptionClient) {
|
||||||
|
subscriptionClient.close()
|
||||||
|
resetSubscriptionClient()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
isBackendGQLEventAdded.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSubscriptionClient = () => {
|
||||||
|
if (!subscriptionClient) resetSubscriptionClient()
|
||||||
|
return subscriptionClient
|
||||||
|
}
|
||||||
154
packages/hoppscotch-selfhost-desktop/src/helpers/ws_wrapper.ts
Normal file
154
packages/hoppscotch-selfhost-desktop/src/helpers/ws_wrapper.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import { Store } from "tauri-plugin-store-api"
|
||||||
|
import TauriWebSocket, {
|
||||||
|
Message,
|
||||||
|
ConnectionConfig,
|
||||||
|
} from "tauri-plugin-websocket-api"
|
||||||
|
import { addCookieToFetchHeaders } from "./GQLClient"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a wrapper around tauri-plugin-websocket-api with cookie injection support. This is required because
|
||||||
|
* subscriptions-transport-ws client expects a custom websocket implementation in the shape of native browser WebSocket.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class WebSocketWrapper extends EventTarget implements WebSocket {
|
||||||
|
public client: TauriWebSocket | undefined
|
||||||
|
private tauriWebSocketConfig:
|
||||||
|
| (ConnectionConfig & { store: Store })
|
||||||
|
| undefined
|
||||||
|
private isConnected: boolean = false
|
||||||
|
binaryType: BinaryType = "blob"
|
||||||
|
extensions = ""
|
||||||
|
onclose: ((this: WebSocket, ev: CloseEvent) => any) | null = null
|
||||||
|
onerror: ((this: WebSocket, ev: Event) => any) | null = null
|
||||||
|
onmessage: ((this: WebSocket, ev: MessageEvent) => any) | null = null
|
||||||
|
onopen: ((this: WebSocket, ev: Event) => any) | null = null
|
||||||
|
protocol = ""
|
||||||
|
url: string
|
||||||
|
|
||||||
|
public static readonly CONNECTING = 0
|
||||||
|
public static readonly OPEN = 1
|
||||||
|
public static readonly CLOSING = 2
|
||||||
|
public static readonly CLOSED = 3
|
||||||
|
|
||||||
|
readonly CONNECTING = 0
|
||||||
|
readonly OPEN = 1
|
||||||
|
readonly CLOSING = 2
|
||||||
|
readonly CLOSED = 3
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
url: string,
|
||||||
|
protocols?: string | string[],
|
||||||
|
config?: ConnectionConfig & { store: Store }
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.url = url
|
||||||
|
this.tauriWebSocketConfig = config
|
||||||
|
this.setup()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setup() {
|
||||||
|
if (this.tauriWebSocketConfig?.store) {
|
||||||
|
const headersStringified =
|
||||||
|
this.tauriWebSocketConfig.headers || ("{}" as any)
|
||||||
|
let headers = JSON.parse(headersStringified)
|
||||||
|
headers = await addCookieToFetchHeaders(
|
||||||
|
this.tauriWebSocketConfig.store,
|
||||||
|
headers
|
||||||
|
)
|
||||||
|
this.tauriWebSocketConfig = {
|
||||||
|
...this.tauriWebSocketConfig,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.client = await TauriWebSocket.connect(this.url, {
|
||||||
|
headers: {
|
||||||
|
"sec-websocket-protocol": "graphql-ws",
|
||||||
|
...this.tauriWebSocketConfig?.headers,
|
||||||
|
},
|
||||||
|
}).catch((error) => {
|
||||||
|
this.isConnected = false
|
||||||
|
if (this.onerror) {
|
||||||
|
this.onerror(new Event("error"))
|
||||||
|
}
|
||||||
|
throw new Error(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.isConnected = true
|
||||||
|
|
||||||
|
this.client.addListener(this.handleMessage.bind(this))
|
||||||
|
if (this.onopen) {
|
||||||
|
this.onopen(new Event("open"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get readyState(): number {
|
||||||
|
return this.client
|
||||||
|
? this.isConnected
|
||||||
|
? this.OPEN
|
||||||
|
: this.CLOSED
|
||||||
|
: this.CONNECTING
|
||||||
|
}
|
||||||
|
|
||||||
|
get bufferedAmount(): number {
|
||||||
|
// TODO implement
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
close(code?: number, reason?: string): void {
|
||||||
|
this.client?.disconnect().then(() => {
|
||||||
|
if (this.onclose) {
|
||||||
|
this.onclose(new CloseEvent("close"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
|
||||||
|
if (
|
||||||
|
typeof data === "string" ||
|
||||||
|
data instanceof ArrayBuffer ||
|
||||||
|
data instanceof Blob
|
||||||
|
) {
|
||||||
|
this.client?.send(data as string).catch((error) => {
|
||||||
|
console.error("error while sending data", data)
|
||||||
|
if (this.onerror) {
|
||||||
|
this.onerror(new Event("error"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// TODO implement, drop the record for now
|
||||||
|
console.warn(
|
||||||
|
"WebSocketWrapper.send() not implemented for non-string data"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMessage(message: Message): void {
|
||||||
|
switch (message.type) {
|
||||||
|
case "Close": {
|
||||||
|
if (this.onclose) {
|
||||||
|
this.onclose(new CloseEvent("close"))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Ping": {
|
||||||
|
this.client?.send("Pong").catch((error) => {
|
||||||
|
console.error("error while sending Pong data", message)
|
||||||
|
if (this.onerror) {
|
||||||
|
this.onerror(new Event("error"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if (this.onmessage) {
|
||||||
|
this.onmessage(
|
||||||
|
new MessageEvent("message", {
|
||||||
|
data: message.data,
|
||||||
|
origin: this.url,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getService } from "@hoppscotch/common/modules/dioc"
|
import { getService } from "@hoppscotch/common/modules/dioc"
|
||||||
|
import axios from "axios"
|
||||||
import {
|
import {
|
||||||
AuthEvent,
|
AuthEvent,
|
||||||
AuthPlatformDef,
|
AuthPlatformDef,
|
||||||
@@ -13,15 +14,50 @@ import { open } from '@tauri-apps/api/shell'
|
|||||||
import { BehaviorSubject, Subject } from "rxjs"
|
import { BehaviorSubject, Subject } from "rxjs"
|
||||||
import { Store } from "tauri-plugin-store-api"
|
import { Store } from "tauri-plugin-store-api"
|
||||||
import { Ref, ref, watch } from "vue"
|
import { Ref, ref, watch } from "vue"
|
||||||
|
import { z } from "zod"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { subscriptionExchange } from "@urql/core"
|
||||||
|
import {
|
||||||
|
getSubscriptionClient,
|
||||||
|
tauriGQLFetchExchange,
|
||||||
|
} from "../helpers/GQLClient"
|
||||||
|
|
||||||
export const authEvents$ = new Subject<AuthEvent | { event: "token_refresh" }>()
|
export const authEvents$ = new Subject<AuthEvent | { event: "token_refresh" }>()
|
||||||
const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
|
const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
|
||||||
export const probableUser$ = new BehaviorSubject<HoppUser | null>(null)
|
export const probableUser$ = new BehaviorSubject<HoppUser | null>(null)
|
||||||
|
|
||||||
const APP_DATA_PATH = "~/.hopp-desktop-app-data.dat"
|
export const APP_DATA_PATH = "~/.hopp-desktop-app-data.dat"
|
||||||
|
|
||||||
const persistenceService = getService(PersistenceService)
|
const persistenceService = getService(PersistenceService)
|
||||||
|
|
||||||
|
const expectedAllowedProvidersSchema = z.object({
|
||||||
|
// currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML"
|
||||||
|
// keeping it as string to avoid backend accidentally breaking frontend when adding new providers
|
||||||
|
providers: z.array(z.string()),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const getAllowedAuthProviders = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(
|
||||||
|
`${import.meta.env.VITE_BACKEND_API_URL}/auth/providers`,
|
||||||
|
{
|
||||||
|
withCredentials: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const parseResult = expectedAllowedProvidersSchema.safeParse(res.data)
|
||||||
|
|
||||||
|
if (!parseResult.success) {
|
||||||
|
return E.left("SOMETHING_WENT_WRONG")
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(parseResult.data.providers)
|
||||||
|
} catch (_) {
|
||||||
|
return E.left("SOMETHING_WENT_WRONG")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
let client = await getClient();
|
let client = await getClient();
|
||||||
await client.get(`${import.meta.env.VITE_BACKEND_API_URL}/auth/logout`)
|
await client.get(`${import.meta.env.VITE_BACKEND_API_URL}/auth/logout`)
|
||||||
@@ -37,7 +73,7 @@ async function signInUserWithGithubFB() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function signInUserWithGoogleFB() {
|
async function signInUserWithGoogleFB() {
|
||||||
await open(`${import.meta.env.VITE_BACKEND_API_URL}/auth/google?redirect_uri=desktop`);
|
await open(`${import.meta.env.VITE_BACKEND_LOGIN_API_URL}/authenticate`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function signInUserWithMicrosoftFB() {
|
async function signInUserWithMicrosoftFB() {
|
||||||
@@ -224,6 +260,15 @@ export const def: AuthPlatformDef = {
|
|||||||
},
|
},
|
||||||
getGQLClientOptions() {
|
getGQLClientOptions() {
|
||||||
return {
|
return {
|
||||||
|
exchanges: [
|
||||||
|
subscriptionExchange({
|
||||||
|
forwardSubscription(fetchBody) {
|
||||||
|
const subscriptionClient = getSubscriptionClient()
|
||||||
|
return subscriptionClient!.request(fetchBody)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
tauriGQLFetchExchange(new Store(APP_DATA_PATH)),
|
||||||
|
],
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
},
|
},
|
||||||
@@ -371,4 +416,5 @@ export const def: AuthPlatformDef = {
|
|||||||
event: "logout",
|
event: "logout",
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
getAllowedAuthProviders,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
"rxjs": "7.8.1",
|
"rxjs": "7.8.1",
|
||||||
"stream-browserify": "3.0.0",
|
"stream-browserify": "3.0.0",
|
||||||
"util": "0.12.5",
|
"util": "0.12.5",
|
||||||
"vue": "3.4.27",
|
"vue": "3.3.9",
|
||||||
"workbox-window": "7.0.0",
|
"workbox-window": "7.0.0",
|
||||||
"zod": "3.22.4"
|
"zod": "3.22.4"
|
||||||
},
|
},
|
||||||
|
|||||||
861
pnpm-lock.yaml
generated
861
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user