chore: merge hoppscotch/hoppscotch/staging into main
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0020 4.77 5.07 5.07 0 0019.91 1S18.73.65 16 2.48a13.38 13.38 0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 005 4.77a5.44 5.44 0 00-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 009 18.13V22" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 504 B |
@@ -323,9 +323,10 @@ pre.ace_editor {
|
|||||||
@apply after:justify-center;
|
@apply after:justify-center;
|
||||||
@apply after:pointer-events-none;
|
@apply after:pointer-events-none;
|
||||||
@apply after:font-icon;
|
@apply after:font-icon;
|
||||||
@apply after:text-secondaryLight;
|
@apply after:text-current;
|
||||||
@apply after:right-3;
|
@apply after:right-3;
|
||||||
@apply after:content-["\e313"];
|
@apply after:content-["\e313"];
|
||||||
|
@apply after:text-lg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-response {
|
.info-response {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"go_back": "Go back",
|
"go_back": "Go back",
|
||||||
|
"go_forward": "Go forward",
|
||||||
"group_by": "Group by",
|
"group_by": "Group by",
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"learn_more": "Learn more",
|
"learn_more": "Learn more",
|
||||||
@@ -117,12 +118,16 @@
|
|||||||
},
|
},
|
||||||
"collection": {
|
"collection": {
|
||||||
"created": "Collection created",
|
"created": "Collection created",
|
||||||
|
"different_parent": "Cannot reorder collection with different parent",
|
||||||
"edit": "Edit Collection",
|
"edit": "Edit Collection",
|
||||||
"invalid_name": "Please provide a name for the collection",
|
"invalid_name": "Please provide a name for the collection",
|
||||||
|
"invalid_root_move": "Collection already in the root",
|
||||||
|
"moved": "Moved Successfully",
|
||||||
"my_collections": "My Collections",
|
"my_collections": "My Collections",
|
||||||
"name": "My New Collection",
|
"name": "My New Collection",
|
||||||
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
"name_length_insufficient": "Collection name should be at least 3 characters long",
|
||||||
"new": "New Collection",
|
"new": "New Collection",
|
||||||
|
"order_changed": "Collection Order Updated",
|
||||||
"renamed": "Collection renamed",
|
"renamed": "Collection renamed",
|
||||||
"request_in_use": "Request in use",
|
"request_in_use": "Request in use",
|
||||||
"save_as": "Save as",
|
"save_as": "Save as",
|
||||||
@@ -389,16 +394,19 @@
|
|||||||
"text": "Text"
|
"text": "Text"
|
||||||
},
|
},
|
||||||
"copy_link": "Copy link",
|
"copy_link": "Copy link",
|
||||||
|
"different_collection": "Cannot reorder requests from different collections",
|
||||||
|
"duplicated": "Request duplicated",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"enter_curl": "Enter cURL command",
|
"enter_curl": "Enter cURL command",
|
||||||
"duplicated": "Request duplicated",
|
|
||||||
"generate_code": "Generate code",
|
"generate_code": "Generate code",
|
||||||
"generated_code": "Generated code",
|
"generated_code": "Generated code",
|
||||||
"header_list": "Header List",
|
"header_list": "Header List",
|
||||||
"invalid_name": "Please provide a name for the request",
|
"invalid_name": "Please provide a name for the request",
|
||||||
"method": "Method",
|
"method": "Method",
|
||||||
|
"moved": "Request moved",
|
||||||
"name": "Request name",
|
"name": "Request name",
|
||||||
"new": "New Request",
|
"new": "New Request",
|
||||||
|
"order_changed": "Request Order Updated",
|
||||||
"override": "Override",
|
"override": "Override",
|
||||||
"override_help": "Set <kbd>Content-Type</kbd> in Headers",
|
"override_help": "Set <kbd>Content-Type</kbd> in Headers",
|
||||||
"overriden": "Overridden",
|
"overriden": "Overridden",
|
||||||
@@ -629,6 +637,7 @@
|
|||||||
"body": "Body",
|
"body": "Body",
|
||||||
"collections": "Collections",
|
"collections": "Collections",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
|
"environments": "Environments",
|
||||||
"headers": "Headers",
|
"headers": "Headers",
|
||||||
"history": "History",
|
"history": "History",
|
||||||
"mqtt": "MQTT",
|
"mqtt": "MQTT",
|
||||||
@@ -655,6 +664,7 @@
|
|||||||
"exit_disabled": "Only owner cannot exit the team",
|
"exit_disabled": "Only owner cannot exit the team",
|
||||||
"invalid_email_format": "Email format is invalid",
|
"invalid_email_format": "Email format is invalid",
|
||||||
"invalid_id": "Invalid team ID. Contact your team owner.",
|
"invalid_id": "Invalid team ID. Contact your team owner.",
|
||||||
|
"invalid_coll_id": "Invalid collection ID",
|
||||||
"invalid_invite_link": "Invalid invite link",
|
"invalid_invite_link": "Invalid invite link",
|
||||||
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
"invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
|
||||||
"invalid_member_permission": "Please provide a valid permission to the team member",
|
"invalid_member_permission": "Please provide a valid permission to the team member",
|
||||||
@@ -676,6 +686,7 @@
|
|||||||
"member_removed": "User removed",
|
"member_removed": "User removed",
|
||||||
"member_role_updated": "User roles updated",
|
"member_role_updated": "User roles updated",
|
||||||
"members": "Members",
|
"members": "Members",
|
||||||
|
"more_members": "+{count} more",
|
||||||
"name_length_insufficient": "Team name should be at least 6 characters long",
|
"name_length_insufficient": "Team name should be at least 6 characters long",
|
||||||
"name_updated": "Team name updated",
|
"name_updated": "Team name updated",
|
||||||
"new": "New Team",
|
"new": "New Team",
|
||||||
@@ -683,10 +694,13 @@
|
|||||||
"new_name": "My New Team",
|
"new_name": "My New Team",
|
||||||
"no_access": "You do not have edit access to these collections",
|
"no_access": "You do not have edit access to these collections",
|
||||||
"no_invite_found": "Invitation not found. Contact your team owner.",
|
"no_invite_found": "Invitation not found. Contact your team owner.",
|
||||||
|
"no_request_found": "Request not found.",
|
||||||
"not_found": "Team not found. Contact your team owner.",
|
"not_found": "Team not found. Contact your team owner.",
|
||||||
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
|
||||||
|
"parent_coll_move": "Cannot move collection to a child collection",
|
||||||
"pending_invites": "Pending invites",
|
"pending_invites": "Pending invites",
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
|
"same_target_destination": "Same target and destination",
|
||||||
"saved": "Team saved",
|
"saved": "Team saved",
|
||||||
"select_a_team": "Select a team",
|
"select_a_team": "Select a team",
|
||||||
"title": "Teams",
|
"title": "Teams",
|
||||||
@@ -714,5 +728,11 @@
|
|||||||
"message": "Message",
|
"message": "Message",
|
||||||
"protocols": "Protocols",
|
"protocols": "Protocols",
|
||||||
"url": "URL"
|
"url": "URL"
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"change": "Change workspace",
|
||||||
|
"personal": "My Workspace",
|
||||||
|
"team": "Team Workspace",
|
||||||
|
"title": "Workspaces"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
packages/hoppscotch-common/src/components.d.ts
vendored
58
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -16,6 +16,7 @@ declare module '@vue/runtime-core' {
|
|||||||
AppHeader: typeof import('./components/app/Header.vue')['default']
|
AppHeader: typeof import('./components/app/Header.vue')['default']
|
||||||
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
|
AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
|
||||||
AppLogo: typeof import('./components/app/Logo.vue')['default']
|
AppLogo: typeof import('./components/app/Logo.vue')['default']
|
||||||
|
AppNavigation: typeof import('./components/app/Navigation.vue')['default']
|
||||||
AppOptions: typeof import('./components/app/Options.vue')['default']
|
AppOptions: typeof import('./components/app/Options.vue')['default']
|
||||||
AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
|
AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
|
||||||
AppPowerSearch: typeof import('./components/app/PowerSearch.vue')['default']
|
AppPowerSearch: typeof import('./components/app/PowerSearch.vue')['default']
|
||||||
@@ -26,8 +27,6 @@ declare module '@vue/runtime-core' {
|
|||||||
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
||||||
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
||||||
AppSupport: typeof import('./components/app/Support.vue')['default']
|
AppSupport: typeof import('./components/app/Support.vue')['default']
|
||||||
ButtonPrimary: typeof import('./../../hoppscotch-ui/src/components/button/Primary.vue')['default']
|
|
||||||
ButtonSecondary: typeof import('./../../hoppscotch-ui/src/components/button/Secondary.vue')['default']
|
|
||||||
Collections: typeof import('./components/collections/index.vue')['default']
|
Collections: typeof import('./components/collections/index.vue')['default']
|
||||||
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
|
CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
|
||||||
CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default']
|
CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default']
|
||||||
@@ -52,9 +51,7 @@ declare module '@vue/runtime-core' {
|
|||||||
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
||||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
||||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||||
CollectionsTeamSelect: typeof import('./components/collections/TeamSelect.vue')['default']
|
|
||||||
Environments: typeof import('./components/environments/index.vue')['default']
|
Environments: typeof import('./components/environments/index.vue')['default']
|
||||||
EnvironmentsChooseType: typeof import('./components/environments/ChooseType.vue')['default']
|
|
||||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||||
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
|
||||||
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
|
||||||
@@ -75,6 +72,24 @@ declare module '@vue/runtime-core' {
|
|||||||
History: typeof import('./components/history/index.vue')['default']
|
History: typeof import('./components/history/index.vue')['default']
|
||||||
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
||||||
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
||||||
|
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||||
|
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||||
|
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||||
|
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
|
||||||
|
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
||||||
|
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
||||||
|
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
||||||
|
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
||||||
|
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
||||||
|
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||||
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
|
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||||
|
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||||
|
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||||
|
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||||
|
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
||||||
|
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||||
|
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
||||||
HttpBody: typeof import('./components/http/Body.vue')['default']
|
HttpBody: typeof import('./components/http/Body.vue')['default']
|
||||||
HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default']
|
HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default']
|
||||||
@@ -97,6 +112,18 @@ declare module '@vue/runtime-core' {
|
|||||||
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
||||||
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
||||||
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
||||||
|
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||||
|
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||||
|
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||||
|
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||||
|
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
||||||
|
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||||
|
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
||||||
|
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||||
|
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||||
|
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||||
|
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
||||||
|
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||||
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
|
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
|
||||||
@@ -116,41 +143,24 @@ declare module '@vue/runtime-core' {
|
|||||||
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
|
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
|
||||||
RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default']
|
RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default']
|
||||||
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
|
||||||
SmartAnchor: typeof import('./../../hoppscotch-ui/src/components/smart/Anchor.vue')['default']
|
|
||||||
SmartAutoComplete: typeof import('./../../hoppscotch-ui/src/components/smart/AutoComplete.vue')['default']
|
|
||||||
SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default']
|
SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default']
|
||||||
SmartCheckbox: typeof import('./../../hoppscotch-ui/src/components/smart/Checkbox.vue')['default']
|
|
||||||
SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default']
|
SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default']
|
||||||
SmartConfirmModal: typeof import('./../../hoppscotch-ui/src/components/smart/ConfirmModal.vue')['default']
|
|
||||||
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
|
||||||
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default']
|
|
||||||
SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default']
|
|
||||||
SmartFontSizePicker: typeof import('./components/smart/FontSizePicker.vue')['default']
|
SmartFontSizePicker: typeof import('./components/smart/FontSizePicker.vue')['default']
|
||||||
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
|
|
||||||
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
|
|
||||||
SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default']
|
|
||||||
SmartModal: typeof import('./../../hoppscotch-ui/src/components/smart/Modal.vue')['default']
|
|
||||||
SmartProgressRing: typeof import('./../../hoppscotch-ui/src/components/smart/ProgressRing.vue')['default']
|
|
||||||
SmartRadio: typeof import('./../../hoppscotch-ui/src/components/smart/Radio.vue')['default']
|
|
||||||
SmartRadioGroup: typeof import('./../../hoppscotch-ui/src/components/smart/RadioGroup.vue')['default']
|
|
||||||
SmartSlideOver: typeof import('./../../hoppscotch-ui/src/components/smart/SlideOver.vue')['default']
|
|
||||||
SmartSpinner: typeof import('./../../hoppscotch-ui/src/components/smart/Spinner.vue')['default']
|
|
||||||
SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default']
|
|
||||||
SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default']
|
|
||||||
SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default']
|
|
||||||
SmartTree: typeof import('./components/smart/Tree.vue')['default']
|
SmartTree: typeof import('./components/smart/Tree.vue')['default']
|
||||||
SmartTreeBranch: typeof import('./components/smart/TreeBranch.vue')['default']
|
SmartTreeBranch: typeof import('./components/smart/TreeBranch.vue')['default']
|
||||||
SmartWindow: typeof import('./../../hoppscotch-ui/src/components/smart/Window.vue')['default']
|
|
||||||
SmartWindows: typeof import('./../../hoppscotch-ui/src/components/smart/Windows.vue')['default']
|
|
||||||
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
TabPrimary: typeof import('./components/tab/Primary.vue')['default']
|
||||||
TabSecondary: typeof import('./components/tab/Secondary.vue')['default']
|
TabSecondary: typeof import('./components/tab/Secondary.vue')['default']
|
||||||
Teams: typeof import('./components/teams/index.vue')['default']
|
Teams: typeof import('./components/teams/index.vue')['default']
|
||||||
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
||||||
TeamsEdit: typeof import('./components/teams/Edit.vue')['default']
|
TeamsEdit: typeof import('./components/teams/Edit.vue')['default']
|
||||||
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
|
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
|
||||||
|
TeamsMemberStack: typeof import('./components/teams/MemberStack.vue')['default']
|
||||||
TeamsModal: typeof import('./components/teams/Modal.vue')['default']
|
TeamsModal: typeof import('./components/teams/Modal.vue')['default']
|
||||||
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']
|
||||||
|
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
:on-shown="() => tippyActions!.focus()"
|
:on-shown="() => tippyActions!.focus()"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:icon="IconHelpCircle"
|
:icon="IconLifeBuoy"
|
||||||
class="!rounded-none"
|
class="!rounded-none"
|
||||||
:label="`${t('app.help')}`"
|
:label="`${t('app.help')}`"
|
||||||
/>
|
/>
|
||||||
@@ -206,7 +206,6 @@ import IconShare2 from "~icons/lucide/share-2"
|
|||||||
import IconColumns from "~icons/lucide/columns"
|
import IconColumns from "~icons/lucide/columns"
|
||||||
import IconSidebarOpen from "~icons/lucide/sidebar-open"
|
import IconSidebarOpen from "~icons/lucide/sidebar-open"
|
||||||
import IconShieldCheck from "~icons/lucide/shield-check"
|
import IconShieldCheck from "~icons/lucide/shield-check"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
|
||||||
import IconBook from "~icons/lucide/book"
|
import IconBook from "~icons/lucide/book"
|
||||||
import IconMessageCircle from "~icons/lucide/message-circle"
|
import IconMessageCircle from "~icons/lucide/message-circle"
|
||||||
import IconGift from "~icons/lucide/gift"
|
import IconGift from "~icons/lucide/gift"
|
||||||
@@ -215,6 +214,7 @@ import IconGithub from "~icons/lucide/github"
|
|||||||
import IconTwitter from "~icons/lucide/twitter"
|
import IconTwitter from "~icons/lucide/twitter"
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
import IconUserPlus from "~icons/lucide/user-plus"
|
||||||
import IconLock from "~icons/lucide/lock"
|
import IconLock from "~icons/lucide/lock"
|
||||||
|
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||||
import { showChat } from "@modules/crisp"
|
import { showChat } from "@modules/crisp"
|
||||||
import { useSetting } from "@composables/settings"
|
import { useSetting } from "@composables/settings"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
class="flex items-center justify-between flex-1 flex-shrink-0 px-2 py-2 space-x-2 overflow-x-auto overflow-y-hidden"
|
class="flex items-center justify-between flex-1 flex-shrink-0 px-2 py-2 space-x-2 overflow-x-auto overflow-y-hidden"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="inline-flex items-center space-x-2"
|
class="inline-flex items-center justify-start flex-1 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,
|
||||||
@@ -15,9 +15,30 @@
|
|||||||
:label="t('app.name')"
|
:label="t('app.name')"
|
||||||
to="/"
|
to="/"
|
||||||
/>
|
/>
|
||||||
<AppGitHubStarButton class="mt-1.5 transition <sm:hidden" />
|
<!-- <AppGitHubStarButton class="mt-1.5 transition" /> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-flex items-center space-x-2">
|
<div class="inline-flex items-center justify-center flex-1 space-x-2">
|
||||||
|
<AppNavigation v-if="mdAndLarger" />
|
||||||
|
<div
|
||||||
|
class="bg-primaryDark max-w-128 text-secondaryLight justify-between cursor-pointer rounded border border-dividerDark hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary focus:outline-none transition flex flex-1 items-center px-2 py-1.25"
|
||||||
|
tabindex="0"
|
||||||
|
@click="invokeAction('modals.search.toggle')"
|
||||||
|
>
|
||||||
|
<span class="inline-flex">
|
||||||
|
<icon-lucide-search class="mr-2 svg-icons" />
|
||||||
|
{{ t("app.search") }}
|
||||||
|
</span>
|
||||||
|
<kbd class="shortcut-key">/</kbd>
|
||||||
|
</div>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
|
:title="`${
|
||||||
|
mdAndLarger ? t('support.title') : t('app.options')
|
||||||
|
} <kbd>?</kbd>`"
|
||||||
|
:icon="IconHelpCircle"
|
||||||
|
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
|
@click="invokeAction('modals.support.toggle')"
|
||||||
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-if="showInstallButton"
|
v-if="showInstallButton"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -26,44 +47,83 @@
|
|||||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
@click="installPWA()"
|
@click="installPWA()"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
</div>
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
<div class="inline-flex items-center justify-end flex-1 space-x-2">
|
||||||
:title="`${t('app.search')} <kbd>/</kbd>`"
|
<div
|
||||||
:icon="IconSearch"
|
|
||||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
|
||||||
@click="invokeAction('modals.search.toggle')"
|
|
||||||
/>
|
|
||||||
<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')"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-if="currentUser === null"
|
v-if="currentUser === null"
|
||||||
|
class="inline-flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
:icon="IconUploadCloud"
|
:icon="IconUploadCloud"
|
||||||
:label="t('header.save_workspace')"
|
:label="t('header.save_workspace')"
|
||||||
filled
|
class="hidden md:flex bg-green-500/15 py-1.75 border border-green-600/25 !text-green-500 hover:bg-green-400/10 focus-visible:bg-green-400/10 focus-visible:border-green-800/50 !focus-visible:text-green-600 hover:border-green-800/50 !hover:text-green-600"
|
||||||
class="hidden md:flex"
|
|
||||||
@click="invokeAction('modals.login.toggle')"
|
@click="invokeAction('modals.login.toggle')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
v-if="currentUser === null"
|
|
||||||
:label="t('header.login')"
|
:label="t('header.login')"
|
||||||
@click="invokeAction('modals.login.toggle')"
|
@click="invokeAction('modals.login.toggle')"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div v-else class="inline-flex items-center space-x-2">
|
<div v-else class="inline-flex items-center space-x-2">
|
||||||
<HoppButtonPrimary
|
<TeamsMemberStack
|
||||||
|
v-if="
|
||||||
|
workspace.type === 'team' &&
|
||||||
|
selectedTeam &&
|
||||||
|
selectedTeam.teamMembers.length > 1
|
||||||
|
"
|
||||||
|
:team-members="selectedTeam.teamMembers"
|
||||||
|
show-count
|
||||||
|
class="mx-2"
|
||||||
|
@handle-click="handleTeamEdit()"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="flex border divide-x rounded bg-green-500/15 divide-green-600/25 border-green-600/25 focus-within:bg-green-400/10 focus-within:border-green-800/50 focus-within:divide-green-800/50 hover:bg-green-400/10 hover:border-green-800/50 hover:divide-green-800/50"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('team.invite_tooltip')"
|
:title="t('team.invite_tooltip')"
|
||||||
:label="t('team.invite')"
|
|
||||||
:icon="IconUserPlus"
|
:icon="IconUserPlus"
|
||||||
class="!bg-green-500 !bg-opacity-15 !text-green-500 !hover:bg-opacity-10 !hover:bg-green-400 !hover:text-green-600"
|
class="py-1.75 !text-green-500 !focus-visible:text-green-600 !hover:text-green-600"
|
||||||
@click="showTeamsModal = true"
|
@click="handleInvite()"
|
||||||
/>
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="
|
||||||
|
workspace.type === 'team' &&
|
||||||
|
selectedTeam &&
|
||||||
|
selectedTeam?.myRole === 'OWNER'
|
||||||
|
"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('team.edit')"
|
||||||
|
:icon="IconSettings"
|
||||||
|
class="py-1.75 !text-green-500 !focus-visible:text-green-600 !hover:text-green-600"
|
||||||
|
@click="handleTeamEdit()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<tippy
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="() => accountActions.focus()"
|
||||||
|
>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="t('workspace.change')"
|
||||||
|
:label="mdAndLarger ? workspaceName : ``"
|
||||||
|
:icon="workspace.type === 'personal' ? IconUser : IconUsers"
|
||||||
|
class="pr-8 select-wrapper rounded bg-blue-500/15 py-1.75 border border-blue-600/25 !text-blue-500 focus-visible:bg-blue-400/10 focus-visible:border-blue-800/50 !focus-visible:text-blue-600 hover:bg-blue-400/10 hover:border-blue-800/50 !hover:text-blue-600"
|
||||||
|
/>
|
||||||
|
<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">
|
<span class="px-2">
|
||||||
<tippy
|
<tippy
|
||||||
interactive
|
interactive
|
||||||
@@ -157,24 +217,42 @@
|
|||||||
</header>
|
</header>
|
||||||
<AppAnnouncement v-if="!network.isOnline" />
|
<AppAnnouncement v-if="!network.isOnline" />
|
||||||
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
|
||||||
|
<TeamsInvite
|
||||||
|
v-if="workspace.type === 'team' && workspace.teamID"
|
||||||
|
:show="showModalInvite"
|
||||||
|
:editing-team-i-d="editingTeamID"
|
||||||
|
@hide-modal="displayModalInvite(false)"
|
||||||
|
/>
|
||||||
|
<TeamsEdit
|
||||||
|
:show="showModalEdit"
|
||||||
|
:editing-team="editingTeamName"
|
||||||
|
:editing-team-i-d="editingTeamID"
|
||||||
|
@hide-modal="displayModalEdit(false)"
|
||||||
|
@invite-team="inviteTeam(editingTeamName, editingTeamID)"
|
||||||
|
@refetch-teams="refetchTeams"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, ref } from "vue"
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
|
import IconUsers from "~icons/lucide/users"
|
||||||
import IconSettings from "~icons/lucide/settings"
|
import IconSettings from "~icons/lucide/settings"
|
||||||
import IconDownload from "~icons/lucide/download"
|
import IconDownload from "~icons/lucide/download"
|
||||||
import IconSearch from "~icons/lucide/search"
|
|
||||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
|
||||||
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
import IconUploadCloud from "~icons/lucide/upload-cloud"
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
import IconUserPlus from "~icons/lucide/user-plus"
|
||||||
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
||||||
import { pwaDefferedPrompt, installPWA } from "@modules/pwa"
|
import { pwaDefferedPrompt, installPWA } from "@modules/pwa"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { invokeAction } from "@helpers/actions"
|
import { invokeAction } from "@helpers/actions"
|
||||||
|
import { workspaceStatus$, updateWorkspaceTeamName } from "~/newstore/workspace"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -198,9 +276,108 @@ const currentUser = useReadonlyStream(
|
|||||||
platform.auth.getProbableUser()
|
platform.auth.getProbableUser()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
||||||
|
|
||||||
|
// TeamList-Adapter
|
||||||
|
const teamListAdapter = new TeamListAdapter(true)
|
||||||
|
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||||
|
|
||||||
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
|
const workspaceName = computed(() =>
|
||||||
|
workspace.value.type === "personal"
|
||||||
|
? t("workspace.personal")
|
||||||
|
: workspace.value.teamName
|
||||||
|
)
|
||||||
|
|
||||||
|
const refetchTeams = () => {
|
||||||
|
teamListAdapter.fetchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoggedIn(() => {
|
||||||
|
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => myTeams.value,
|
||||||
|
(newTeams) => {
|
||||||
|
if (newTeams && workspace.value.type === "team" && workspace.value.teamID) {
|
||||||
|
const team = newTeams.find((team) => team.id === workspace.value.teamID)
|
||||||
|
if (team) {
|
||||||
|
selectedTeam.value = team
|
||||||
|
// Update the workspace name if it's not the same as the updated team name
|
||||||
|
if (team.name !== workspace.value.teamName) {
|
||||||
|
updateWorkspaceTeamName(workspace.value, team.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => workspace.value,
|
||||||
|
(newWorkspace) => {
|
||||||
|
if (newWorkspace.type === "team") {
|
||||||
|
const team = myTeams.value?.find((t) => t.id === newWorkspace.teamID)
|
||||||
|
if (team) {
|
||||||
|
selectedTeam.value = team
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const showModalInvite = ref(false)
|
||||||
|
const showModalEdit = ref(false)
|
||||||
|
|
||||||
|
const editingTeamName = ref<{ name: string }>({ name: "" })
|
||||||
|
const editingTeamID = ref("")
|
||||||
|
|
||||||
|
const displayModalInvite = (show: boolean) => {
|
||||||
|
showModalInvite.value = show
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayModalEdit = (show: boolean) => {
|
||||||
|
showModalEdit.value = show
|
||||||
|
teamListAdapter.fetchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const inviteTeam = (team: { name: string }, teamID: string) => {
|
||||||
|
editingTeamName.value = team
|
||||||
|
editingTeamID.value = teamID
|
||||||
|
displayModalInvite(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the workspace selected team invite modal if the user is an owner of the team else show the default invite modal
|
||||||
|
const handleInvite = () => {
|
||||||
|
if (
|
||||||
|
workspace.value.type === "team" &&
|
||||||
|
workspace.value.teamID &&
|
||||||
|
selectedTeam.value?.myRole === "OWNER"
|
||||||
|
) {
|
||||||
|
editingTeamID.value = workspace.value.teamID
|
||||||
|
displayModalInvite(true)
|
||||||
|
} else {
|
||||||
|
showTeamsModal.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the workspace selected team edit modal if the user is an owner of the team
|
||||||
|
const handleTeamEdit = () => {
|
||||||
|
if (
|
||||||
|
workspace.value.type === "team" &&
|
||||||
|
workspace.value.teamID &&
|
||||||
|
selectedTeam.value?.myRole === "OWNER"
|
||||||
|
) {
|
||||||
|
editingTeamID.value = workspace.value.teamID
|
||||||
|
editingTeamName.value = { name: selectedTeam.value.name }
|
||||||
|
displayModalEdit(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<any | null>(null)
|
const tippyActions = ref<any | null>(null)
|
||||||
const profile = ref<any | null>(null)
|
const profile = ref<any | null>(null)
|
||||||
const settings = ref<any | null>(null)
|
const settings = ref<any | null>(null)
|
||||||
const logout = ref<any | null>(null)
|
const logout = ref<any | null>(null)
|
||||||
|
const accountActions = ref<any | null>(null)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
33
packages/hoppscotch-common/src/components/app/Navigation.vue
Normal file
33
packages/hoppscotch-common/src/components/app/Navigation.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip', delay: [500, 20], allowHTML: true }"
|
||||||
|
:title="`${t(
|
||||||
|
'action.go_back'
|
||||||
|
)} <kbd>${getSpecialKey()}</kbd><kbd>←</kbd>`"
|
||||||
|
:icon="IconArrowLeft"
|
||||||
|
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
|
@click="router.go(-1)"
|
||||||
|
/>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip', delay: [500, 20], allowHTML: true }"
|
||||||
|
:title="`${t(
|
||||||
|
'action.go_forward'
|
||||||
|
)} <kbd>${getSpecialKey()}</kbd><kbd>→</kbd>`"
|
||||||
|
:icon="IconArrowRight"
|
||||||
|
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||||
|
@click="router.go(1)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { useRouter } from "vue-router"
|
||||||
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import IconArrowLeft from "~icons/lucide/arrow-left"
|
||||||
|
import IconArrowRight from "~icons/lucide/arrow-right"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
const router = useRouter()
|
||||||
|
</script>
|
||||||
@@ -145,7 +145,7 @@ import IconActivity from "~icons/lucide/activity"
|
|||||||
import IconLock from "~icons/lucide/lock"
|
import IconLock from "~icons/lucide/lock"
|
||||||
import IconDiscord from "~icons/brands/discord"
|
import IconDiscord from "~icons/brands/discord"
|
||||||
import IconTwitter from "~icons/brands/twitter"
|
import IconTwitter from "~icons/brands/twitter"
|
||||||
import IconGithub from "~icons/hopp/github"
|
import IconGithub from "~icons/lucide/github"
|
||||||
import IconMessageCircle from "~icons/lucide/message-circle"
|
import IconMessageCircle from "~icons/lucide/message-circle"
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
import IconUserPlus from "~icons/lucide/user-plus"
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import IconTwitter from "~icons/brands/twitter"
|
import IconTwitter from "~icons/brands/twitter"
|
||||||
import IconDiscord from "~icons/brands/discord"
|
import IconDiscord from "~icons/brands/discord"
|
||||||
import IconGitHub from "~icons/hopp/github"
|
import IconGitHub from "~icons/lucide/github"
|
||||||
import IconMessageCircle from "~icons/lucide/message-circle"
|
import IconMessageCircle from "~icons/lucide/message-circle"
|
||||||
import IconGift from "~icons/lucide/gift"
|
import IconGift from "~icons/lucide/gift"
|
||||||
import IconZap from "~icons/lucide/zap"
|
import IconZap from "~icons/lucide/zap"
|
||||||
|
|||||||
@@ -1,21 +1,42 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
<div class="flex flex-col">
|
||||||
<div
|
<div
|
||||||
class="flex items-stretch group"
|
class="h-1 w-full transition"
|
||||||
@dragover.prevent
|
:class="[
|
||||||
@drop.prevent="dropEvent"
|
{
|
||||||
|
'bg-accentDark': ordering && notSameDestination,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@drop="orderUpdateCollectionEvent"
|
||||||
|
@dragover.prevent="ordering = true"
|
||||||
|
@dragleave="ordering = false"
|
||||||
|
@dragend="resetDragState"
|
||||||
|
></div>
|
||||||
|
<div class="flex flex-col relative">
|
||||||
|
<div
|
||||||
|
class="absolute bg-accent opacity-0 pointer-events-none inset-0 z-1 transition"
|
||||||
|
:class="{
|
||||||
|
'opacity-25': dragging && notSameDestination,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="flex items-stretch group relative z-3"
|
||||||
|
:draggable="!hasNoTeamAccess"
|
||||||
|
@dragstart="dragStart"
|
||||||
|
@drop="dropEvent"
|
||||||
@dragover="dragging = true"
|
@dragover="dragging = true"
|
||||||
@drop="dragging = false"
|
|
||||||
@dragleave="dragging = false"
|
@dragleave="dragging = false"
|
||||||
@dragend="dragging = false"
|
@dragend="resetDragState"
|
||||||
@contextmenu.prevent="options?.tippy.show()"
|
@contextmenu.prevent="options?.tippy.show()"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="flex items-center justify-center px-4 cursor-pointer"
|
class="flex items-center justify-center px-4 cursor-pointer"
|
||||||
@click="emit('toggle-children')"
|
@click="emit('toggle-children')"
|
||||||
>
|
>
|
||||||
|
<HoppSmartSpinner v-if="isCollLoading" />
|
||||||
<component
|
<component
|
||||||
:is="collectionIcon"
|
:is="collectionIcon"
|
||||||
|
v-else
|
||||||
class="svg-icons"
|
class="svg-icons"
|
||||||
:class="{ 'text-accent': isSelected }"
|
:class="{ 'text-accent': isSelected }"
|
||||||
/>
|
/>
|
||||||
@@ -136,6 +157,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -160,6 +182,11 @@ type FolderType = "collection" | "folder"
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
data: {
|
data: {
|
||||||
type: Object as PropType<HoppCollection<HoppRESTRequest> | TeamCollection>,
|
type: Object as PropType<HoppCollection<HoppRESTRequest> | TeamCollection>,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
@@ -185,7 +212,7 @@ const props = defineProps({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
isSelected: {
|
isSelected: {
|
||||||
type: Boolean,
|
type: Boolean as PropType<boolean | null>,
|
||||||
default: false,
|
default: false,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
@@ -199,6 +226,11 @@ const props = defineProps({
|
|||||||
default: false,
|
default: false,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
collectionMoveLoading: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -209,6 +241,9 @@ const emit = defineEmits<{
|
|||||||
(event: "export-data"): void
|
(event: "export-data"): void
|
||||||
(event: "remove-collection"): void
|
(event: "remove-collection"): void
|
||||||
(event: "drop-event", payload: DataTransfer): void
|
(event: "drop-event", payload: DataTransfer): void
|
||||||
|
(event: "drag-event", payload: DataTransfer): void
|
||||||
|
(event: "dragging", payload: boolean): void
|
||||||
|
(event: "update-collection-order", payload: DataTransfer): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
@@ -220,6 +255,21 @@ const exportAction = ref<HTMLButtonElement | null>(null)
|
|||||||
const options = ref<TippyComponent | null>(null)
|
const options = ref<TippyComponent | null>(null)
|
||||||
|
|
||||||
const dragging = ref(false)
|
const dragging = ref(false)
|
||||||
|
const ordering = ref(false)
|
||||||
|
const dropItemID = ref("")
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
emit("dragging", true)
|
||||||
|
} else {
|
||||||
|
emit("dragging", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const collectionIcon = computed(() => {
|
const collectionIcon = computed(() => {
|
||||||
if (props.isSelected) return IconCheckCircle
|
if (props.isSelected) return IconCheckCircle
|
||||||
@@ -243,10 +293,47 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const dropEvent = ({ dataTransfer }: DragEvent) => {
|
const dragStart = ({ dataTransfer }: DragEvent) => {
|
||||||
if (dataTransfer) {
|
if (dataTransfer) {
|
||||||
|
emit("drag-event", dataTransfer)
|
||||||
|
dropItemID.value = dataTransfer.getData("collectionIndex")
|
||||||
dragging.value = !dragging.value
|
dragging.value = !dragging.value
|
||||||
emit("drop-event", dataTransfer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dropEvent = (e: DragEvent) => {
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.stopPropagation()
|
||||||
|
emit("drop-event", e.dataTransfer)
|
||||||
|
dragging.value = !dragging.value
|
||||||
|
dropItemID.value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderUpdateCollectionEvent = (e: DragEvent) => {
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.stopPropagation()
|
||||||
|
emit("update-collection-order", e.dataTransfer)
|
||||||
|
ordering.value = !ordering.value
|
||||||
|
dropItemID.value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notSameDestination = computed(() => {
|
||||||
|
return dropItemID.value !== props.id
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCollLoading = computed(() => {
|
||||||
|
if (props.collectionMoveLoading.length > 0 && props.data.id) {
|
||||||
|
return props.collectionMoveLoading.includes(props.data.id)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const resetDragState = () => {
|
||||||
|
dragging.value = false
|
||||||
|
ordering.value = false
|
||||||
|
dropItemID.value = ""
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1 bg-primary">
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
||||||
:style="
|
:style="
|
||||||
@@ -33,9 +33,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1">
|
||||||
<SmartTree :adapter="myAdapter">
|
<SmartTree :adapter="myAdapter">
|
||||||
<template #content="{ node, toggleChildren, isOpen }">
|
<template
|
||||||
|
#content="{ node, toggleChildren, isOpen, highlightChildren }"
|
||||||
|
>
|
||||||
<CollectionsCollection
|
<CollectionsCollection
|
||||||
v-if="node.data.type === 'collections'"
|
v-if="node.data.type === 'collections'"
|
||||||
|
:id="node.id"
|
||||||
:data="node.data.data.data"
|
:data="node.data.data.data"
|
||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:is-open="isOpen"
|
:is-open="isOpen"
|
||||||
@@ -72,6 +75,11 @@
|
|||||||
"
|
"
|
||||||
@remove-collection="emit('remove-collection', node.id)"
|
@remove-collection="emit('remove-collection', node.id)"
|
||||||
@drop-event="dropEvent($event, node.id)"
|
@drop-event="dropEvent($event, node.id)"
|
||||||
|
@drag-event="dragEvent($event, node.id)"
|
||||||
|
@update-collection-order="updateCollectionOrder($event, node.id)"
|
||||||
|
@dragging="
|
||||||
|
(isDraging) => highlightChildren(isDraging ? node.id : null)
|
||||||
|
"
|
||||||
@toggle-children="
|
@toggle-children="
|
||||||
() => {
|
() => {
|
||||||
toggleChildren(),
|
toggleChildren(),
|
||||||
@@ -85,6 +93,7 @@
|
|||||||
/>
|
/>
|
||||||
<CollectionsCollection
|
<CollectionsCollection
|
||||||
v-if="node.data.type === 'folders'"
|
v-if="node.data.type === 'folders'"
|
||||||
|
:id="node.id"
|
||||||
:data="node.data.data.data"
|
:data="node.data.data.data"
|
||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:is-open="isOpen"
|
:is-open="isOpen"
|
||||||
@@ -121,6 +130,11 @@
|
|||||||
"
|
"
|
||||||
@remove-collection="emit('remove-folder', node.id)"
|
@remove-collection="emit('remove-folder', node.id)"
|
||||||
@drop-event="dropEvent($event, node.id)"
|
@drop-event="dropEvent($event, node.id)"
|
||||||
|
@drag-event="dragEvent($event, node.id)"
|
||||||
|
@update-collection-order="updateCollectionOrder($event, node.id)"
|
||||||
|
@dragging="
|
||||||
|
(isDraging) => highlightChildren(isDraging ? node.id : null)
|
||||||
|
"
|
||||||
@toggle-children="
|
@toggle-children="
|
||||||
() => {
|
() => {
|
||||||
toggleChildren(),
|
toggleChildren(),
|
||||||
@@ -182,7 +196,13 @@
|
|||||||
@drag-request="
|
@drag-request="
|
||||||
dragRequest($event, {
|
dragRequest($event, {
|
||||||
folderPath: node.data.data.parentIndex,
|
folderPath: node.data.data.parentIndex,
|
||||||
requestIndex: pathToIndex(node.id),
|
requestIndex: node.id,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@update-request-order="
|
||||||
|
updateRequestOrder($event, {
|
||||||
|
folderPath: node.data.data.parentIndex,
|
||||||
|
requestIndex: node.id,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
@@ -413,7 +433,29 @@ const emit = defineEmits<{
|
|||||||
payload: {
|
payload: {
|
||||||
folderPath: string
|
folderPath: string
|
||||||
requestIndex: string
|
requestIndex: string
|
||||||
collectionIndex: string
|
destinationCollectionIndex: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
event: "drop-collection",
|
||||||
|
payload: {
|
||||||
|
collectionIndexDragged: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
event: "update-request-order",
|
||||||
|
payload: {
|
||||||
|
dragedRequestIndex: string
|
||||||
|
destinationRequestIndex: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
event: "update-collection-order",
|
||||||
|
payload: {
|
||||||
|
dragedCollectionIndex: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
}
|
}
|
||||||
): void
|
): void
|
||||||
(event: "select", payload: Picked | null): void
|
(event: "select", payload: Picked | null): void
|
||||||
@@ -502,6 +544,10 @@ const selectRequest = (data: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dragEvent = (dataTransfer: DataTransfer, collectionIndex: string) => {
|
||||||
|
dataTransfer.setData("collectionIndex", collectionIndex)
|
||||||
|
}
|
||||||
|
|
||||||
const dragRequest = (
|
const dragRequest = (
|
||||||
dataTransfer: DataTransfer,
|
dataTransfer: DataTransfer,
|
||||||
{
|
{
|
||||||
@@ -514,13 +560,56 @@ const dragRequest = (
|
|||||||
dataTransfer.setData("requestIndex", requestIndex)
|
dataTransfer.setData("requestIndex", requestIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dropEvent = (dataTransfer: DataTransfer, collectionIndex: string) => {
|
const dropEvent = (
|
||||||
|
dataTransfer: DataTransfer,
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
) => {
|
||||||
const folderPath = dataTransfer.getData("folderPath")
|
const folderPath = dataTransfer.getData("folderPath")
|
||||||
const requestIndex = dataTransfer.getData("requestIndex")
|
const requestIndex = dataTransfer.getData("requestIndex")
|
||||||
|
const collectionIndexDragged = dataTransfer.getData("collectionIndex")
|
||||||
|
|
||||||
|
if (folderPath && requestIndex) {
|
||||||
emit("drop-request", {
|
emit("drop-request", {
|
||||||
folderPath,
|
folderPath,
|
||||||
requestIndex,
|
requestIndex,
|
||||||
collectionIndex,
|
destinationCollectionIndex,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
emit("drop-collection", {
|
||||||
|
collectionIndexDragged,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRequestOrder = (
|
||||||
|
dataTransfer: DataTransfer,
|
||||||
|
{
|
||||||
|
folderPath,
|
||||||
|
requestIndex,
|
||||||
|
}: { folderPath: string | null; requestIndex: string }
|
||||||
|
) => {
|
||||||
|
if (!folderPath) return
|
||||||
|
const dragedRequestIndex = dataTransfer.getData("requestIndex")
|
||||||
|
const destinationRequestIndex = requestIndex
|
||||||
|
const destinationCollectionIndex = folderPath
|
||||||
|
|
||||||
|
emit("update-request-order", {
|
||||||
|
dragedRequestIndex,
|
||||||
|
destinationRequestIndex,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollectionOrder = (
|
||||||
|
dataTransfer: DataTransfer,
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
) => {
|
||||||
|
const dragedCollectionIndex = dataTransfer.getData("collectionIndex")
|
||||||
|
|
||||||
|
emit("update-collection-order", {
|
||||||
|
dragedCollectionIndex,
|
||||||
|
destinationCollectionIndex,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
|
<div class="flex flex-col">
|
||||||
|
<div
|
||||||
|
class="h-1"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
'bg-accentDark': ordering,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@drop="dropEvent"
|
||||||
|
@dragover.prevent="ordering = true"
|
||||||
|
@dragleave="ordering = false"
|
||||||
|
@dragend="ordering = false"
|
||||||
|
></div>
|
||||||
<div
|
<div
|
||||||
class="flex items-stretch group"
|
class="flex items-stretch group"
|
||||||
draggable="true"
|
:draggable="!hasNoTeamAccess"
|
||||||
@dragstart="dragStart"
|
@dragstart="dragStart"
|
||||||
@dragover.stop
|
@dragover.prevent="dragging = true"
|
||||||
@dragleave="dragging = false"
|
@dragleave="dragging = false"
|
||||||
@dragend="dragging = false"
|
@dragend="dragging = false"
|
||||||
@contextmenu.prevent="options?.tippy.show()"
|
@contextmenu.prevent="options?.tippy.show()"
|
||||||
@@ -20,6 +32,7 @@
|
|||||||
class="svg-icons"
|
class="svg-icons"
|
||||||
:class="{ 'text-accent': isSelected }"
|
:class="{ 'text-accent': isSelected }"
|
||||||
/>
|
/>
|
||||||
|
<HoppSmartSpinner v-else-if="isRequestLoading" />
|
||||||
<span v-else class="font-semibold truncate text-tiny">
|
<span v-else class="font-semibold truncate text-tiny">
|
||||||
{{ request.method }}
|
{{ request.method }}
|
||||||
</span>
|
</span>
|
||||||
@@ -149,6 +162,11 @@ const props = defineProps({
|
|||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
requestID: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
collectionsType: {
|
collectionsType: {
|
||||||
type: String as PropType<CollectionType>,
|
type: String as PropType<CollectionType>,
|
||||||
default: "my-collections",
|
default: "my-collections",
|
||||||
@@ -175,10 +193,15 @@ const props = defineProps({
|
|||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
isSelected: {
|
isSelected: {
|
||||||
type: Boolean,
|
type: Boolean as PropType<boolean | null>,
|
||||||
default: false,
|
default: false,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
requestMoveLoading: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -187,6 +210,7 @@ const emit = defineEmits<{
|
|||||||
(event: "remove-request"): void
|
(event: "remove-request"): void
|
||||||
(event: "select-request"): void
|
(event: "select-request"): void
|
||||||
(event: "drag-request", payload: DataTransfer): void
|
(event: "drag-request", payload: DataTransfer): void
|
||||||
|
(event: "update-request-order", payload: DataTransfer): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
@@ -196,6 +220,7 @@ const options = ref<TippyComponent | null>(null)
|
|||||||
const duplicate = ref<HTMLButtonElement | null>(null)
|
const duplicate = ref<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
const dragging = ref(false)
|
const dragging = ref(false)
|
||||||
|
const ordering = ref(false)
|
||||||
|
|
||||||
const requestMethodLabels = {
|
const requestMethodLabels = {
|
||||||
get: "text-green-500",
|
get: "text-green-500",
|
||||||
@@ -228,8 +253,24 @@ const selectRequest = () => {
|
|||||||
|
|
||||||
const dragStart = ({ dataTransfer }: DragEvent) => {
|
const dragStart = ({ dataTransfer }: DragEvent) => {
|
||||||
if (dataTransfer) {
|
if (dataTransfer) {
|
||||||
dragging.value = !dragging.value
|
|
||||||
emit("drag-request", dataTransfer)
|
emit("drag-request", dataTransfer)
|
||||||
|
dragging.value = !dragging.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dropEvent = (e: DragEvent) => {
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.stopPropagation()
|
||||||
|
ordering.value = !ordering.value
|
||||||
|
emit("update-request-order", e.dataTransfer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRequestLoading = computed(() => {
|
||||||
|
if (props.requestMoveLoading.length > 0 && props.requestID) {
|
||||||
|
return props.requestMoveLoading.includes(props.requestID)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1 bg-primary">
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
||||||
:style="
|
:style="
|
||||||
saveRequest
|
saveRequest
|
||||||
? 'top: calc(var(--upper-secondary-sticky-fold) - var(--line-height-body))'
|
? 'top: calc(var(--upper-primary-sticky-fold) - var(--line-height-body))'
|
||||||
: 'top: var(--upper-secondary-sticky-fold)'
|
: 'top: var(--upper-primary-sticky-fold)'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
@@ -47,14 +47,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col overflow-hidden">
|
<div class="flex flex-col overflow-hidden">
|
||||||
<SmartTree :adapter="teamAdapter">
|
<SmartTree :adapter="teamAdapter">
|
||||||
<template #content="{ node, toggleChildren, isOpen }">
|
<template
|
||||||
|
#content="{ node, toggleChildren, isOpen, highlightChildren }"
|
||||||
|
>
|
||||||
<CollectionsCollection
|
<CollectionsCollection
|
||||||
v-if="node.data.type === 'collections'"
|
v-if="node.data.type === 'collections'"
|
||||||
|
:id="node.data.data.data.id"
|
||||||
:data="node.data.data.data"
|
:data="node.data.data.data"
|
||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:is-open="isOpen"
|
:is-open="isOpen"
|
||||||
:export-loading="exportLoading"
|
:export-loading="exportLoading"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess"
|
||||||
|
:collection-move-loading="collectionMoveLoading"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
isSelected({
|
isSelected({
|
||||||
collectionID: node.id,
|
collectionID: node.id,
|
||||||
@@ -87,6 +91,15 @@
|
|||||||
emit('export-data', node.data.data.data)
|
emit('export-data', node.data.data.data)
|
||||||
"
|
"
|
||||||
@remove-collection="emit('remove-collection', node.id)"
|
@remove-collection="emit('remove-collection', node.id)"
|
||||||
|
@drop-event="dropEvent($event, node.id)"
|
||||||
|
@drag-event="dragEvent($event, node.id)"
|
||||||
|
@update-collection-order="
|
||||||
|
updateCollectionOrder($event, node.data.data.data.id)
|
||||||
|
"
|
||||||
|
@dragging="
|
||||||
|
(isDraging) =>
|
||||||
|
highlightChildren(isDraging ? node.data.data.data.id : null)
|
||||||
|
"
|
||||||
@toggle-children="
|
@toggle-children="
|
||||||
() => {
|
() => {
|
||||||
toggleChildren(),
|
toggleChildren(),
|
||||||
@@ -100,11 +113,13 @@
|
|||||||
/>
|
/>
|
||||||
<CollectionsCollection
|
<CollectionsCollection
|
||||||
v-if="node.data.type === 'folders'"
|
v-if="node.data.type === 'folders'"
|
||||||
|
:id="node.data.data.data.id"
|
||||||
:data="node.data.data.data"
|
:data="node.data.data.data"
|
||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:is-open="isOpen"
|
:is-open="isOpen"
|
||||||
:export-loading="exportLoading"
|
:export-loading="exportLoading"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess"
|
||||||
|
:collection-move-loading="collectionMoveLoading"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
isSelected({
|
isSelected({
|
||||||
folderID: node.data.data.data.id,
|
folderID: node.data.data.data.id,
|
||||||
@@ -139,6 +154,15 @@
|
|||||||
node.data.type === 'folders' &&
|
node.data.type === 'folders' &&
|
||||||
emit('remove-folder', node.data.data.data.id)
|
emit('remove-folder', node.data.data.data.id)
|
||||||
"
|
"
|
||||||
|
@drop-event="dropEvent($event, node.data.data.data.id)"
|
||||||
|
@drag-event="dragEvent($event, node.data.data.data.id)"
|
||||||
|
@update-collection-order="
|
||||||
|
updateCollectionOrder($event, node.data.data.data.id)
|
||||||
|
"
|
||||||
|
@dragging="
|
||||||
|
(isDraging) =>
|
||||||
|
highlightChildren(isDraging ? node.data.data.data.id : null)
|
||||||
|
"
|
||||||
@toggle-children="
|
@toggle-children="
|
||||||
() => {
|
() => {
|
||||||
toggleChildren(),
|
toggleChildren(),
|
||||||
@@ -153,10 +177,12 @@
|
|||||||
<CollectionsRequest
|
<CollectionsRequest
|
||||||
v-if="node.data.type === 'requests'"
|
v-if="node.data.type === 'requests'"
|
||||||
:request="node.data.data.data.request"
|
:request="node.data.data.data.request"
|
||||||
|
:request-i-d="node.data.data.data.id"
|
||||||
:collections-type="collectionsType.type"
|
:collections-type="collectionsType.type"
|
||||||
:duplicate-loading="duplicateLoading"
|
:duplicate-loading="duplicateLoading"
|
||||||
:is-active="isActiveRequest(node.data.data.data.id)"
|
:is-active="isActiveRequest(node.data.data.data.id)"
|
||||||
:has-no-team-access="hasNoTeamAccess"
|
:has-no-team-access="hasNoTeamAccess"
|
||||||
|
:request-move-loading="requestMoveLoading"
|
||||||
:is-selected="
|
:is-selected="
|
||||||
isSelected({
|
isSelected({
|
||||||
requestID: node.data.data.data.id,
|
requestID: node.data.data.data.id,
|
||||||
@@ -190,18 +216,31 @@
|
|||||||
requestIndex: node.data.data.data.id,
|
requestIndex: node.data.data.data.id,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
|
@drag-request="
|
||||||
|
dragRequest($event, {
|
||||||
|
folderPath: node.data.data.parentIndex,
|
||||||
|
requestIndex: node.data.data.data.id,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@update-request-order="
|
||||||
|
updateRequestOrder($event, {
|
||||||
|
folderPath: node.data.data.parentIndex,
|
||||||
|
requestIndex: node.data.data.data.id,
|
||||||
|
})
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #emptyNode="{ node }">
|
<template #emptyNode="{ node }">
|
||||||
<div v-if="node === null">
|
<div v-if="node === null">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||||
|
@drop="(e) => e.stopPropagation()"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-4"
|
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-4"
|
||||||
:alt="`${t('empty.collections')}`"
|
:alt="`${t('empty.collection')}`"
|
||||||
/>
|
/>
|
||||||
<span class="pb-4 text-center">
|
<span class="pb-4 text-center">
|
||||||
{{ t("empty.collections") }}
|
{{ t("empty.collections") }}
|
||||||
@@ -213,11 +252,12 @@
|
|||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
:title="t('team.no_access')"
|
:title="t('team.no_access')"
|
||||||
:label="t('add.new')"
|
:label="t('action.new')"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-else
|
v-else
|
||||||
:label="t('add.new')"
|
:icon="IconPlus"
|
||||||
|
:label="t('action.new')"
|
||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
@click="emit('display-modal-add')"
|
@click="emit('display-modal-add')"
|
||||||
@@ -227,6 +267,7 @@
|
|||||||
<div
|
<div
|
||||||
v-else-if="node.data.type === 'collections'"
|
v-else-if="node.data.type === 'collections'"
|
||||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||||
|
@drop="(e) => e.stopPropagation()"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
@@ -235,34 +276,13 @@
|
|||||||
:alt="`${t('empty.collection')}`"
|
:alt="`${t('empty.collection')}`"
|
||||||
/>
|
/>
|
||||||
<span class="pb-4 text-center">
|
<span class="pb-4 text-center">
|
||||||
{{ t("empty.collection") }}
|
{{ t("empty.collections") }}
|
||||||
</span>
|
</span>
|
||||||
<HoppButtonSecondary
|
|
||||||
v-if="hasNoTeamAccess"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
disabled
|
|
||||||
filled
|
|
||||||
outline
|
|
||||||
:title="t('team.no_access')"
|
|
||||||
:label="t('add.new')"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-else
|
|
||||||
:label="t('add.new')"
|
|
||||||
filled
|
|
||||||
outline
|
|
||||||
@click="
|
|
||||||
node.data.type === 'collections' &&
|
|
||||||
emit('add-folder', {
|
|
||||||
path: node.id,
|
|
||||||
folder: node.data.data.data,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="node.data.type === 'folders'"
|
v-else-if="node.data.type === 'folders'"
|
||||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||||
|
@drop="(e) => e.stopPropagation()"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="`/images/states/${colorMode.value}/pack.svg`"
|
:src="`/images/states/${colorMode.value}/pack.svg`"
|
||||||
@@ -347,6 +367,16 @@ const props = defineProps({
|
|||||||
default: null,
|
default: null,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
collectionMoveLoading: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
requestMoveLoading: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -410,6 +440,36 @@ const emit = defineEmits<{
|
|||||||
folderPath?: string | undefined
|
folderPath?: string | undefined
|
||||||
}
|
}
|
||||||
): void
|
): void
|
||||||
|
(
|
||||||
|
event: "drop-request",
|
||||||
|
payload: {
|
||||||
|
folderPath: string
|
||||||
|
requestIndex: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
event: "drop-collection",
|
||||||
|
payload: {
|
||||||
|
collectionIndexDragged: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
event: "update-request-order",
|
||||||
|
payload: {
|
||||||
|
dragedRequestIndex: string
|
||||||
|
destinationRequestIndex: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
|
(
|
||||||
|
event: "update-collection-order",
|
||||||
|
payload: {
|
||||||
|
dragedCollectionIndex: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}
|
||||||
|
): void
|
||||||
(event: "select", payload: Picked | null): void
|
(event: "select", payload: Picked | null): void
|
||||||
(event: "expand-team-collection", payload: string): void
|
(event: "expand-team-collection", payload: string): void
|
||||||
(event: "display-modal-add"): void
|
(event: "display-modal-add"): void
|
||||||
@@ -493,6 +553,74 @@ const selectRequest = (data: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dragRequest = (
|
||||||
|
dataTransfer: DataTransfer,
|
||||||
|
{
|
||||||
|
folderPath,
|
||||||
|
requestIndex,
|
||||||
|
}: { folderPath: string | null; requestIndex: string }
|
||||||
|
) => {
|
||||||
|
if (!folderPath) return
|
||||||
|
dataTransfer.setData("folderPath", folderPath)
|
||||||
|
dataTransfer.setData("requestIndex", requestIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragEvent = (dataTransfer: DataTransfer, collectionIndex: string) => {
|
||||||
|
dataTransfer.setData("collectionIndex", collectionIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropEvent = (
|
||||||
|
dataTransfer: DataTransfer,
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
) => {
|
||||||
|
const folderPath = dataTransfer.getData("folderPath")
|
||||||
|
const requestIndex = dataTransfer.getData("requestIndex")
|
||||||
|
const collectionIndexDragged = dataTransfer.getData("collectionIndex")
|
||||||
|
if (folderPath && requestIndex) {
|
||||||
|
emit("drop-request", {
|
||||||
|
folderPath,
|
||||||
|
requestIndex,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
emit("drop-collection", {
|
||||||
|
collectionIndexDragged,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRequestOrder = (
|
||||||
|
dataTransfer: DataTransfer,
|
||||||
|
{
|
||||||
|
folderPath,
|
||||||
|
requestIndex,
|
||||||
|
}: { folderPath: string | null; requestIndex: string }
|
||||||
|
) => {
|
||||||
|
if (!folderPath) return
|
||||||
|
const dragedRequestIndex = dataTransfer.getData("requestIndex")
|
||||||
|
const destinationRequestIndex = requestIndex
|
||||||
|
const destinationCollectionIndex = folderPath
|
||||||
|
|
||||||
|
emit("update-request-order", {
|
||||||
|
dragedRequestIndex,
|
||||||
|
destinationRequestIndex,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCollectionOrder = (
|
||||||
|
dataTransfer: DataTransfer,
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
) => {
|
||||||
|
const dragedCollectionIndex = dataTransfer.getData("collectionIndex")
|
||||||
|
|
||||||
|
emit("update-collection-order", {
|
||||||
|
dragedCollectionIndex,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type TeamCollections = {
|
type TeamCollections = {
|
||||||
type: "collections"
|
type: "collections"
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-1">
|
|
||||||
<HoppSmartIntersection
|
|
||||||
class="flex flex-col flex-1"
|
|
||||||
@intersecting="onTeamSelectIntersect"
|
|
||||||
>
|
|
||||||
<tippy
|
|
||||||
interactive
|
|
||||||
trigger="click"
|
|
||||||
theme="popover"
|
|
||||||
placement="bottom"
|
|
||||||
:on-shown="() => tippyActions!.focus()"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="`${t('collection.select_team')}`"
|
|
||||||
class="bg-transparent border-b border-dividerLight select-wrapper"
|
|
||||||
>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-if="collectionsType.selectedTeam"
|
|
||||||
:icon="IconUsers"
|
|
||||||
:label="collectionsType.selectedTeam.name"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-else
|
|
||||||
:label="`${t('collection.select_team')}`"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
ref="tippyActions"
|
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="isTeamListLoading && myTeams.length === 0"
|
|
||||||
class="flex flex-col items-center justify-center flex-1 p-2"
|
|
||||||
>
|
|
||||||
<HoppSmartSpinner class="my-2" />
|
|
||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="myTeams.length > 0" class="flex flex-col">
|
|
||||||
<HoppSmartItem
|
|
||||||
v-for="(team, index) in myTeams"
|
|
||||||
:key="`team-${index}`"
|
|
||||||
:label="team.name"
|
|
||||||
:info-icon="
|
|
||||||
team.id === collectionsType.selectedTeam?.id
|
|
||||||
? IconDone
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
:active-info-icon="team.id === collectionsType.selectedTeam?.id"
|
|
||||||
:icon="IconUsers"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
updateSelectedTeam(team)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<hr />
|
|
||||||
<HoppSmartItem
|
|
||||||
:icon="IconPlus"
|
|
||||||
:label="t('team.create_new')"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
displayTeamModalAdd(true)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="flex flex-col items-center justify-center p-2 text-secondaryLight"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="`/images/states/${colorMode.value}/add_group.svg`"
|
|
||||||
loading="lazy"
|
|
||||||
class="inline-flex flex-col object-contain object-center mb-4 w-14 h-14"
|
|
||||||
:alt="`${t('empty.teams')}`"
|
|
||||||
/>
|
|
||||||
<span class="pb-4 text-center">
|
|
||||||
{{ t("empty.teams") }}
|
|
||||||
</span>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
:label="t('team.create_new')"
|
|
||||||
filled
|
|
||||||
outline
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
displayTeamModalAdd(true)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
</HoppSmartIntersection>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import IconUsers from "~icons/lucide/users"
|
|
||||||
import IconDone from "~icons/lucide/check"
|
|
||||||
import { PropType, ref } from "vue"
|
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import { TippyComponent } from "vue-tippy"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { useColorMode } from "@composables/theming"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
|
||||||
|
|
||||||
const t = useI18n()
|
|
||||||
const colorMode = useColorMode()
|
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
|
||||||
|
|
||||||
type CollectionType =
|
|
||||||
| {
|
|
||||||
type: "team-collections"
|
|
||||||
selectedTeam: SelectedTeam
|
|
||||||
}
|
|
||||||
| { type: "my-collections"; selectedTeam: undefined }
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
collectionsType: {
|
|
||||||
type: Object as PropType<CollectionType>,
|
|
||||||
default: () => ({ type: "my-collections", selectedTeam: undefined }),
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
myTeams: {
|
|
||||||
type: Array as PropType<GetMyTeamsQuery["myTeams"]>,
|
|
||||||
default: () => [],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
isTeamListLoading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: "update-selected-team", payload: SelectedTeam): void
|
|
||||||
(e: "team-select-intersect", payload: boolean): void
|
|
||||||
(e: "display-team-modal-add", payload: boolean): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const updateSelectedTeam = (team: SelectedTeam) => {
|
|
||||||
emit("update-selected-team", team)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTeamSelectIntersect = () => {
|
|
||||||
emit("team-select-intersect", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayTeamModalAdd = (display: boolean) => {
|
|
||||||
emit("display-team-modal-add", display)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="{ 'rounded border border-divider': saveRequest }">
|
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto rounded-t bg-primary"
|
:class="{
|
||||||
|
'rounded border border-divider': saveRequest,
|
||||||
|
'bg-primaryDark': draggingToRoot,
|
||||||
|
}"
|
||||||
|
class="flex-1"
|
||||||
|
@drop.prevent="dropToRoot"
|
||||||
|
@dragover.prevent="draggingToRoot = true"
|
||||||
|
@dragend="draggingToRoot = false"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto border-b bg-primary border-dividerLight"
|
||||||
|
:class="{ 'rounded-t': saveRequest }"
|
||||||
:style="
|
:style="
|
||||||
saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0'
|
saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<WorkspaceCurrent :section="t('tab.collections')" />
|
||||||
<input
|
<input
|
||||||
v-model="filterTexts"
|
v-model="filterTexts"
|
||||||
type="search"
|
type="search"
|
||||||
@@ -15,23 +26,8 @@
|
|||||||
:disabled="collectionsType.type === 'team-collections'"
|
:disabled="collectionsType.type === 'team-collections'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartTabs
|
|
||||||
v-model="selectedCollectionTab"
|
|
||||||
render-inactive-tabs
|
|
||||||
:styles="`
|
|
||||||
sticky overflow-x-auto border-y bg-primary border-dividerLight flex-shrink-0 z-10
|
|
||||||
${
|
|
||||||
saveRequest
|
|
||||||
? 'top-sidebarSecondaryStickyFold'
|
|
||||||
: 'top-sidebarPrimaryStickyFold'
|
|
||||||
}
|
|
||||||
`"
|
|
||||||
>
|
|
||||||
<HoppSmartTab
|
|
||||||
:id="'my-collections'"
|
|
||||||
:label="`${t('collection.my_collections')}`"
|
|
||||||
>
|
|
||||||
<CollectionsMyCollections
|
<CollectionsMyCollections
|
||||||
|
v-if="collectionsType.type === 'my-collections'"
|
||||||
:collections-type="collectionsType"
|
:collections-type="collectionsType"
|
||||||
:filtered-collections="filteredCollections"
|
:filtered-collections="filteredCollections"
|
||||||
:filter-text="filterTexts"
|
:filter-text="filterTexts"
|
||||||
@@ -44,6 +40,9 @@
|
|||||||
@export-data="exportData"
|
@export-data="exportData"
|
||||||
@remove-collection="removeCollection"
|
@remove-collection="removeCollection"
|
||||||
@remove-folder="removeFolder"
|
@remove-folder="removeFolder"
|
||||||
|
@drop-collection="dropCollection"
|
||||||
|
@update-request-order="updateRequestOrder"
|
||||||
|
@update-collection-order="updateCollectionOrder"
|
||||||
@edit-request="editRequest"
|
@edit-request="editRequest"
|
||||||
@duplicate-request="duplicateRequest"
|
@duplicate-request="duplicateRequest"
|
||||||
@remove-request="removeRequest"
|
@remove-request="removeRequest"
|
||||||
@@ -53,29 +52,8 @@
|
|||||||
@display-modal-add="displayModalAdd(true)"
|
@display-modal-add="displayModalAdd(true)"
|
||||||
@display-modal-import-export="displayModalImportExport(true)"
|
@display-modal-import-export="displayModalImportExport(true)"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
|
||||||
<HoppSmartTab
|
|
||||||
:id="'team-collections'"
|
|
||||||
:label="`${t('collection.team_collections')}`"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="sticky z-10 flex flex-1 bg-primary"
|
|
||||||
:style="
|
|
||||||
saveRequest
|
|
||||||
? 'top: calc(var(--upper-primary-sticky-fold) - var(--line-height-body))'
|
|
||||||
: 'top: var(--upper-primary-sticky-fold)'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<CollectionsTeamSelect
|
|
||||||
:collections-type="collectionsType"
|
|
||||||
:my-teams="myTeams"
|
|
||||||
:is-team-list-loading="isTeamListLoading"
|
|
||||||
@update-selected-team="updateSelectedTeam"
|
|
||||||
@team-select-intersect="onTeamSelectIntersect"
|
|
||||||
@display-team-modal-add="displayTeamModalAdd(true)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CollectionsTeamCollections
|
<CollectionsTeamCollections
|
||||||
|
v-else
|
||||||
:collections-type="collectionsType"
|
:collections-type="collectionsType"
|
||||||
:team-collection-list="teamCollectionList"
|
:team-collection-list="teamCollectionList"
|
||||||
:team-loading-collections="teamLoadingCollections"
|
:team-loading-collections="teamLoadingCollections"
|
||||||
@@ -83,6 +61,8 @@
|
|||||||
:duplicate-loading="duplicateLoading"
|
:duplicate-loading="duplicateLoading"
|
||||||
:save-request="saveRequest"
|
:save-request="saveRequest"
|
||||||
:picked="picked"
|
:picked="picked"
|
||||||
|
:collection-move-loading="collectionMoveLoading"
|
||||||
|
:request-move-loading="requestMoveLoading"
|
||||||
@add-request="addRequest"
|
@add-request="addRequest"
|
||||||
@add-folder="addFolder"
|
@add-folder="addFolder"
|
||||||
@edit-collection="editCollection"
|
@edit-collection="editCollection"
|
||||||
@@ -95,12 +75,20 @@
|
|||||||
@remove-request="removeRequest"
|
@remove-request="removeRequest"
|
||||||
@select-request="selectRequest"
|
@select-request="selectRequest"
|
||||||
@select="selectPicked"
|
@select="selectPicked"
|
||||||
|
@drop-request="dropRequest"
|
||||||
|
@drop-collection="dropCollection"
|
||||||
|
@update-request-order="updateRequestOrder"
|
||||||
|
@update-collection-order="updateCollectionOrder"
|
||||||
@expand-team-collection="expandTeamCollection"
|
@expand-team-collection="expandTeamCollection"
|
||||||
@display-modal-add="displayModalAdd(true)"
|
@display-modal-add="displayModalAdd(true)"
|
||||||
@display-modal-import-export="displayModalImportExport(true)"
|
@display-modal-import-export="displayModalImportExport(true)"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
<div
|
||||||
</HoppSmartTabs>
|
class="hidden bg-primaryDark flex-col flex-1 items-center py-15 justify-center px-4 text-secondaryLight"
|
||||||
|
:class="{ '!flex': draggingToRoot }"
|
||||||
|
>
|
||||||
|
<component :is="IconListEnd" class="svg-icons !w-8 !h-8" />
|
||||||
|
</div>
|
||||||
<CollectionsAdd
|
<CollectionsAdd
|
||||||
:show="showModalAdd"
|
:show="showModalAdd"
|
||||||
:loading-state="modalLoadingState"
|
:loading-state="modalLoadingState"
|
||||||
@@ -178,7 +166,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, PropType, reactive, ref, watch, nextTick } from "vue"
|
import { computed, PropType, reactive, ref, watch } from "vue"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
@@ -195,12 +183,15 @@ import {
|
|||||||
editRESTCollection,
|
editRESTCollection,
|
||||||
editRESTFolder,
|
editRESTFolder,
|
||||||
editRESTRequest,
|
editRESTRequest,
|
||||||
|
moveRESTFolder,
|
||||||
moveRESTRequest,
|
moveRESTRequest,
|
||||||
removeRESTCollection,
|
removeRESTCollection,
|
||||||
removeRESTFolder,
|
removeRESTFolder,
|
||||||
removeRESTRequest,
|
removeRESTRequest,
|
||||||
restCollections$,
|
restCollections$,
|
||||||
saveRESTRequestAs,
|
saveRESTRequestAs,
|
||||||
|
updateRESTRequestOrder,
|
||||||
|
updateRESTCollectionOrder,
|
||||||
} from "~/newstore/collections"
|
} from "~/newstore/collections"
|
||||||
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
|
import TeamCollectionAdapter from "~/helpers/teams/TeamCollectionAdapter"
|
||||||
import {
|
import {
|
||||||
@@ -226,11 +217,15 @@ import {
|
|||||||
renameCollection,
|
renameCollection,
|
||||||
deleteCollection,
|
deleteCollection,
|
||||||
importJSONToTeam,
|
importJSONToTeam,
|
||||||
|
moveRESTTeamCollection,
|
||||||
|
updateOrderRESTTeamCollection,
|
||||||
} from "~/helpers/backend/mutations/TeamCollection"
|
} from "~/helpers/backend/mutations/TeamCollection"
|
||||||
import {
|
import {
|
||||||
updateTeamRequest,
|
updateTeamRequest,
|
||||||
createRequestInCollection,
|
createRequestInCollection,
|
||||||
deleteTeamRequest,
|
deleteTeamRequest,
|
||||||
|
moveRESTTeamRequest,
|
||||||
|
updateOrderRESTTeamRequest,
|
||||||
} from "~/helpers/backend/mutations/TeamRequest"
|
} from "~/helpers/backend/mutations/TeamRequest"
|
||||||
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
import { TeamCollection } from "~/helpers/teams/TeamCollection"
|
||||||
import { Collection as NodeCollection } from "./MyCollections.vue"
|
import { Collection as NodeCollection } from "./MyCollections.vue"
|
||||||
@@ -243,7 +238,8 @@ import { HoppRequestSaveContext } from "~/helpers/types/HoppRequestSaveContext"
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { createCollectionGists } from "~/helpers/gist"
|
import { createCollectionGists } from "~/helpers/gist"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
|
import IconListEnd from "~icons/lucide/list-end"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -267,8 +263,6 @@ const emit = defineEmits<{
|
|||||||
(event: "update-collection-type", type: CollectionType["type"]): void
|
(event: "update-collection-type", type: CollectionType["type"]): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
type CollectionTabs = "my-collections" | "team-collections"
|
|
||||||
|
|
||||||
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||||
|
|
||||||
type CollectionType =
|
type CollectionType =
|
||||||
@@ -278,26 +272,11 @@ type CollectionType =
|
|||||||
}
|
}
|
||||||
| { type: "my-collections"; selectedTeam: undefined }
|
| { type: "my-collections"; selectedTeam: undefined }
|
||||||
|
|
||||||
const selectedCollectionTab = ref<CollectionTabs>("my-collections")
|
|
||||||
|
|
||||||
const collectionsType = ref<CollectionType>({
|
const collectionsType = ref<CollectionType>({
|
||||||
type: "my-collections",
|
type: "my-collections",
|
||||||
selectedTeam: undefined,
|
selectedTeam: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
|
||||||
() => selectedCollectionTab.value,
|
|
||||||
(tab) => {
|
|
||||||
if (tab === "team-collections" && !currentUser.value) {
|
|
||||||
invokeAction("modals.login.toggle")
|
|
||||||
nextTick(() => (selectedCollectionTab.value = "my-collections"))
|
|
||||||
} else {
|
|
||||||
collectionsType.value.type = tab
|
|
||||||
emit("update-collection-type", tab)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Collection Data
|
// Collection Data
|
||||||
const editingCollection = ref<
|
const editingCollection = ref<
|
||||||
HoppCollection<HoppRESTRequest> | TeamCollection | null
|
HoppCollection<HoppRESTRequest> | TeamCollection | null
|
||||||
@@ -324,6 +303,11 @@ const currentUser = useReadonlyStream(
|
|||||||
)
|
)
|
||||||
const myCollections = useReadonlyStream(restCollections$, [], "deep")
|
const myCollections = useReadonlyStream(restCollections$, [], "deep")
|
||||||
|
|
||||||
|
// Draging
|
||||||
|
const draggingToRoot = ref(false)
|
||||||
|
const collectionMoveLoading = ref<string[]>([])
|
||||||
|
const requestMoveLoading = ref<string[]>([])
|
||||||
|
|
||||||
// Export - Import refs
|
// Export - Import refs
|
||||||
const collectionJSON = ref("")
|
const collectionJSON = ref("")
|
||||||
const exportingTeamCollections = ref(false)
|
const exportingTeamCollections = ref(false)
|
||||||
@@ -342,7 +326,6 @@ const clickedRequest = reactive({
|
|||||||
// TeamList-Adapter
|
// TeamList-Adapter
|
||||||
const teamListAdapter = new TeamListAdapter(true)
|
const teamListAdapter = new TeamListAdapter(true)
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||||
const isTeamListLoading = useReadonlyStream(teamListAdapter.loading$, false)
|
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
const teamListFetched = ref(false)
|
const teamListFetched = ref(false)
|
||||||
|
|
||||||
@@ -357,6 +340,19 @@ const teamLoadingCollections = useReadonlyStream(
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => myTeams.value,
|
||||||
|
(newTeams) => {
|
||||||
|
if (newTeams && !teamListFetched.value) {
|
||||||
|
teamListFetched.value = true
|
||||||
|
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
||||||
|
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||||
|
if (team) updateSelectedTeam(team)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => collectionsType.value.selectedTeam,
|
() => collectionsType.value.selectedTeam,
|
||||||
(newTeam) => {
|
(newTeam) => {
|
||||||
@@ -366,45 +362,53 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const switchToMyCollections = () => {
|
||||||
|
collectionsType.value.type = "my-collections"
|
||||||
|
collectionsType.value.selectedTeam = undefined
|
||||||
|
teamCollectionAdapter.changeTeamID(null)
|
||||||
|
}
|
||||||
|
|
||||||
const expandTeamCollection = (collectionID: string) => {
|
const expandTeamCollection = (collectionID: string) => {
|
||||||
teamCollectionAdapter.expandCollection(collectionID)
|
teamCollectionAdapter.expandCollection(collectionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) updateSelectedTeam(team)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const updateSelectedTeam = (team: SelectedTeam) => {
|
const updateSelectedTeam = (team: SelectedTeam) => {
|
||||||
if (team) {
|
if (team) {
|
||||||
|
collectionsType.value.type = "team-collections"
|
||||||
collectionsType.value.selectedTeam = team
|
collectionsType.value.selectedTeam = team
|
||||||
REMEMBERED_TEAM_ID.value = team.id
|
REMEMBERED_TEAM_ID.value = team.id
|
||||||
emit("update-team", team)
|
emit("update-team", team)
|
||||||
|
emit("update-collection-type", "team-collections")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoggedIn(() => {
|
onLoggedIn(() => {
|
||||||
teamListAdapter.initialize()
|
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||||
})
|
})
|
||||||
|
|
||||||
const onTeamSelectIntersect = () => {
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
// Load team data as soon as intersection
|
|
||||||
teamListAdapter.fetchList()
|
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
||||||
}
|
// Check if there is a teamID in the workspace, if yes, switch to team collection and select the team
|
||||||
|
// If there is no teamID, switch to my environment
|
||||||
|
watch(
|
||||||
|
() => workspace.value.teamID,
|
||||||
|
(teamID) => {
|
||||||
|
if (!teamID) {
|
||||||
|
switchToMyCollections()
|
||||||
|
} else if (teamID) {
|
||||||
|
const team = myTeams.value?.find((t) => t.id === teamID)
|
||||||
|
if (team) updateSelectedTeam(team)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Switch to my-collections and reset the team collection when user logout
|
// Switch to my-collections and reset the team collection when user logout
|
||||||
watch(
|
watch(
|
||||||
() => currentUser.value,
|
() => currentUser.value,
|
||||||
(user) => {
|
(user) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
selectedCollectionTab.value = "my-collections"
|
switchToMyCollections()
|
||||||
collectionsType.value.selectedTeam = undefined
|
|
||||||
teamCollectionAdapter.changeTeamID(null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1333,16 +1337,314 @@ const discardRequestChange = () => {
|
|||||||
confirmChangeToRequest.value = false
|
confirmChangeToRequest.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag and drop functions
|
/**
|
||||||
|
* Used to get the index of the request from the path
|
||||||
|
* @param path The path of the request
|
||||||
|
* @returns The index of the request
|
||||||
|
*/
|
||||||
|
const pathToIndex = computed(() => {
|
||||||
|
return (path: string) => {
|
||||||
|
const pathArr = path.split("/")
|
||||||
|
return parseInt(pathArr[pathArr.length - 1])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when the user drops the request inside a collection
|
||||||
|
* @param payload Object that contains the folder path, request index and the destination collection index
|
||||||
|
*/
|
||||||
const dropRequest = (payload: {
|
const dropRequest = (payload: {
|
||||||
folderPath: string
|
folderPath?: string | undefined
|
||||||
requestIndex: string
|
requestIndex: string
|
||||||
collectionIndex: string
|
destinationCollectionIndex: string
|
||||||
}) => {
|
}) => {
|
||||||
const { folderPath, requestIndex, collectionIndex } = payload
|
const { folderPath, requestIndex, destinationCollectionIndex } = payload
|
||||||
moveRESTRequest(folderPath, parseInt(requestIndex), collectionIndex)
|
if (!requestIndex || !destinationCollectionIndex) return
|
||||||
|
if (collectionsType.value.type === "my-collections" && folderPath) {
|
||||||
|
moveRESTRequest(
|
||||||
|
folderPath,
|
||||||
|
pathToIndex.value(requestIndex),
|
||||||
|
destinationCollectionIndex
|
||||||
|
)
|
||||||
|
toast.success(`${t("request.moved")}`)
|
||||||
|
draggingToRoot.value = false
|
||||||
|
} else if (hasTeamWriteAccess.value) {
|
||||||
|
// add the request index to the loading array
|
||||||
|
requestMoveLoading.value.push(requestIndex)
|
||||||
|
|
||||||
|
pipe(
|
||||||
|
moveRESTTeamRequest(destinationCollectionIndex, requestIndex),
|
||||||
|
TE.match(
|
||||||
|
(err: GQLError<string>) => {
|
||||||
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
|
requestMoveLoading.value.splice(
|
||||||
|
requestMoveLoading.value.indexOf(requestIndex),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// remove the request index from the loading array
|
||||||
|
requestMoveLoading.value.splice(
|
||||||
|
requestMoveLoading.value.indexOf(requestIndex),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
toast.success(`${t("request.moved")}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when the user moves the collection
|
||||||
|
* to a different collection or folder
|
||||||
|
* @param payload - object containing the collection index dragged and the destination collection index
|
||||||
|
*/
|
||||||
|
const dropCollection = (payload: {
|
||||||
|
collectionIndexDragged: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}) => {
|
||||||
|
const { collectionIndexDragged, destinationCollectionIndex } = payload
|
||||||
|
if (!collectionIndexDragged || !destinationCollectionIndex) return
|
||||||
|
if (collectionIndexDragged === destinationCollectionIndex) return
|
||||||
|
if (collectionsType.value.type === "my-collections") {
|
||||||
|
moveRESTFolder(collectionIndexDragged, destinationCollectionIndex)
|
||||||
|
draggingToRoot.value = false
|
||||||
|
toast.success(`${t("collection.moved")}`)
|
||||||
|
} else if (hasTeamWriteAccess.value) {
|
||||||
|
// add the collection index to the loading array
|
||||||
|
collectionMoveLoading.value.push(collectionIndexDragged)
|
||||||
|
pipe(
|
||||||
|
moveRESTTeamCollection(
|
||||||
|
collectionIndexDragged,
|
||||||
|
destinationCollectionIndex
|
||||||
|
),
|
||||||
|
TE.match(
|
||||||
|
(err: GQLError<string>) => {
|
||||||
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
|
collectionMoveLoading.value.splice(
|
||||||
|
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
toast.success(`${t("collection.moved")}`)
|
||||||
|
// remove the collection index from the loading array
|
||||||
|
collectionMoveLoading.value.splice(
|
||||||
|
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the collection is already in the root
|
||||||
|
* @param id - path of the collection
|
||||||
|
* @returns boolean - true if the collection is already in the root
|
||||||
|
*/
|
||||||
|
const isAlreadyInRoot = computed(() => {
|
||||||
|
return (id: string) => {
|
||||||
|
const indexPath = id.split("/").map((i) => parseInt(i))
|
||||||
|
return indexPath.length === 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when the user drops the collection
|
||||||
|
* to the root
|
||||||
|
* @param payload - object containing the collection index dragged
|
||||||
|
*/
|
||||||
|
const dropToRoot = ({ dataTransfer }: DragEvent) => {
|
||||||
|
if (dataTransfer) {
|
||||||
|
const collectionIndexDragged = dataTransfer.getData("collectionIndex")
|
||||||
|
if (!collectionIndexDragged) return
|
||||||
|
if (collectionsType.value.type === "my-collections") {
|
||||||
|
// check if the collection is already in the root
|
||||||
|
if (isAlreadyInRoot.value(collectionIndexDragged)) {
|
||||||
|
toast.error(`${t("collection.invalid_root_move")}`)
|
||||||
|
} else {
|
||||||
|
moveRESTFolder(collectionIndexDragged, null)
|
||||||
|
toast.success(`${t("collection.moved")}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
draggingToRoot.value = false
|
||||||
|
} else if (hasTeamWriteAccess.value) {
|
||||||
|
// add the collection index to the loading array
|
||||||
|
collectionMoveLoading.value.push(collectionIndexDragged)
|
||||||
|
|
||||||
|
// destination collection index is null since we are moving to root
|
||||||
|
pipe(
|
||||||
|
moveRESTTeamCollection(collectionIndexDragged, null),
|
||||||
|
TE.match(
|
||||||
|
(err: GQLError<string>) => {
|
||||||
|
collectionMoveLoading.value.splice(
|
||||||
|
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// remove the collection index from the loading array
|
||||||
|
collectionMoveLoading.value.splice(
|
||||||
|
collectionMoveLoading.value.indexOf(collectionIndexDragged),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
toast.success(`${t("collection.moved")}`)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to check if the request/collection is being moved to the same parent since reorder is only allowed within the same parent
|
||||||
|
* @param draggedReq - path index of the dragged request
|
||||||
|
* @param destinationReq - path index of the destination request
|
||||||
|
* @returns boolean - true if the request is being moved to the same parent
|
||||||
|
*/
|
||||||
|
const isSameSameParent = computed(
|
||||||
|
() => (draggedReq: string, destinationReq: string) => {
|
||||||
|
const draggedReqIndex = draggedReq.split("/").map((i) => parseInt(i))
|
||||||
|
const destinationReqIndex = destinationReq
|
||||||
|
.split("/")
|
||||||
|
.map((i) => parseInt(i))
|
||||||
|
|
||||||
|
// length of 1 means the request is in the root
|
||||||
|
if (draggedReqIndex.length === 1 && destinationReqIndex.length === 1) {
|
||||||
|
return true
|
||||||
|
} else if (
|
||||||
|
draggedReqIndex[draggedReqIndex.length - 2] ===
|
||||||
|
destinationReqIndex[destinationReqIndex.length - 2]
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when the user updates the request order in a collection
|
||||||
|
* @param payload - object containing the request index dragged and the destination request index
|
||||||
|
* with the destination collection index
|
||||||
|
*/
|
||||||
|
const updateRequestOrder = (payload: {
|
||||||
|
dragedRequestIndex: string
|
||||||
|
destinationRequestIndex: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
dragedRequestIndex,
|
||||||
|
destinationRequestIndex,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
} = payload
|
||||||
|
|
||||||
|
if (
|
||||||
|
!dragedRequestIndex ||
|
||||||
|
!destinationRequestIndex ||
|
||||||
|
!destinationCollectionIndex
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (dragedRequestIndex === destinationRequestIndex) return
|
||||||
|
|
||||||
|
if (collectionsType.value.type === "my-collections") {
|
||||||
|
if (!isSameSameParent.value(dragedRequestIndex, destinationRequestIndex)) {
|
||||||
|
toast.error(`${t("collection.different_parent")}`)
|
||||||
|
} else {
|
||||||
|
updateRESTRequestOrder(
|
||||||
|
pathToIndex.value(dragedRequestIndex),
|
||||||
|
pathToIndex.value(destinationRequestIndex),
|
||||||
|
destinationCollectionIndex
|
||||||
|
)
|
||||||
|
toast.success(`${t("request.order_changed")}`)
|
||||||
|
}
|
||||||
|
} else if (hasTeamWriteAccess.value) {
|
||||||
|
// add the request index to the loading array
|
||||||
|
requestMoveLoading.value.push(dragedRequestIndex)
|
||||||
|
|
||||||
|
pipe(
|
||||||
|
updateOrderRESTTeamRequest(
|
||||||
|
dragedRequestIndex,
|
||||||
|
destinationRequestIndex,
|
||||||
|
destinationCollectionIndex
|
||||||
|
),
|
||||||
|
TE.match(
|
||||||
|
(err: GQLError<string>) => {
|
||||||
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
|
requestMoveLoading.value.splice(
|
||||||
|
requestMoveLoading.value.indexOf(dragedRequestIndex),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
toast.success(`${t("request.order_changed")}`)
|
||||||
|
|
||||||
|
// remove the request index from the loading array
|
||||||
|
requestMoveLoading.value.splice(
|
||||||
|
requestMoveLoading.value.indexOf(dragedRequestIndex),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is called when the user updates the collection or folder order
|
||||||
|
* @param payload - object containing the collection index dragged and the destination collection index
|
||||||
|
*/
|
||||||
|
const updateCollectionOrder = (payload: {
|
||||||
|
dragedCollectionIndex: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}) => {
|
||||||
|
const { dragedCollectionIndex, destinationCollectionIndex } = payload
|
||||||
|
if (!dragedCollectionIndex || !destinationCollectionIndex) return
|
||||||
|
if (dragedCollectionIndex === destinationCollectionIndex) return
|
||||||
|
|
||||||
|
if (collectionsType.value.type === "my-collections") {
|
||||||
|
if (
|
||||||
|
!isSameSameParent.value(dragedCollectionIndex, destinationCollectionIndex)
|
||||||
|
) {
|
||||||
|
toast.error(`${t("collection.different_parent")}`)
|
||||||
|
} else {
|
||||||
|
updateRESTCollectionOrder(
|
||||||
|
dragedCollectionIndex,
|
||||||
|
destinationCollectionIndex
|
||||||
|
)
|
||||||
|
toast.success(`${t("collection.order_changed")}`)
|
||||||
|
}
|
||||||
|
} else if (hasTeamWriteAccess.value) {
|
||||||
|
collectionMoveLoading.value.push(dragedCollectionIndex)
|
||||||
|
pipe(
|
||||||
|
updateOrderRESTTeamCollection(
|
||||||
|
dragedCollectionIndex,
|
||||||
|
destinationCollectionIndex
|
||||||
|
),
|
||||||
|
TE.match(
|
||||||
|
(err: GQLError<string>) => {
|
||||||
|
toast.error(`${getErrorMessage(err)}`)
|
||||||
|
collectionMoveLoading.value.splice(
|
||||||
|
collectionMoveLoading.value.indexOf(dragedCollectionIndex),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
toast.success(`${t("collection.order_changed")}`)
|
||||||
|
collectionMoveLoading.value.splice(
|
||||||
|
collectionMoveLoading.value.indexOf(dragedCollectionIndex),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
}
|
||||||
// Import - Export Collection functions
|
// Import - Export Collection functions
|
||||||
/**
|
/**
|
||||||
* Export the whole my collection or specific team collection to JSON
|
* Export the whole my collection or specific team collection to JSON
|
||||||
@@ -1525,28 +1827,38 @@ const resetSelectedData = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getErrorMessage = (err: GQLError<string>) => {
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
if (err.type === "network_error") {
|
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
if (err.type === "network_error") {
|
||||||
return t("error.network_error")
|
return t("error.network_error")
|
||||||
} else {
|
} else {
|
||||||
switch (err.error) {
|
switch (err.error) {
|
||||||
case "team_coll/short_title":
|
case "team_coll/short_title":
|
||||||
console.error(err)
|
|
||||||
return t("collection.name_length_insufficient")
|
return t("collection.name_length_insufficient")
|
||||||
case "team/invalid_coll_id":
|
case "team/invalid_coll_id":
|
||||||
console.error(err)
|
case "bug/team_coll/no_coll_id":
|
||||||
return t("team.invalid_id")
|
case "team_req/invalid_target_id":
|
||||||
|
return t("team.invalid_coll_id")
|
||||||
case "team/not_required_role":
|
case "team/not_required_role":
|
||||||
console.error(err)
|
|
||||||
return t("profile.no_permission")
|
return t("profile.no_permission")
|
||||||
case "team_req/not_required_role":
|
case "team_req/not_required_role":
|
||||||
console.error(err)
|
|
||||||
return t("profile.no_permission")
|
return t("profile.no_permission")
|
||||||
case "Forbidden resource":
|
case "Forbidden resource":
|
||||||
console.error(err)
|
|
||||||
return t("profile.no_permission")
|
return t("profile.no_permission")
|
||||||
|
case "team_req/not_found":
|
||||||
|
return t("team.no_request_found")
|
||||||
|
case "bug/team_req/no_req_id":
|
||||||
|
return t("team.no_request_found")
|
||||||
|
case "team/collection_is_parent_coll":
|
||||||
|
return t("team.parent_coll_move")
|
||||||
|
case "team/target_and_destination_collection_are_same":
|
||||||
|
return t("team.same_target_destination")
|
||||||
|
case "team/target_collection_is_already_root_collection":
|
||||||
|
return t("collection.invalid_root_move")
|
||||||
|
case "team_req/requests_not_from_same_collection":
|
||||||
|
return t("request.different_collection")
|
||||||
|
case "team/team_collections_have_different_parents":
|
||||||
|
return t("collection.different_parent")
|
||||||
default:
|
default:
|
||||||
console.error(err)
|
|
||||||
return t("error.something_went_wrong")
|
return t("error.something_went_wrong")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<HoppSmartTabs
|
|
||||||
:id="'environments_tab'"
|
|
||||||
v-model="selectedEnvironmentTab"
|
|
||||||
render-inactive-tabs
|
|
||||||
>
|
|
||||||
<HoppSmartTab
|
|
||||||
:id="'my-environments'"
|
|
||||||
:label="`${t('environment.my_environments')}`"
|
|
||||||
/>
|
|
||||||
<HoppSmartTab
|
|
||||||
:id="'team-environments'"
|
|
||||||
:label="`${t('environment.team_environments')}`"
|
|
||||||
>
|
|
||||||
<HoppSmartIntersection @intersecting="onTeamSelectIntersect">
|
|
||||||
<tippy
|
|
||||||
interactive
|
|
||||||
trigger="click"
|
|
||||||
theme="popover"
|
|
||||||
placement="bottom"
|
|
||||||
:on-shown="() => tippyActions.focus()"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="`${t('collection.select_team')}`"
|
|
||||||
class="bg-transparent border-b border-dividerLight select-wrapper"
|
|
||||||
>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-if="environmentType.selectedTeam"
|
|
||||||
:icon="IconUsers"
|
|
||||||
:label="environmentType.selectedTeam.name"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-else
|
|
||||||
:label="`${t('collection.select_team')}`"
|
|
||||||
class="flex-1 !justify-start pr-8 rounded-none"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<template #content="{ hide }">
|
|
||||||
<div
|
|
||||||
ref="tippyActions"
|
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
|
||||||
@keyup.escape="hide()"
|
|
||||||
>
|
|
||||||
<HoppSmartItem
|
|
||||||
v-for="(team, index) in myTeams"
|
|
||||||
:key="`team-${index}`"
|
|
||||||
:label="team.name"
|
|
||||||
:info-icon="
|
|
||||||
team.id === environmentType.selectedTeam?.id
|
|
||||||
? IconDone
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
:active-info-icon="
|
|
||||||
team.id === environmentType.selectedTeam?.id
|
|
||||||
"
|
|
||||||
:icon="IconUsers"
|
|
||||||
@click="
|
|
||||||
() => {
|
|
||||||
updateSelectedTeam(team)
|
|
||||||
hide()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
</HoppSmartIntersection>
|
|
||||||
</HoppSmartTab>
|
|
||||||
</HoppSmartTabs>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { nextTick, ref, watch } from "vue"
|
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
import { onLoggedIn } from "@composables/auth"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import IconDone from "~icons/lucide/check"
|
|
||||||
import IconUsers from "~icons/lucide/users"
|
|
||||||
import { invokeAction } from "~/helpers/actions"
|
|
||||||
|
|
||||||
const t = useI18n()
|
|
||||||
|
|
||||||
type TeamData = GetMyTeamsQuery["myTeams"][number]
|
|
||||||
|
|
||||||
type SelectedTeam = TeamData | undefined
|
|
||||||
|
|
||||||
type EnvironmentTabs = "my-environments" | "team-environments"
|
|
||||||
|
|
||||||
// Template refs
|
|
||||||
const tippyActions = ref<any | null>(null)
|
|
||||||
const selectedEnvironmentTab = ref<EnvironmentTabs>("my-environments")
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
environmentType: {
|
|
||||||
type: "my-environments" | "team-environments"
|
|
||||||
selectedTeam: SelectedTeam
|
|
||||||
}
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: "update-environment-type", tabID: EnvironmentTabs): void
|
|
||||||
(e: "update-selected-team", team: SelectedTeam): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const currentUser = useReadonlyStream(
|
|
||||||
platform.auth.getCurrentUserStream(),
|
|
||||||
platform.auth.getCurrentUser()
|
|
||||||
)
|
|
||||||
|
|
||||||
const adapter = new TeamListAdapter(true)
|
|
||||||
const myTeams = useReadonlyStream(adapter.teamList$, null)
|
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
|
||||||
let teamListFetched = false
|
|
||||||
|
|
||||||
watch(myTeams, (teams) => {
|
|
||||||
if (teams && !teamListFetched) {
|
|
||||||
teamListFetched = true
|
|
||||||
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
|
||||||
const team = teams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
|
||||||
if (team) updateSelectedTeam(team)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => currentUser.value,
|
|
||||||
(user) => {
|
|
||||||
if (!user) {
|
|
||||||
selectedEnvironmentTab.value = "my-environments"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
onLoggedIn(() => {
|
|
||||||
try {
|
|
||||||
adapter.initialize()
|
|
||||||
} catch (e) {}
|
|
||||||
})
|
|
||||||
|
|
||||||
const onTeamSelectIntersect = () => {
|
|
||||||
// Load team data as soon as intersection
|
|
||||||
adapter.fetchList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateEnvironmentType = (tabID: EnvironmentTabs) => {
|
|
||||||
emit("update-environment-type", tabID)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateSelectedTeam = (team: SelectedTeam) => {
|
|
||||||
REMEMBERED_TEAM_ID.value = team?.id
|
|
||||||
emit("update-selected-team", team)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(selectedEnvironmentTab, (newValue: EnvironmentTabs) => {
|
|
||||||
if (newValue === "team-environments" && !currentUser.value) {
|
|
||||||
invokeAction("modals.login.toggle")
|
|
||||||
nextTick(() => (selectedEnvironmentTab.value = "my-environments"))
|
|
||||||
} else updateEnvironmentType(newValue)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto rounded-t bg-primary"
|
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
|
||||||
>
|
>
|
||||||
|
<WorkspaceCurrent :section="t('tab.environments')" />
|
||||||
<tippy
|
<tippy
|
||||||
v-if="environmentType.type === 'my-environments'"
|
v-if="environmentType.type === 'my-environments'"
|
||||||
interactive
|
interactive
|
||||||
@@ -144,7 +145,7 @@
|
|||||||
v-if="!loading && adapterError"
|
v-if="!loading && adapterError"
|
||||||
class="flex flex-col items-center py-4"
|
class="flex flex-col items-center py-4"
|
||||||
>
|
>
|
||||||
<i class="mb-4 material-icons">help_outline</i>
|
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||||
{{ getErrorMessage(adapterError) }}
|
{{ getErrorMessage(adapterError) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -156,11 +157,6 @@
|
|||||||
class="border-b border-dividerLight"
|
class="border-b border-dividerLight"
|
||||||
@edit-environment="editEnvironment('Global')"
|
@edit-environment="editEnvironment('Global')"
|
||||||
/>
|
/>
|
||||||
<EnvironmentsChooseType
|
|
||||||
:environment-type="environmentType"
|
|
||||||
@update-environment-type="updateEnvironmentType"
|
|
||||||
@update-selected-team="updateSelectedTeam"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<EnvironmentsMy v-if="environmentType.type === 'my-environments'" />
|
<EnvironmentsMy v-if="environmentType.type === 'my-environments'" />
|
||||||
<EnvironmentsTeams
|
<EnvironmentsTeams
|
||||||
@@ -184,7 +180,7 @@
|
|||||||
import { computed, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import { isEqual } from "lodash-es"
|
import { isEqual } from "lodash-es"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { Team } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import {
|
import {
|
||||||
@@ -198,12 +194,16 @@ import { GQLError } from "~/helpers/backend/GQLClient"
|
|||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
type EnvironmentType = "my-environments" | "team-environments"
|
type EnvironmentType = "my-environments" | "team-environments"
|
||||||
|
|
||||||
type SelectedTeam = Team | undefined
|
type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined
|
||||||
|
|
||||||
type EnvironmentsChooseType = {
|
type EnvironmentsChooseType = {
|
||||||
type: EnvironmentType
|
type: EnvironmentType
|
||||||
@@ -227,12 +227,11 @@ const currentUser = useReadonlyStream(
|
|||||||
platform.auth.getCurrentUser()
|
platform.auth.getCurrentUser()
|
||||||
)
|
)
|
||||||
|
|
||||||
const updateSelectedTeam = (newSelectedTeam: SelectedTeam) => {
|
// TeamList-Adapter
|
||||||
environmentType.value.selectedTeam = newSelectedTeam
|
const teamListAdapter = new TeamListAdapter(true)
|
||||||
}
|
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||||
const updateEnvironmentType = (newEnvironmentType: EnvironmentType) => {
|
const teamListFetched = ref(false)
|
||||||
environmentType.value.type = newEnvironmentType
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
}
|
|
||||||
|
|
||||||
const adapter = new TeamEnvironmentAdapter(undefined)
|
const adapter = new TeamEnvironmentAdapter(undefined)
|
||||||
const adapterLoading = useReadonlyStream(adapter.loading$, false)
|
const adapterLoading = useReadonlyStream(adapter.loading$, false)
|
||||||
@@ -243,6 +242,45 @@ const loading = computed(
|
|||||||
() => adapterLoading.value && teamEnvironmentList.value.length === 0
|
() => adapterLoading.value && teamEnvironmentList.value.length === 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => myTeams.value,
|
||||||
|
(newTeams) => {
|
||||||
|
if (newTeams && !teamListFetched.value) {
|
||||||
|
teamListFetched.value = true
|
||||||
|
if (REMEMBERED_TEAM_ID.value && currentUser.value) {
|
||||||
|
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
||||||
|
if (team) updateSelectedTeam(team)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => environmentType.value.selectedTeam,
|
||||||
|
(newTeam) => {
|
||||||
|
if (newTeam) {
|
||||||
|
adapter.changeTeamID(newTeam.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const switchToMyEnvironments = () => {
|
||||||
|
environmentType.value.selectedTeam = undefined
|
||||||
|
updateEnvironmentType("my-environments")
|
||||||
|
adapter.changeTeamID(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSelectedTeam = (newSelectedTeam: SelectedTeam) => {
|
||||||
|
if (newSelectedTeam) {
|
||||||
|
environmentType.value.selectedTeam = newSelectedTeam
|
||||||
|
REMEMBERED_TEAM_ID.value = newSelectedTeam.id
|
||||||
|
updateEnvironmentType("team-environments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updateEnvironmentType = (newEnvironmentType: EnvironmentType) => {
|
||||||
|
environmentType.value.type = newEnvironmentType
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => environmentType.value.selectedTeam?.id,
|
() => environmentType.value.selectedTeam?.id,
|
||||||
(newTeamID) => {
|
(newTeamID) => {
|
||||||
@@ -254,7 +292,30 @@ watch(
|
|||||||
() => currentUser.value,
|
() => currentUser.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
if (!newValue) {
|
if (!newValue) {
|
||||||
updateEnvironmentType("my-environments")
|
switchToMyEnvironments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onLoggedIn(() => {
|
||||||
|
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
|
// Used to switch environment type and team when user switch workspace in the global workspace switcher
|
||||||
|
// Check if there is a teamID in the workspace, if yes, switch to team environment and select the team
|
||||||
|
// If there is no teamID, switch to my environment
|
||||||
|
watch(
|
||||||
|
() => workspace.value.teamID,
|
||||||
|
(teamID) => {
|
||||||
|
if (!teamID) {
|
||||||
|
switchToMyEnvironments()
|
||||||
|
} else if (teamID) {
|
||||||
|
const team = myTeams.value?.find((t) => t.id === teamID)
|
||||||
|
if (team) {
|
||||||
|
updateSelectedTeam(team)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
v-if="!loading && adapterError"
|
v-if="!loading && adapterError"
|
||||||
class="flex flex-col items-center py-4"
|
class="flex flex-col items-center py-4"
|
||||||
>
|
>
|
||||||
<i class="mb-4 material-icons">help_outline</i>
|
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||||
{{ getErrorMessage(adapterError) }}
|
{{ getErrorMessage(adapterError) }}
|
||||||
</div>
|
</div>
|
||||||
<EnvironmentsTeamsDetails
|
<EnvironmentsTeamsDetails
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex flex-shrink-0 overflow-x-auto border-b bg-primary border-dividerLight"
|
class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto border-b bg-primary border-dividerLight"
|
||||||
>
|
>
|
||||||
|
<WorkspaceCurrent :section="t('tab.history')" />
|
||||||
|
<div class="flex">
|
||||||
<input
|
<input
|
||||||
v-model="filterText"
|
v-model="filterText"
|
||||||
type="search"
|
type="search"
|
||||||
@@ -56,6 +58,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<details
|
<details
|
||||||
v-for="(
|
v-for="(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="flex flex-col space-y-2">
|
||||||
<div class="sticky top-0 flex-shrink-0 overflow-x-auto">
|
<div class="sticky z-10 top-0 flex-shrink-0 overflow-x-auto">
|
||||||
<input
|
<input
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
type="search"
|
type="search"
|
||||||
|
|||||||
@@ -161,7 +161,6 @@
|
|||||||
ref="saveTippyActions"
|
ref="saveTippyActions"
|
||||||
class="flex flex-col focus:outline-none"
|
class="flex flex-col focus:outline-none"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keyup.c="copyRequestAction.$el.click()"
|
|
||||||
@keyup.s="saveRequestAction.$el.click()"
|
@keyup.s="saveRequestAction.$el.click()"
|
||||||
@keyup.escape="hide()"
|
@keyup.escape="hide()"
|
||||||
>
|
>
|
||||||
@@ -180,7 +179,6 @@
|
|||||||
:label="shareButtonText"
|
:label="shareButtonText"
|
||||||
:icon="copyLinkIcon"
|
:icon="copyLinkIcon"
|
||||||
:loading="fetchingShareLink"
|
:loading="fetchingShareLink"
|
||||||
:shortcut="['C']"
|
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
copyRequest()
|
copyRequest()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
:id="'env'"
|
:id="'env'"
|
||||||
:icon="IconLayers"
|
:icon="IconLayers"
|
||||||
:label="`${t('environment.title')}`"
|
:label="`${t('tab.environments')}`"
|
||||||
>
|
>
|
||||||
<Environments />
|
<Environments />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<button
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="relative flex items-center justify-center cursor-pointer focus:outline-none focus-visible:ring focus-visible:ring-primaryDark"
|
class="relative flex items-center justify-center overflow-visible cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primaryDark"
|
||||||
:class="[`rounded-${rounded}`, `w-${size} h-${size}`]"
|
:class="[`rounded-${rounded}`, `w-${size} h-${size}`]"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
:class="[`rounded-${rounded}`, indicatorStyles]"
|
:class="[`rounded-${rounded}`, indicatorStyles]"
|
||||||
></span>
|
></span>
|
||||||
<!-- w-5 h-5 rounded-lg -->
|
<!-- w-5 h-5 rounded-lg -->
|
||||||
</div>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
<div class="flex flex-col space-y-2">
|
<div class="flex flex-col space-y-2">
|
||||||
<div class="sticky top-0 flex-shrink-0 overflow-x-auto">
|
<div class="sticky z-10 top-0 flex-shrink-0 overflow-x-auto">
|
||||||
<input
|
<input
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
type="search"
|
type="search"
|
||||||
|
|||||||
@@ -13,12 +13,15 @@
|
|||||||
:node-item="rootNode"
|
:node-item="rootNode"
|
||||||
:adapter="adapter as SmartTreeAdapter<T>"
|
:adapter="adapter as SmartTreeAdapter<T>"
|
||||||
>
|
>
|
||||||
<template #default="{ node, toggleChildren, isOpen }">
|
<template
|
||||||
|
#default="{ node, toggleChildren, isOpen, highlightChildren }"
|
||||||
|
>
|
||||||
<slot
|
<slot
|
||||||
name="content"
|
name="content"
|
||||||
:node="node as TreeNode<T>"
|
:node="node as TreeNode<T>"
|
||||||
:toggle-children="toggleChildren as () => void"
|
:toggle-children="toggleChildren as () => void"
|
||||||
:is-open="isOpen as boolean"
|
:is-open="isOpen as boolean"
|
||||||
|
:highlight-children="(id:string|null) => highlightChildren(id)"
|
||||||
></slot>
|
></slot>
|
||||||
</template>
|
</template>
|
||||||
<template #emptyNode="{ node }">
|
<template #emptyNode="{ node }">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
:node="nodeItem"
|
:node="nodeItem"
|
||||||
:toggle-children="toggleNodeChildren"
|
:toggle-children="toggleNodeChildren"
|
||||||
:is-open="isNodeOpen"
|
:is-open="isNodeOpen"
|
||||||
|
:highlight-children="(id:string|null) => highlightNodeChildren(id)"
|
||||||
></slot>
|
></slot>
|
||||||
|
|
||||||
<!-- This is a performance optimization trick -->
|
<!-- This is a performance optimization trick -->
|
||||||
@@ -20,6 +21,9 @@
|
|||||||
<div
|
<div
|
||||||
v-if="childNodes.status === 'loaded' && childNodes.data.length > 0"
|
v-if="childNodes.status === 'loaded' && childNodes.data.length > 0"
|
||||||
class="flex flex-col flex-1 truncate"
|
class="flex flex-col flex-1 truncate"
|
||||||
|
:class="{
|
||||||
|
'bg-divider': highlightNode,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<TreeBranch
|
<TreeBranch
|
||||||
v-for="childNode in childNodes.data"
|
v-for="childNode in childNodes.data"
|
||||||
@@ -28,12 +32,20 @@
|
|||||||
:adapter="adapter"
|
:adapter="adapter"
|
||||||
>
|
>
|
||||||
<!-- The child slot is given a dynamic name in order to not break Volar -->
|
<!-- The child slot is given a dynamic name in order to not break Volar -->
|
||||||
<template #[CHILD_SLOT_NAME]="{ node, toggleChildren, isOpen }">
|
<template
|
||||||
|
#[CHILD_SLOT_NAME]="{
|
||||||
|
node,
|
||||||
|
toggleChildren,
|
||||||
|
isOpen,
|
||||||
|
highlightChildren,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<!-- Casting to help with type checking -->
|
<!-- Casting to help with type checking -->
|
||||||
<slot
|
<slot
|
||||||
:node="node as TreeNode<T>"
|
:node="node as TreeNode<T>"
|
||||||
:toggle-children="toggleChildren as () => void"
|
:toggle-children="toggleChildren as () => void"
|
||||||
:is-open="isOpen as boolean"
|
:is-open="isOpen as boolean"
|
||||||
|
:highlight-children="(id:string|null) => highlightChildren(id) as void"
|
||||||
></slot>
|
></slot>
|
||||||
</template>
|
</template>
|
||||||
<template #emptyNode="{ node }">
|
<template #emptyNode="{ node }">
|
||||||
@@ -87,6 +99,8 @@ const childrenRendered = ref(false)
|
|||||||
const showChildren = ref(false)
|
const showChildren = ref(false)
|
||||||
const isNodeOpen = ref(false)
|
const isNodeOpen = ref(false)
|
||||||
|
|
||||||
|
const highlightNode = ref(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the child nodes from the adapter by passing the node id of the current node
|
* Fetch the child nodes from the adapter by passing the node id of the current node
|
||||||
*/
|
*/
|
||||||
@@ -100,4 +114,12 @@ const toggleNodeChildren = () => {
|
|||||||
showChildren.value = !showChildren.value
|
showChildren.value = !showChildren.value
|
||||||
isNodeOpen.value = !isNodeOpen.value
|
isNodeOpen.value = !isNodeOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const highlightNodeChildren = (id: string | null) => {
|
||||||
|
if (id) {
|
||||||
|
highlightNode.value = true
|
||||||
|
} else {
|
||||||
|
highlightNode.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
:to="to"
|
:to="to"
|
||||||
:exact="exact"
|
:exact="exact"
|
||||||
:blank="blank"
|
:blank="blank"
|
||||||
class="inline-flex items-center px-4 py-2 truncate rounded transition focus:outline-none"
|
class="inline-flex items-center px-4 py-2 truncate transition rounded focus:outline-none"
|
||||||
:class="[
|
:class="[
|
||||||
color
|
color
|
||||||
? `text-${color}-500 hover:text-${color}-600 focus-visible:text-${color}-600`
|
? `text-${color}-500 hover:text-${color}-600 focus-visible:text-${color}-600`
|
||||||
@@ -19,11 +19,13 @@
|
|||||||
class="opacity-75 svg-icons"
|
class="opacity-75 svg-icons"
|
||||||
:class="label ? (reverse ? 'ml-4' : 'mr-4') : ''"
|
:class="label ? (reverse ? 'ml-4' : 'mr-4') : ''"
|
||||||
/>
|
/>
|
||||||
|
<div class="truncate max-w-54">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
|
</div>
|
||||||
</HoppSmartLink>
|
</HoppSmartLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue"
|
import { defineComponent } from "vue"
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
class="svg-icons"
|
class="svg-icons"
|
||||||
:class="label ? 'mr-4 opacity-75' : ''"
|
:class="label ? 'mr-4 opacity-75' : ''"
|
||||||
/>
|
/>
|
||||||
<span class="truncate">
|
<div class="truncate max-w-54">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</span>
|
</div>
|
||||||
</HoppSmartLink>
|
</HoppSmartLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-center -space-x-1">
|
||||||
|
<div
|
||||||
|
v-for="(member, index) in slicedTeamMembers"
|
||||||
|
:key="`member-${index}`"
|
||||||
|
class="inline-flex"
|
||||||
|
>
|
||||||
|
<ProfilePicture
|
||||||
|
v-if="member.user.photoURL"
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:url="member.user.photoURL"
|
||||||
|
:title="getUserName(member)"
|
||||||
|
:alt="getUserName(member)"
|
||||||
|
class="ring-primary ring-2"
|
||||||
|
@click="handleClick()"
|
||||||
|
/>
|
||||||
|
<ProfilePicture
|
||||||
|
v-else
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:title="getUserName(member)"
|
||||||
|
:initial="getUserName(member)"
|
||||||
|
class="ring-primary ring-2"
|
||||||
|
@click="handleClick()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="props.showCount && props.teamMembers.length > maxMembersSoftLimit"
|
||||||
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
|
:title="remainingSlicedMembers"
|
||||||
|
class="z-10 inline-flex items-center justify-center w-5 h-5 rounded-full cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primaryDark font- text-8px text-secondaryDark bg-dividerDark ring-2 ring-primary"
|
||||||
|
tabindex="0"
|
||||||
|
@click="handleClick()"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
teamMembers.length > 0
|
||||||
|
? `+${teamMembers.length - maxMembersSoftLimit}`
|
||||||
|
: ""
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { GetMyTeamsQuery, TeamMember } from "~/helpers/backend/graphql"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
teamMembers: GetMyTeamsQuery["myTeams"][number]["teamMembers"]
|
||||||
|
showCount?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "handle-click"): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const getUserName = (member: TeamMember): string =>
|
||||||
|
member.user.displayName ||
|
||||||
|
member.user.email ||
|
||||||
|
t("profile.default_hopp_displayName")
|
||||||
|
|
||||||
|
const maxMembersSoftLimit = 4
|
||||||
|
const maxMembersHardLimit = 6
|
||||||
|
|
||||||
|
const slicedTeamMembers = computed(() => {
|
||||||
|
if (props.showCount && props.teamMembers.length > maxMembersSoftLimit) {
|
||||||
|
return props.teamMembers.slice(0, maxMembersSoftLimit)
|
||||||
|
} else {
|
||||||
|
return props.teamMembers
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const remainingSlicedMembers = computed(
|
||||||
|
() =>
|
||||||
|
props.teamMembers
|
||||||
|
.slice(maxMembersSoftLimit)
|
||||||
|
.slice(0, maxMembersHardLimit)
|
||||||
|
.map((member) => getUserName(member))
|
||||||
|
.join(`,<br>`) +
|
||||||
|
(props.teamMembers.length - (maxMembersSoftLimit + maxMembersHardLimit) > 0
|
||||||
|
? `,<br>${t("team.more_members", {
|
||||||
|
count:
|
||||||
|
props.teamMembers.length -
|
||||||
|
(maxMembersSoftLimit + maxMembersHardLimit),
|
||||||
|
})}`
|
||||||
|
: ``)
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
emit("handle-click")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -27,31 +27,7 @@
|
|||||||
>
|
>
|
||||||
{{ team.name || t("state.nothing_found") }}
|
{{ team.name || t("state.nothing_found") }}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex mt-2 overflow-hidden -space-x-1">
|
<TeamsMemberStack :team-members="team.teamMembers" class="mt-4" />
|
||||||
<div
|
|
||||||
v-for="(member, index) in team.teamMembers"
|
|
||||||
:key="`member-${index}`"
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="
|
|
||||||
member.user.displayName ||
|
|
||||||
member.user.email ||
|
|
||||||
t('default_hopp_displayName')
|
|
||||||
"
|
|
||||||
class="inline-flex"
|
|
||||||
>
|
|
||||||
<ProfilePicture
|
|
||||||
v-if="member.user.photoURL"
|
|
||||||
:url="member.user.photoURL"
|
|
||||||
:alt="member.user.displayName"
|
|
||||||
class="ring-primary ring-2"
|
|
||||||
/>
|
|
||||||
<ProfilePicture
|
|
||||||
v-else
|
|
||||||
:initial="member.user.displayName || member.user.email"
|
|
||||||
class="ring-primary ring-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!compact" class="flex items-end justify-between flex-shrink-0">
|
<div v-if="!compact" class="flex items-end justify-between flex-shrink-0">
|
||||||
@@ -171,7 +147,7 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { TeamMemberRole } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import {
|
import {
|
||||||
deleteTeam as backendDeleteTeam,
|
deleteTeam as backendDeleteTeam,
|
||||||
leaveTeam,
|
leaveTeam,
|
||||||
@@ -189,18 +165,7 @@ import IconTrash2 from "~icons/lucide/trash-2"
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
team: {
|
team: GetMyTeamsQuery["myTeams"][number]
|
||||||
name: string
|
|
||||||
myRole: TeamMemberRole
|
|
||||||
ownersCount: number
|
|
||||||
teamMembers: Array<{
|
|
||||||
user: {
|
|
||||||
displayName: string
|
|
||||||
photoURL: string | null
|
|
||||||
email: string | null
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
teamID: string
|
teamID: string
|
||||||
compact: boolean
|
compact: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex items-center px-4 py-2 overflow-x-auto border-b whitespace-nowrap border-dividerLight text-tiny text-secondaryLight"
|
||||||
|
>
|
||||||
|
<span class="truncate">
|
||||||
|
{{
|
||||||
|
workspace.type === "personal"
|
||||||
|
? t("workspace.personal")
|
||||||
|
: teamWorkspaceName
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<icon-lucide-chevron-right v-if="section" class="mx-2" />
|
||||||
|
{{ section }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
section?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
|
const teamWorkspaceName = computed(() => {
|
||||||
|
if (workspace.value.type === "team" && workspace.value.teamName) {
|
||||||
|
return workspace.value.teamName
|
||||||
|
} else {
|
||||||
|
return `${t("workspace.team")}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
156
packages/hoppscotch-common/src/components/workspace/Selector.vue
Normal file
156
packages/hoppscotch-common/src/components/workspace/Selector.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<HoppSmartItem
|
||||||
|
label="My Workspace"
|
||||||
|
:icon="IconUser"
|
||||||
|
:info-icon="workspace.type === 'personal' ? IconDone : undefined"
|
||||||
|
:active-info-icon="workspace.type === 'personal'"
|
||||||
|
@click="switchToPersonalWorkspace"
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
|
</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>
|
||||||
|
<div
|
||||||
|
v-if="!loading && myTeams.length === 0"
|
||||||
|
class="flex flex-col items-center justify-center flex-1 p-4 text-secondaryLight"
|
||||||
|
>
|
||||||
|
<span class="mb-4 text-center">
|
||||||
|
{{ t("empty.teams") }}
|
||||||
|
</span>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('team.create_new')"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
:icon="IconPlus"
|
||||||
|
@click="displayModalAdd(true)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!loading" class="flex flex-col">
|
||||||
|
<div
|
||||||
|
class="sticky top-0 z-10 flex items-center justify-between py-2 pl-2 mb-2 -top-2 bg-popover"
|
||||||
|
>
|
||||||
|
<div class="flex items-center px-2 font-semibold text-secondaryLight">
|
||||||
|
{{ t("team.title") }}
|
||||||
|
</div>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconPlus"
|
||||||
|
:title="`${t('team.create_new')}`"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
class="!p-0.75 rounded ml-8"
|
||||||
|
@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-if="!loading && teamListAdapterError"
|
||||||
|
class="flex flex-col items-center py-4"
|
||||||
|
>
|
||||||
|
<i class="mb-4 material-icons">help_outline</i>
|
||||||
|
{{ t("error.something_went_wrong") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TeamsAdd :show="showModalAdd" @hide-modal="displayModalAdd(false)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from "vue"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
|
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 { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
||||||
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
|
import IconDone from "~icons/lucide/check"
|
||||||
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const showModalAdd = ref(false)
|
||||||
|
|
||||||
|
const currentUser = useReadonlyStream(
|
||||||
|
platform.auth.getProbableUserStream(),
|
||||||
|
platform.auth.getProbableUser()
|
||||||
|
)
|
||||||
|
|
||||||
|
const teamListadapter = new TeamListAdapter(true)
|
||||||
|
const myTeams = useReadonlyStream(teamListadapter.teamList$, [])
|
||||||
|
const isTeamListLoading = useReadonlyStream(teamListadapter.loading$, false)
|
||||||
|
const teamListAdapterError = useReadonlyStream(teamListadapter.error$, null)
|
||||||
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
|
const teamListFetched = ref(false)
|
||||||
|
|
||||||
|
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 = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
|
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
|
||||||
|
changeWorkspace({
|
||||||
|
teamID: team.id,
|
||||||
|
teamName: team.name,
|
||||||
|
type: "team",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchToPersonalWorkspace = () => {
|
||||||
|
REMEMBERED_TEAM_ID.value = undefined
|
||||||
|
changeWorkspace({
|
||||||
|
type: "personal",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoggedIn(() => {
|
||||||
|
teamListadapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => currentUser.value,
|
||||||
|
(user) => {
|
||||||
|
if (!user) {
|
||||||
|
switchToPersonalWorkspace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
|
showModalAdd.value = shouldDisplay
|
||||||
|
teamListadapter.fetchList()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
mutation MoveRESTTeamCollection($collectionID: ID!, $parentCollectionID: ID) {
|
||||||
|
moveCollection(
|
||||||
|
collectionID: $collectionID
|
||||||
|
parentCollectionID: $parentCollectionID
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mutation MoveRESTTeamRequest($collectionID: ID!, $requestID: ID!) {
|
||||||
|
moveRequest(destCollID: $collectionID, requestID: $requestID) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
mutation MoveRESTTeamRequest($requestID: ID!, $collectionID: ID!) {
|
|
||||||
moveRequest(requestID: $requestID, destCollID: $collectionID) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mutation UpdateCollectionOrder($collectionID: ID!, $destCollID: ID!) {
|
||||||
|
updateCollectionOrder(collectionID: $collectionID, destCollID: $destCollID)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
mutation UpdateLookUpRequestOrder(
|
||||||
|
$requestID: ID!
|
||||||
|
$nextRequestID: ID
|
||||||
|
$collectionID: ID!
|
||||||
|
) {
|
||||||
|
updateLookUpRequestOrder(
|
||||||
|
requestID: $requestID
|
||||||
|
nextRequestID: $nextRequestID
|
||||||
|
collectionID: $collectionID
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
query GetSingleCollection($collectionID: ID!) {
|
||||||
|
collection(collectionID: $collectionID) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
query GetSingleRequest($requestID: ID!) {
|
||||||
|
request(requestID: $requestID) {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
title
|
||||||
|
request
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
subscription TeamCollectionMoved($teamID: ID!) {
|
||||||
|
teamCollectionMoved(teamID: $teamID) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
subscription TeamCollectionOrderUpdated($teamID: ID!) {
|
||||||
|
collectionOrderUpdated(teamID: $teamID) {
|
||||||
|
collection {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextCollection {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
subscription TeamRequestMoved($teamID: ID!) {
|
||||||
|
requestMoved(teamID: $teamID) {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
request
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
subscription TeamRequestOrderUpdated($teamID: ID!) {
|
||||||
|
requestOrderUpdated(teamID: $teamID) {
|
||||||
|
request {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
request
|
||||||
|
title
|
||||||
|
}
|
||||||
|
nextRequest {
|
||||||
|
id
|
||||||
|
collectionID
|
||||||
|
request
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,16 +12,37 @@ import {
|
|||||||
ImportFromJsonDocument,
|
ImportFromJsonDocument,
|
||||||
ImportFromJsonMutation,
|
ImportFromJsonMutation,
|
||||||
ImportFromJsonMutationVariables,
|
ImportFromJsonMutationVariables,
|
||||||
|
MoveRestTeamCollectionDocument,
|
||||||
|
MoveRestTeamCollectionMutation,
|
||||||
|
MoveRestTeamCollectionMutationVariables,
|
||||||
RenameCollectionDocument,
|
RenameCollectionDocument,
|
||||||
RenameCollectionMutation,
|
RenameCollectionMutation,
|
||||||
RenameCollectionMutationVariables,
|
RenameCollectionMutationVariables,
|
||||||
|
UpdateCollectionOrderDocument,
|
||||||
|
UpdateCollectionOrderMutation,
|
||||||
|
UpdateCollectionOrderMutationVariables,
|
||||||
} from "../graphql"
|
} from "../graphql"
|
||||||
|
|
||||||
type CreateNewRootCollectionError = "team_coll/short_title"
|
type CreateNewRootCollectionError = "team_coll/short_title"
|
||||||
|
|
||||||
type CreateChildCollectionError = "team_coll/short_title"
|
type CreateChildCollectionError = "team_coll/short_title"
|
||||||
|
|
||||||
type RenameCollectionError = "team_coll/short_title"
|
type RenameCollectionError = "team_coll/short_title"
|
||||||
|
|
||||||
type DeleteCollectionError = "team/invalid_coll_id"
|
type DeleteCollectionError = "team/invalid_coll_id"
|
||||||
|
|
||||||
|
type MoveRestTeamCollectionError =
|
||||||
|
| "team/invalid_coll_id"
|
||||||
|
| "team_coll/invalid_target_id"
|
||||||
|
| "team/collection_is_parent_coll"
|
||||||
|
| "team/target_and_destination_collection_are_same"
|
||||||
|
| "team/target_collection_is_already_root_collection"
|
||||||
|
|
||||||
|
type UpdateCollectionOrderError =
|
||||||
|
| "team/invalid_coll_id"
|
||||||
|
| "team/collection_and_next_collection_are_same"
|
||||||
|
| "team/team_collections_have_different_parents"
|
||||||
|
|
||||||
export const createNewRootCollection = (title: string, teamID: string) =>
|
export const createNewRootCollection = (title: string, teamID: string) =>
|
||||||
runMutation<
|
runMutation<
|
||||||
CreateNewRootCollectionMutation,
|
CreateNewRootCollectionMutation,
|
||||||
@@ -66,6 +87,33 @@ export const deleteCollection = (collectionID: string) =>
|
|||||||
collectionID,
|
collectionID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** Can be used to move both collection and folder (considered same in BE) */
|
||||||
|
export const moveRESTTeamCollection = (
|
||||||
|
collectionID: string,
|
||||||
|
destinationCollectionID: string | null
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
MoveRestTeamCollectionMutation,
|
||||||
|
MoveRestTeamCollectionMutationVariables,
|
||||||
|
MoveRestTeamCollectionError
|
||||||
|
>(MoveRestTeamCollectionDocument, {
|
||||||
|
collectionID,
|
||||||
|
parentCollectionID: destinationCollectionID,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateOrderRESTTeamCollection = (
|
||||||
|
collectionID: string,
|
||||||
|
destCollID: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
UpdateCollectionOrderMutation,
|
||||||
|
UpdateCollectionOrderMutationVariables,
|
||||||
|
UpdateCollectionOrderError
|
||||||
|
>(UpdateCollectionOrderDocument, {
|
||||||
|
collectionID,
|
||||||
|
destCollID,
|
||||||
|
})
|
||||||
|
|
||||||
export const importJSONToTeam = (collectionJSON: string, teamID: string) =>
|
export const importJSONToTeam = (collectionJSON: string, teamID: string) =>
|
||||||
runMutation<ImportFromJsonMutation, ImportFromJsonMutationVariables, "">(
|
runMutation<ImportFromJsonMutation, ImportFromJsonMutationVariables, "">(
|
||||||
ImportFromJsonDocument,
|
ImportFromJsonDocument,
|
||||||
|
|||||||
@@ -9,16 +9,27 @@ import {
|
|||||||
MoveRestTeamRequestDocument,
|
MoveRestTeamRequestDocument,
|
||||||
MoveRestTeamRequestMutation,
|
MoveRestTeamRequestMutation,
|
||||||
MoveRestTeamRequestMutationVariables,
|
MoveRestTeamRequestMutationVariables,
|
||||||
|
UpdateLookUpRequestOrderDocument,
|
||||||
|
UpdateLookUpRequestOrderMutation,
|
||||||
|
UpdateLookUpRequestOrderMutationVariables,
|
||||||
UpdateRequestDocument,
|
UpdateRequestDocument,
|
||||||
UpdateRequestMutation,
|
UpdateRequestMutation,
|
||||||
UpdateRequestMutationVariables,
|
UpdateRequestMutationVariables,
|
||||||
} from "../graphql"
|
} from "../graphql"
|
||||||
|
|
||||||
|
type DeleteRequestErrors = "team_req/not_found"
|
||||||
|
|
||||||
type MoveRestTeamRequestErrors =
|
type MoveRestTeamRequestErrors =
|
||||||
| "team_req/not_found"
|
| "team_req/not_found"
|
||||||
| "team_req/invalid_target_id"
|
| "team_req/invalid_target_id"
|
||||||
|
| "team/invalid_coll_id"
|
||||||
|
| "team_req/not_required_role"
|
||||||
|
| "bug/team_req/no_req_id"
|
||||||
|
|
||||||
type DeleteRequestErrors = "team_req/not_found"
|
type UpdateLookUpRequestOrderErrors =
|
||||||
|
| "team_req/not_found"
|
||||||
|
| "team/request_and_next_request_are_same"
|
||||||
|
| "team_req/requests_not_from_same_collection"
|
||||||
|
|
||||||
export const createRequestInCollection = (
|
export const createRequestInCollection = (
|
||||||
collectionID: string,
|
collectionID: string,
|
||||||
@@ -61,12 +72,27 @@ export const deleteTeamRequest = (requestID: string) =>
|
|||||||
requestID,
|
requestID,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const moveRESTTeamRequest = (requestID: string, collectionID: string) =>
|
export const moveRESTTeamRequest = (collectionID: string, requestID: string) =>
|
||||||
runMutation<
|
runMutation<
|
||||||
MoveRestTeamRequestMutation,
|
MoveRestTeamRequestMutation,
|
||||||
MoveRestTeamRequestMutationVariables,
|
MoveRestTeamRequestMutationVariables,
|
||||||
MoveRestTeamRequestErrors
|
MoveRestTeamRequestErrors
|
||||||
>(MoveRestTeamRequestDocument, {
|
>(MoveRestTeamRequestDocument, {
|
||||||
|
collectionID,
|
||||||
requestID,
|
requestID,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateOrderRESTTeamRequest = (
|
||||||
|
requestID: string,
|
||||||
|
nextRequestID: string,
|
||||||
|
collectionID: string
|
||||||
|
) =>
|
||||||
|
runMutation<
|
||||||
|
UpdateLookUpRequestOrderMutation,
|
||||||
|
UpdateLookUpRequestOrderMutationVariables,
|
||||||
|
UpdateLookUpRequestOrderErrors
|
||||||
|
>(UpdateLookUpRequestOrderDocument, {
|
||||||
|
requestID,
|
||||||
|
nextRequestID,
|
||||||
collectionID,
|
collectionID,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { initializeApp } from "firebase/app"
|
|||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { initAnalytics } from "./analytics"
|
import { initAnalytics } from "./analytics"
|
||||||
import { initCollections } from "./collections"
|
import { initCollections } from "./collections"
|
||||||
import { initEnvironments } from "./environments"
|
|
||||||
import { initHistory } from "./history"
|
import { initHistory } from "./history"
|
||||||
import { initSettings } from "./settings"
|
import { initSettings } from "./settings"
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ export function initializeFirebase() {
|
|||||||
initSettings()
|
initSettings()
|
||||||
initCollections()
|
initCollections()
|
||||||
initHistory()
|
initHistory()
|
||||||
initEnvironments()
|
platform.sync.environments.initEnvironmentsSync()
|
||||||
initAnalytics()
|
initAnalytics()
|
||||||
|
|
||||||
initialized = true
|
initialized = true
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ import {
|
|||||||
TeamRequestDeletedDocument,
|
TeamRequestDeletedDocument,
|
||||||
GetCollectionChildrenDocument,
|
GetCollectionChildrenDocument,
|
||||||
GetCollectionRequestsDocument,
|
GetCollectionRequestsDocument,
|
||||||
|
TeamRequestMovedDocument,
|
||||||
|
TeamCollectionMovedDocument,
|
||||||
|
TeamRequestOrderUpdatedDocument,
|
||||||
|
TeamCollectionOrderUpdatedDocument,
|
||||||
} from "~/helpers/backend/graphql"
|
} from "~/helpers/backend/graphql"
|
||||||
|
|
||||||
const TEAMS_BACKEND_PAGE_SIZE = 10
|
const TEAMS_BACKEND_PAGE_SIZE = 10
|
||||||
@@ -201,6 +205,10 @@ export default class NewTeamCollectionAdapter {
|
|||||||
private teamRequestAdded$: Subscription | null
|
private teamRequestAdded$: Subscription | null
|
||||||
private teamRequestUpdated$: Subscription | null
|
private teamRequestUpdated$: Subscription | null
|
||||||
private teamRequestDeleted$: Subscription | null
|
private teamRequestDeleted$: Subscription | null
|
||||||
|
private teamRequestMoved$: Subscription | null
|
||||||
|
private teamCollectionMoved$: Subscription | null
|
||||||
|
private teamRequestOrderUpdated$: Subscription | null
|
||||||
|
private teamCollectionOrderUpdated$: Subscription | null
|
||||||
|
|
||||||
private teamCollectionAddedSub: WSubscription | null
|
private teamCollectionAddedSub: WSubscription | null
|
||||||
private teamCollectionUpdatedSub: WSubscription | null
|
private teamCollectionUpdatedSub: WSubscription | null
|
||||||
@@ -208,6 +216,10 @@ export default class NewTeamCollectionAdapter {
|
|||||||
private teamRequestAddedSub: WSubscription | null
|
private teamRequestAddedSub: WSubscription | null
|
||||||
private teamRequestUpdatedSub: WSubscription | null
|
private teamRequestUpdatedSub: WSubscription | null
|
||||||
private teamRequestDeletedSub: WSubscription | null
|
private teamRequestDeletedSub: WSubscription | null
|
||||||
|
private teamRequestMovedSub: WSubscription | null
|
||||||
|
private teamCollectionMovedSub: WSubscription | null
|
||||||
|
private teamRequestOrderUpdatedSub: WSubscription | null
|
||||||
|
private teamCollectionOrderUpdatedSub: WSubscription | null
|
||||||
|
|
||||||
constructor(private teamID: string | null) {
|
constructor(private teamID: string | null) {
|
||||||
this.collections$ = new BehaviorSubject<TeamCollection[]>([])
|
this.collections$ = new BehaviorSubject<TeamCollection[]>([])
|
||||||
@@ -221,6 +233,10 @@ export default class NewTeamCollectionAdapter {
|
|||||||
this.teamRequestAdded$ = null
|
this.teamRequestAdded$ = null
|
||||||
this.teamRequestDeleted$ = null
|
this.teamRequestDeleted$ = null
|
||||||
this.teamRequestUpdated$ = null
|
this.teamRequestUpdated$ = null
|
||||||
|
this.teamRequestMoved$ = null
|
||||||
|
this.teamCollectionMoved$ = null
|
||||||
|
this.teamRequestOrderUpdated$ = null
|
||||||
|
this.teamCollectionOrderUpdated$ = null
|
||||||
|
|
||||||
this.teamCollectionAddedSub = null
|
this.teamCollectionAddedSub = null
|
||||||
this.teamCollectionUpdatedSub = null
|
this.teamCollectionUpdatedSub = null
|
||||||
@@ -228,6 +244,10 @@ export default class NewTeamCollectionAdapter {
|
|||||||
this.teamRequestAddedSub = null
|
this.teamRequestAddedSub = null
|
||||||
this.teamRequestDeletedSub = null
|
this.teamRequestDeletedSub = null
|
||||||
this.teamRequestUpdatedSub = null
|
this.teamRequestUpdatedSub = null
|
||||||
|
this.teamRequestMovedSub = null
|
||||||
|
this.teamCollectionMovedSub = null
|
||||||
|
this.teamRequestOrderUpdatedSub = null
|
||||||
|
this.teamCollectionOrderUpdatedSub = null
|
||||||
|
|
||||||
if (this.teamID) this.initialize()
|
if (this.teamID) this.initialize()
|
||||||
}
|
}
|
||||||
@@ -255,6 +275,10 @@ export default class NewTeamCollectionAdapter {
|
|||||||
this.teamRequestAdded$?.unsubscribe()
|
this.teamRequestAdded$?.unsubscribe()
|
||||||
this.teamRequestDeleted$?.unsubscribe()
|
this.teamRequestDeleted$?.unsubscribe()
|
||||||
this.teamRequestUpdated$?.unsubscribe()
|
this.teamRequestUpdated$?.unsubscribe()
|
||||||
|
this.teamRequestMoved$?.unsubscribe()
|
||||||
|
this.teamCollectionMoved$?.unsubscribe()
|
||||||
|
this.teamRequestOrderUpdated$?.unsubscribe()
|
||||||
|
this.teamCollectionOrderUpdated$?.unsubscribe()
|
||||||
|
|
||||||
this.teamCollectionAddedSub?.unsubscribe()
|
this.teamCollectionAddedSub?.unsubscribe()
|
||||||
this.teamCollectionUpdatedSub?.unsubscribe()
|
this.teamCollectionUpdatedSub?.unsubscribe()
|
||||||
@@ -262,6 +286,10 @@ export default class NewTeamCollectionAdapter {
|
|||||||
this.teamRequestAddedSub?.unsubscribe()
|
this.teamRequestAddedSub?.unsubscribe()
|
||||||
this.teamRequestDeletedSub?.unsubscribe()
|
this.teamRequestDeletedSub?.unsubscribe()
|
||||||
this.teamRequestUpdatedSub?.unsubscribe()
|
this.teamRequestUpdatedSub?.unsubscribe()
|
||||||
|
this.teamRequestMovedSub?.unsubscribe()
|
||||||
|
this.teamCollectionMovedSub?.unsubscribe()
|
||||||
|
this.teamRequestOrderUpdatedSub?.unsubscribe()
|
||||||
|
this.teamCollectionOrderUpdatedSub?.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initialize() {
|
private async initialize() {
|
||||||
@@ -279,6 +307,9 @@ export default class NewTeamCollectionAdapter {
|
|||||||
collection: TeamCollection,
|
collection: TeamCollection,
|
||||||
parentCollectionID: string | null
|
parentCollectionID: string | null
|
||||||
) {
|
) {
|
||||||
|
// Check if we have it already in the entity tree, if so, we don't need it again
|
||||||
|
if (this.entityIDs.has(`collection-${collection.id}`)) return
|
||||||
|
|
||||||
const tree = this.collections$.value
|
const tree = this.collections$.value
|
||||||
|
|
||||||
if (!parentCollectionID) {
|
if (!parentCollectionID) {
|
||||||
@@ -328,7 +359,7 @@ export default class NewTeamCollectionAdapter {
|
|||||||
this.loadingCollections$.getValue().filter((x) => x !== "root")
|
this.loadingCollections$.getValue().filter((x) => x !== "root")
|
||||||
)
|
)
|
||||||
|
|
||||||
throw new Error(`Error fetching root collections: ${result}`)
|
throw new Error(`Error fetching root collections: ${result.left.error}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
totalCollections.push(
|
totalCollections.push(
|
||||||
@@ -456,6 +487,143 @@ export default class NewTeamCollectionAdapter {
|
|||||||
this.collections$.next(tree)
|
this.collections$.next(tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a request from one collection to another
|
||||||
|
*
|
||||||
|
* @param {string} request - The request to move
|
||||||
|
*/
|
||||||
|
private async moveRequest(request: TeamRequest) {
|
||||||
|
const tree = this.collections$.value
|
||||||
|
|
||||||
|
// Remove the request from the current collection
|
||||||
|
this.removeRequest(request.id)
|
||||||
|
|
||||||
|
const currentRequest = request.request
|
||||||
|
|
||||||
|
if (currentRequest === null || currentRequest === undefined) return
|
||||||
|
|
||||||
|
// Find request in tree, don't attempt if no collection or no requests is found
|
||||||
|
const collection = findCollInTree(tree, request.collectionID)
|
||||||
|
if (!collection) return // Ignore add request
|
||||||
|
|
||||||
|
// Collection is not expanded
|
||||||
|
if (!collection.requests) return
|
||||||
|
|
||||||
|
this.addRequest({
|
||||||
|
id: request.id,
|
||||||
|
collectionID: request.collectionID,
|
||||||
|
request: translateToNewRequest(request.request),
|
||||||
|
title: request.title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a collection from one collection to another or to root
|
||||||
|
*
|
||||||
|
* @param {string} collectionID - The ID of the collection to move
|
||||||
|
*/
|
||||||
|
private async moveCollection(
|
||||||
|
collectionID: string,
|
||||||
|
parentID: string | null,
|
||||||
|
title: string
|
||||||
|
) {
|
||||||
|
// Remove the collection from the current position
|
||||||
|
this.removeCollection(collectionID)
|
||||||
|
|
||||||
|
if (collectionID === null || parentID === undefined) return
|
||||||
|
|
||||||
|
// Expand the parent collection if it is not expanded
|
||||||
|
// so that the old children is also visible when expanding
|
||||||
|
if (parentID) this.expandCollection(parentID)
|
||||||
|
|
||||||
|
this.addCollection(
|
||||||
|
{
|
||||||
|
id: collectionID,
|
||||||
|
children: null,
|
||||||
|
requests: null,
|
||||||
|
title: title,
|
||||||
|
},
|
||||||
|
parentID ?? null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateRequestOrder(
|
||||||
|
dragedRequestID: string,
|
||||||
|
destinationRequestID: string,
|
||||||
|
destinationCollectionID: string
|
||||||
|
) {
|
||||||
|
const tree = this.collections$.value
|
||||||
|
|
||||||
|
// Find collection in tree, don't attempt if no collection is found
|
||||||
|
const collection = findCollInTree(tree, destinationCollectionID)
|
||||||
|
if (!collection) return // Ignore order update
|
||||||
|
|
||||||
|
// Collection is not expanded
|
||||||
|
if (!collection.requests) return
|
||||||
|
|
||||||
|
const requestIndex = collection.requests.findIndex(
|
||||||
|
(req) => req.id === dragedRequestID
|
||||||
|
)
|
||||||
|
const destinationIndex = collection.requests.findIndex(
|
||||||
|
(req) => req.id === destinationRequestID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (requestIndex === -1) return
|
||||||
|
|
||||||
|
const request = collection.requests[requestIndex]
|
||||||
|
|
||||||
|
collection.requests.splice(requestIndex, 1)
|
||||||
|
collection.requests.splice(destinationIndex, 0, request)
|
||||||
|
|
||||||
|
this.collections$.next(tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateCollectionOrder = (
|
||||||
|
collectionID: string,
|
||||||
|
destinationCollectionID: string
|
||||||
|
) => {
|
||||||
|
const tree = this.collections$.value
|
||||||
|
|
||||||
|
// Find collection in tree
|
||||||
|
const coll = findParentOfColl(tree, destinationCollectionID)
|
||||||
|
|
||||||
|
// If the collection has a parent collection and check if it has children
|
||||||
|
if (coll && coll.children) {
|
||||||
|
const collectionIndex = coll.children.findIndex(
|
||||||
|
(coll) => coll.id === collectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
const destinationIndex = coll.children.findIndex(
|
||||||
|
(coll) => coll.id === destinationCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the collection index is not found, don't update
|
||||||
|
if (collectionIndex === -1) return
|
||||||
|
|
||||||
|
const collection = coll.children[collectionIndex]
|
||||||
|
|
||||||
|
coll.children.splice(collectionIndex, 1)
|
||||||
|
coll.children.splice(destinationIndex, 0, collection)
|
||||||
|
} else {
|
||||||
|
// If the collection has no parent collection, it is a root collection
|
||||||
|
const collectionIndex = tree.findIndex((coll) => coll.id === collectionID)
|
||||||
|
|
||||||
|
const destinationIndex = tree.findIndex(
|
||||||
|
(coll) => coll.id === destinationCollectionID
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the collection index is not found, don't update
|
||||||
|
if (collectionIndex === -1) return
|
||||||
|
|
||||||
|
const collection = tree[collectionIndex]
|
||||||
|
|
||||||
|
tree.splice(collectionIndex, 1)
|
||||||
|
tree.splice(destinationIndex, 0, collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collections$.next(tree)
|
||||||
|
}
|
||||||
|
|
||||||
private registerSubscriptions() {
|
private registerSubscriptions() {
|
||||||
if (!this.teamID) return
|
if (!this.teamID) return
|
||||||
|
|
||||||
@@ -575,7 +743,7 @@ export default class NewTeamCollectionAdapter {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
this.teamRequestUpdatedSub = teamReqDeleted
|
this.teamRequestDeletedSub = teamReqDeleted
|
||||||
this.teamRequestDeleted$ = teamReqDeleted$.subscribe((result) => {
|
this.teamRequestDeleted$ = teamReqDeleted$.subscribe((result) => {
|
||||||
if (E.isLeft(result))
|
if (E.isLeft(result))
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -584,6 +752,110 @@ export default class NewTeamCollectionAdapter {
|
|||||||
|
|
||||||
this.removeRequest(result.right.teamRequestDeleted)
|
this.removeRequest(result.right.teamRequestDeleted)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [teamRequestMoved$, teamRequestMoved] = runGQLSubscription({
|
||||||
|
query: TeamRequestMovedDocument,
|
||||||
|
variables: {
|
||||||
|
teamID: this.teamID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
this.teamRequestMovedSub = teamRequestMoved
|
||||||
|
this.teamRequestMoved$ = teamRequestMoved$.subscribe((result) => {
|
||||||
|
if (E.isLeft(result))
|
||||||
|
throw new Error(
|
||||||
|
`Team Request Move Error ${JSON.stringify(result.left)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const { requestMoved } = result.right
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
id: requestMoved.id,
|
||||||
|
collectionID: requestMoved.collectionID,
|
||||||
|
title: requestMoved.title,
|
||||||
|
request: JSON.parse(requestMoved.request),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moveRequest(request)
|
||||||
|
})
|
||||||
|
|
||||||
|
const [teamCollectionMoved$, teamCollectionMoved] = runGQLSubscription({
|
||||||
|
query: TeamCollectionMovedDocument,
|
||||||
|
variables: {
|
||||||
|
teamID: this.teamID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
this.teamCollectionMovedSub = teamCollectionMoved
|
||||||
|
this.teamCollectionMoved$ = teamCollectionMoved$.subscribe((result) => {
|
||||||
|
if (E.isLeft(result))
|
||||||
|
throw new Error(
|
||||||
|
`Team Collection Move Error ${JSON.stringify(result.left)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const { teamCollectionMoved } = result.right
|
||||||
|
const { id, parent, title } = teamCollectionMoved
|
||||||
|
|
||||||
|
const parentID = parent?.id ?? null
|
||||||
|
|
||||||
|
this.moveCollection(id, parentID, title)
|
||||||
|
})
|
||||||
|
|
||||||
|
const [teamRequestOrderUpdated$, teamRequestOrderUpdated] =
|
||||||
|
runGQLSubscription({
|
||||||
|
query: TeamRequestOrderUpdatedDocument,
|
||||||
|
variables: {
|
||||||
|
teamID: this.teamID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
this.teamRequestOrderUpdatedSub = teamRequestOrderUpdated
|
||||||
|
this.teamRequestOrderUpdated$ = teamRequestOrderUpdated$.subscribe(
|
||||||
|
(result) => {
|
||||||
|
if (E.isLeft(result))
|
||||||
|
throw new Error(
|
||||||
|
`Team Request Order Update Error ${JSON.stringify(result.left)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const { requestOrderUpdated } = result.right
|
||||||
|
const { request } = requestOrderUpdated
|
||||||
|
const { nextRequest } = requestOrderUpdated
|
||||||
|
|
||||||
|
if (!nextRequest) return
|
||||||
|
|
||||||
|
this.updateRequestOrder(
|
||||||
|
request.id,
|
||||||
|
nextRequest.id,
|
||||||
|
nextRequest.collectionID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const [teamCollectionOrderUpdated$, teamCollectionOrderUpdated] =
|
||||||
|
runGQLSubscription({
|
||||||
|
query: TeamCollectionOrderUpdatedDocument,
|
||||||
|
variables: {
|
||||||
|
teamID: this.teamID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
this.teamCollectionOrderUpdatedSub = teamCollectionOrderUpdated
|
||||||
|
this.teamCollectionOrderUpdated$ = teamCollectionOrderUpdated$.subscribe(
|
||||||
|
(result) => {
|
||||||
|
if (E.isLeft(result))
|
||||||
|
throw new Error(
|
||||||
|
`Team Collection Order Update Error ${JSON.stringify(result.left)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const { collectionOrderUpdated } = result.right
|
||||||
|
const { collection } = collectionOrderUpdated
|
||||||
|
const { nextCollection } = collectionOrderUpdated
|
||||||
|
|
||||||
|
if (!nextCollection) return
|
||||||
|
|
||||||
|
this.updateCollectionOrder(collection.id, nextCollection.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,11 +11,19 @@ import {
|
|||||||
} from "../backend/graphql"
|
} from "../backend/graphql"
|
||||||
import { TeamEnvironment } from "./TeamEnvironment"
|
import { TeamEnvironment } from "./TeamEnvironment"
|
||||||
|
|
||||||
|
type EntityType = "environment"
|
||||||
|
type EntityID = `${EntityType}-${string}`
|
||||||
export default class TeamEnvironmentAdapter {
|
export default class TeamEnvironmentAdapter {
|
||||||
error$: BehaviorSubject<GQLError<string> | null>
|
error$: BehaviorSubject<GQLError<string> | null>
|
||||||
loading$: BehaviorSubject<boolean>
|
loading$: BehaviorSubject<boolean>
|
||||||
teamEnvironmentList$: BehaviorSubject<TeamEnvironment[]>
|
teamEnvironmentList$: BehaviorSubject<TeamEnvironment[]>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the entity (environments) ids of all the loaded entities.
|
||||||
|
* Used for preventing duplication of data which definitely is not possible (duplication due to network problems etc.)
|
||||||
|
*/
|
||||||
|
private entityIDs: Set<EntityID>
|
||||||
|
|
||||||
private isDispose: boolean
|
private isDispose: boolean
|
||||||
|
|
||||||
private teamEnvironmentCreated$: Subscription | null
|
private teamEnvironmentCreated$: Subscription | null
|
||||||
@@ -32,6 +40,8 @@ export default class TeamEnvironmentAdapter {
|
|||||||
this.teamEnvironmentList$ = new BehaviorSubject<TeamEnvironment[]>([])
|
this.teamEnvironmentList$ = new BehaviorSubject<TeamEnvironment[]>([])
|
||||||
this.isDispose = true
|
this.isDispose = true
|
||||||
|
|
||||||
|
this.entityIDs = new Set()
|
||||||
|
|
||||||
this.teamEnvironmentCreated$ = null
|
this.teamEnvironmentCreated$ = null
|
||||||
this.teamEnvironmentDeleted$ = null
|
this.teamEnvironmentDeleted$ = null
|
||||||
this.teamEnvironmentUpdated$ = null
|
this.teamEnvironmentUpdated$ = null
|
||||||
@@ -56,6 +66,8 @@ export default class TeamEnvironmentAdapter {
|
|||||||
this.teamEnvironmentList$.next([])
|
this.teamEnvironmentList$.next([])
|
||||||
this.loading$.next(false)
|
this.loading$.next(false)
|
||||||
|
|
||||||
|
this.entityIDs.clear()
|
||||||
|
|
||||||
this.unsubscribeSubscriptions()
|
this.unsubscribeSubscriptions()
|
||||||
|
|
||||||
if (this.teamID) this.initialize()
|
if (this.teamID) this.initialize()
|
||||||
@@ -112,16 +124,25 @@ export default class TeamEnvironmentAdapter {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add all the environments to the entity ids list
|
||||||
|
results.forEach((env) => this.entityIDs.add(`environment-${env.id}`))
|
||||||
|
|
||||||
this.teamEnvironmentList$.next(results)
|
this.teamEnvironmentList$.next(results)
|
||||||
|
|
||||||
this.loading$.next(false)
|
this.loading$.next(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private createNewTeamEnvironment(newEnvironment: TeamEnvironment) {
|
private createNewTeamEnvironment(newEnvironment: TeamEnvironment) {
|
||||||
|
// Check if we have it already in the entity tree, if so, we don't need it again
|
||||||
|
if (this.entityIDs.has(`environment-${newEnvironment.id}`)) return
|
||||||
|
|
||||||
const teamEnvironments = this.teamEnvironmentList$.value
|
const teamEnvironments = this.teamEnvironmentList$.value
|
||||||
|
|
||||||
teamEnvironments.push(newEnvironment)
|
teamEnvironments.push(newEnvironment)
|
||||||
|
|
||||||
|
// Add to entity ids set
|
||||||
|
this.entityIDs.add(`environment-${newEnvironment.id}`)
|
||||||
|
|
||||||
this.teamEnvironmentList$.next(teamEnvironments)
|
this.teamEnvironmentList$.next(teamEnvironments)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +150,7 @@ export default class TeamEnvironmentAdapter {
|
|||||||
const teamEnvironments = this.teamEnvironmentList$.value.filter(
|
const teamEnvironments = this.teamEnvironmentList$.value.filter(
|
||||||
({ id }) => id !== envId
|
({ id }) => id !== envId
|
||||||
)
|
)
|
||||||
|
this.entityIDs.delete(`environment-${envId}`)
|
||||||
this.teamEnvironmentList$.next(teamEnvironments)
|
this.teamEnvironmentList$.next(teamEnvironments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export default class TeamListAdapter {
|
|||||||
private timeoutHandle: ReturnType<typeof setTimeout> | null
|
private timeoutHandle: ReturnType<typeof setTimeout> | null
|
||||||
private isDispose: boolean
|
private isDispose: boolean
|
||||||
|
|
||||||
|
public isInitialized: boolean
|
||||||
|
|
||||||
constructor(deferInit = false) {
|
constructor(deferInit = false) {
|
||||||
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
||||||
this.loading$ = new BehaviorSubject<boolean>(false)
|
this.loading$ = new BehaviorSubject<boolean>(false)
|
||||||
@@ -22,6 +24,8 @@ export default class TeamListAdapter {
|
|||||||
this.timeoutHandle = null
|
this.timeoutHandle = null
|
||||||
this.isDispose = false
|
this.isDispose = false
|
||||||
|
|
||||||
|
this.isInitialized = false
|
||||||
|
|
||||||
if (!deferInit) this.initialize()
|
if (!deferInit) this.initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +33,8 @@ export default class TeamListAdapter {
|
|||||||
if (this.timeoutHandle) throw new Error(`Adapter already initialized`)
|
if (this.timeoutHandle) throw new Error(`Adapter already initialized`)
|
||||||
if (this.isDispose) throw new Error(`Adapter has been disposed`)
|
if (this.isDispose) throw new Error(`Adapter has been disposed`)
|
||||||
|
|
||||||
|
this.isInitialized = true
|
||||||
|
|
||||||
const func = async () => {
|
const func = async () => {
|
||||||
await this.fetchList()
|
await this.fetchList()
|
||||||
|
|
||||||
@@ -44,6 +50,7 @@ export default class TeamListAdapter {
|
|||||||
this.isDispose = true
|
this.isDispose = true
|
||||||
clearTimeout(this.timeoutHandle as any)
|
clearTimeout(this.timeoutHandle as any)
|
||||||
this.timeoutHandle = null
|
this.timeoutHandle = null
|
||||||
|
this.isInitialized = false
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchList() {
|
async fetchList() {
|
||||||
|
|||||||
@@ -186,6 +186,133 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
moveFolder(
|
||||||
|
{ state }: RESTCollectionStoreType,
|
||||||
|
{ path, destinationPath }: { path: string; destinationPath: string | null }
|
||||||
|
) {
|
||||||
|
const newState = state
|
||||||
|
|
||||||
|
// Move the folder to the root
|
||||||
|
if (destinationPath === null) {
|
||||||
|
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||||
|
|
||||||
|
if (indexPaths.length === 0) {
|
||||||
|
console.log("Given path too short. Skipping request.")
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderIndex = indexPaths.pop() as number
|
||||||
|
|
||||||
|
const containingFolder = navigateToFolderWithIndexPath(
|
||||||
|
newState,
|
||||||
|
indexPaths
|
||||||
|
)
|
||||||
|
if (containingFolder === null) {
|
||||||
|
console.error(
|
||||||
|
`The folder to move is already in the root. Skipping request to move folder.`
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const theFolder = containingFolder.folders.splice(folderIndex, 1)
|
||||||
|
newState.push(theFolder[0] as HoppCollection<HoppRESTRequest>)
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: newState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||||
|
|
||||||
|
const destinationIndexPaths = destinationPath
|
||||||
|
.split("/")
|
||||||
|
.map((x) => parseInt(x))
|
||||||
|
|
||||||
|
if (indexPaths.length === 0 || destinationIndexPaths.length === 0) {
|
||||||
|
console.error(
|
||||||
|
`Given path is too short. Skipping request to move folder '${path}' to destination '${destinationPath}'.`
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = navigateToFolderWithIndexPath(
|
||||||
|
newState,
|
||||||
|
destinationIndexPaths
|
||||||
|
)
|
||||||
|
if (target === null) {
|
||||||
|
console.error(
|
||||||
|
`Could not resolve destination path '${destinationPath}'. Skipping moveFolder dispatch.`
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderIndex = indexPaths.pop() as number
|
||||||
|
|
||||||
|
const containingFolder = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||||
|
// We are moving a folder from the root
|
||||||
|
if (containingFolder === null) {
|
||||||
|
const theFolder = newState.splice(folderIndex, 1)
|
||||||
|
|
||||||
|
target.folders.push(theFolder[0])
|
||||||
|
} else {
|
||||||
|
const theFolder = containingFolder.folders.splice(folderIndex, 1)
|
||||||
|
|
||||||
|
target.folders.push(theFolder[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return { state: newState }
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCollectionOrder(
|
||||||
|
{ state }: RESTCollectionStoreType,
|
||||||
|
{
|
||||||
|
collectionIndex,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
}: {
|
||||||
|
collectionIndex: string
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const newState = state
|
||||||
|
|
||||||
|
const indexPaths = collectionIndex.split("/").map((x) => parseInt(x))
|
||||||
|
|
||||||
|
const destinationIndexPaths = destinationCollectionIndex
|
||||||
|
.split("/")
|
||||||
|
.map((x) => parseInt(x))
|
||||||
|
|
||||||
|
if (indexPaths.length === 0 || destinationIndexPaths.length === 0) {
|
||||||
|
console.log("Given path too short. Skipping request.")
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderIndex = indexPaths.pop() as number
|
||||||
|
const destinationFolderIndex = destinationIndexPaths.pop() as number
|
||||||
|
|
||||||
|
const containingFolder = navigateToFolderWithIndexPath(
|
||||||
|
newState,
|
||||||
|
destinationIndexPaths
|
||||||
|
)
|
||||||
|
|
||||||
|
if (containingFolder === null) {
|
||||||
|
const [removed] = newState.splice(folderIndex, 1)
|
||||||
|
|
||||||
|
newState.splice(destinationFolderIndex, 0, removed)
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: newState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [removed] = containingFolder.folders.splice(folderIndex, 1)
|
||||||
|
|
||||||
|
containingFolder.folders.splice(destinationFolderIndex, 0, removed)
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: newState,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
editRequest(
|
editRequest(
|
||||||
{ state }: RESTCollectionStoreType,
|
{ state }: RESTCollectionStoreType,
|
||||||
{
|
{
|
||||||
@@ -286,6 +413,11 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
|
|
||||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||||
|
|
||||||
|
if (indexPaths.length === 0) {
|
||||||
|
console.log("Given path too short. Skipping request.")
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||||
|
|
||||||
if (targetLocation === null) {
|
if (targetLocation === null) {
|
||||||
@@ -315,6 +447,47 @@ const restCollectionDispatchers = defineDispatchers({
|
|||||||
state: newState,
|
state: newState,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateRequestOrder(
|
||||||
|
{ state }: RESTCollectionStoreType,
|
||||||
|
{
|
||||||
|
requestIndex,
|
||||||
|
destinationRequestIndex,
|
||||||
|
destinationCollectionPath,
|
||||||
|
}: {
|
||||||
|
requestIndex: number
|
||||||
|
destinationRequestIndex: number
|
||||||
|
destinationCollectionPath: string
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const newState = state
|
||||||
|
|
||||||
|
const indexPaths = destinationCollectionPath
|
||||||
|
.split("/")
|
||||||
|
.map((x) => parseInt(x))
|
||||||
|
|
||||||
|
if (indexPaths.length === 0) {
|
||||||
|
console.log("Given path too short. Skipping request.")
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||||
|
|
||||||
|
if (targetLocation === null) {
|
||||||
|
console.log(
|
||||||
|
`Could not resolve path '${destinationCollectionPath}'. Ignoring reorderRequest dispatch.`
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [removed] = targetLocation.requests.splice(requestIndex, 1)
|
||||||
|
|
||||||
|
targetLocation.requests.splice(destinationRequestIndex, 0, removed)
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: newState,
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const gqlCollectionDispatchers = defineDispatchers({
|
const gqlCollectionDispatchers = defineDispatchers({
|
||||||
@@ -691,6 +864,16 @@ export function removeRESTFolder(path: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function moveRESTFolder(path: string, destinationPath: string | null) {
|
||||||
|
restCollectionStore.dispatch({
|
||||||
|
dispatcher: "moveFolder",
|
||||||
|
payload: {
|
||||||
|
path,
|
||||||
|
destinationPath,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function editRESTRequest(
|
export function editRESTRequest(
|
||||||
path: string,
|
path: string,
|
||||||
requestIndex: number,
|
requestIndex: number,
|
||||||
@@ -757,6 +940,34 @@ export function moveRESTRequest(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateRESTRequestOrder(
|
||||||
|
requestIndex: number,
|
||||||
|
destinationRequestIndex: number,
|
||||||
|
destinationCollectionPath: string
|
||||||
|
) {
|
||||||
|
restCollectionStore.dispatch({
|
||||||
|
dispatcher: "updateRequestOrder",
|
||||||
|
payload: {
|
||||||
|
requestIndex,
|
||||||
|
destinationRequestIndex,
|
||||||
|
destinationCollectionPath,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateRESTCollectionOrder(
|
||||||
|
collectionIndex: string,
|
||||||
|
destinationCollectionIndex: string
|
||||||
|
) {
|
||||||
|
restCollectionStore.dispatch({
|
||||||
|
dispatcher: "updateCollectionOrder",
|
||||||
|
payload: {
|
||||||
|
collectionIndex,
|
||||||
|
destinationCollectionIndex,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function setGraphqlCollections(
|
export function setGraphqlCollections(
|
||||||
entries: HoppCollection<HoppGQLRequest>[]
|
entries: HoppCollection<HoppGQLRequest>[]
|
||||||
) {
|
) {
|
||||||
|
|||||||
67
packages/hoppscotch-common/src/newstore/workspace.ts
Normal file
67
packages/hoppscotch-common/src/newstore/workspace.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { distinctUntilChanged, pluck } from "rxjs"
|
||||||
|
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||||
|
|
||||||
|
type Workspace =
|
||||||
|
| { type: "personal" }
|
||||||
|
| { type: "team"; teamID: string; teamName: string }
|
||||||
|
|
||||||
|
type WorkspaceState = {
|
||||||
|
workspace: Workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: WorkspaceState = {
|
||||||
|
workspace: {
|
||||||
|
type: "personal",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispatchers = defineDispatchers({
|
||||||
|
changeWorkspace(_, { workspace }: { workspace: Workspace }) {
|
||||||
|
return {
|
||||||
|
workspace,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateWorkspaceTeamName(
|
||||||
|
_,
|
||||||
|
{ workspace, newTeamName }: { workspace: Workspace; newTeamName: string }
|
||||||
|
) {
|
||||||
|
if (workspace.type === "team") {
|
||||||
|
return {
|
||||||
|
workspace: {
|
||||||
|
...workspace,
|
||||||
|
teamName: newTeamName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
workspace,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const hoppWorkspaceStore = new DispatchingStore(
|
||||||
|
initialState,
|
||||||
|
dispatchers
|
||||||
|
)
|
||||||
|
|
||||||
|
export const workspaceStatus$ = hoppWorkspaceStore.subject$.pipe(
|
||||||
|
pluck("workspace"),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
|
||||||
|
export function changeWorkspace(workspace: Workspace) {
|
||||||
|
hoppWorkspaceStore.dispatch({
|
||||||
|
dispatcher: "changeWorkspace",
|
||||||
|
payload: { workspace },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateWorkspaceTeamName(
|
||||||
|
workspace: Workspace,
|
||||||
|
newTeamName: string
|
||||||
|
) {
|
||||||
|
hoppWorkspaceStore.dispatch({
|
||||||
|
dispatcher: "updateWorkspaceTeamName",
|
||||||
|
payload: { workspace, newTeamName },
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
v-if="invalidLink"
|
v-if="invalidLink"
|
||||||
class="flex flex-col items-center justify-center flex-1"
|
class="flex flex-col items-center justify-center flex-1"
|
||||||
>
|
>
|
||||||
<i class="pb-2 opacity-75 material-icons">error_outline</i>
|
<icon-lucide-alert-triangle class="mb-2 opacity-75 svg-icons" />
|
||||||
<h1 class="text-center heading">
|
<h1 class="text-center heading">
|
||||||
{{ t("team.invalid_invite_link") }}
|
{{ t("team.invalid_invite_link") }}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
v-if="!inviteDetails.loading && E.isLeft(inviteDetails.data)"
|
v-if="!inviteDetails.loading && E.isLeft(inviteDetails.data)"
|
||||||
class="flex flex-col items-center p-4"
|
class="flex flex-col items-center p-4"
|
||||||
>
|
>
|
||||||
<i class="mb-4 material-icons">error_outline</i>
|
<icon-lucide-alert-triangle class="mb-4 svg-icons" />
|
||||||
<p>
|
<p>
|
||||||
{{ getErrorMessage(inviteDetails.data.left) }}
|
{{ getErrorMessage(inviteDetails.data.left) }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
v-if="invalidLink"
|
v-if="invalidLink"
|
||||||
class="flex flex-col items-center justify-center flex-1"
|
class="flex flex-col items-center justify-center flex-1"
|
||||||
>
|
>
|
||||||
<i class="pb-2 opacity-75 material-icons">error_outline</i>
|
<icon-lucide-alert-triangle class="mb-2 opacity-75 svg-icons" />
|
||||||
<h1 class="text-center heading">
|
<h1 class="text-center heading">
|
||||||
{{ t("error.invalid_link") }}
|
{{ t("error.invalid_link") }}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
v-if="!shortcodeDetails.loading && E.isLeft(shortcodeDetails.data)"
|
v-if="!shortcodeDetails.loading && E.isLeft(shortcodeDetails.data)"
|
||||||
class="flex flex-col items-center p-4"
|
class="flex flex-col items-center p-4"
|
||||||
>
|
>
|
||||||
<i class="pb-2 opacity-75 material-icons">error_outline</i>
|
<icon-lucide-alert-triangle class="mb-2 opacity-75 svg-icons" />
|
||||||
<h1 class="text-center heading">
|
<h1 class="text-center heading">
|
||||||
{{ t("error.invalid_link") }}
|
{{ t("error.invalid_link") }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
3
packages/hoppscotch-common/src/platform/environments.ts
Normal file
3
packages/hoppscotch-common/src/platform/environments.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export type EnvironmentsPlatformDef = {
|
||||||
|
initEnvironmentsSync: () => void
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import { AuthPlatformDef } from "./auth"
|
import { AuthPlatformDef } from "./auth"
|
||||||
import { UIPlatformDef } from "./ui"
|
import { UIPlatformDef } from "./ui"
|
||||||
|
import { EnvironmentsPlatformDef } from "./environments"
|
||||||
|
|
||||||
export type PlatformDef = {
|
export type PlatformDef = {
|
||||||
ui?: UIPlatformDef
|
ui?: UIPlatformDef
|
||||||
auth: AuthPlatformDef
|
auth: AuthPlatformDef
|
||||||
|
sync: {
|
||||||
|
environments: EnvironmentsPlatformDef
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export let platform: PlatformDef
|
export let platform: PlatformDef
|
||||||
|
|||||||
@@ -19,14 +19,27 @@
|
|||||||
'border border-accent hover:border-accentDark focus-visible:border-accentDark':
|
'border border-accent hover:border-accentDark focus-visible:border-accentDark':
|
||||||
outline,
|
outline,
|
||||||
},
|
},
|
||||||
]" :disabled="disabled" :tabindex="loading ? '-1' : '0'" role="button">
|
]"
|
||||||
<span class="inline-flex items-center justify-center whitespace-nowrap"
|
:disabled="disabled"
|
||||||
:class="[{ 'flex-row-reverse': reverse }, { 'opacity-50': loading }]">
|
:tabindex="loading ? '-1' : '0'"
|
||||||
<component :is="icon" v-if="icon" class="svg-icons" :class="[
|
role="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center justify-center whitespace-nowrap"
|
||||||
|
:class="[{ 'flex-row-reverse': reverse }, { 'opacity-50': loading }]"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="icon"
|
||||||
|
v-if="icon"
|
||||||
|
class="svg-icons"
|
||||||
|
:class="[
|
||||||
{ '!text-2xl': large },
|
{ '!text-2xl': large },
|
||||||
label ? (reverse ? 'ml-2' : 'mr-2') : '',
|
label ? (reverse ? 'ml-2' : 'mr-2') : '',
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
|
<div class="truncate max-w-54">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
|
</div>
|
||||||
<div v-if="shortcut.length" class="<sm:hidden">
|
<div v-if="shortcut.length" class="<sm:hidden">
|
||||||
<kbd v-for="(key, index) in shortcut" :key="`key-${index}`"
|
<kbd v-for="(key, index) in shortcut" :key="`key-${index}`"
|
||||||
class="shortcut-key !bg-accentDark !border-accentLight">
|
class="shortcut-key !bg-accentDark !border-accentLight">
|
||||||
|
|||||||
@@ -19,14 +19,28 @@
|
|||||||
'bg-primaryLight hover:bg-primaryDark focus-visible:bg-primaryDark':
|
'bg-primaryLight hover:bg-primaryDark focus-visible:bg-primaryDark':
|
||||||
filled,
|
filled,
|
||||||
},
|
},
|
||||||
]" :disabled="disabled" :tabindex="loading ? '-1' : '0'" role="button">
|
]"
|
||||||
<span v-if="!loading" class="inline-flex items-center justify-center whitespace-nowrap"
|
:disabled="disabled"
|
||||||
:class="{ 'flex-row-reverse': reverse }">
|
:tabindex="loading ? '-1' : '0'"
|
||||||
<component :is="icon" v-if="icon" class="svg-icons" :class="[
|
role="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="!loading"
|
||||||
|
class="inline-flex items-center justify-center whitespace-nowrap"
|
||||||
|
:class="{ 'flex-row-reverse': reverse }"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="icon"
|
||||||
|
v-if="icon"
|
||||||
|
class="svg-icons"
|
||||||
|
:class="[
|
||||||
{ '!text-2xl': large },
|
{ '!text-2xl': large },
|
||||||
label ? (reverse ? 'ml-2' : 'mr-2') : '',
|
label ? (reverse ? 'ml-2' : 'mr-2') : '',
|
||||||
]" />
|
]"
|
||||||
|
/>
|
||||||
|
<div class="truncate max-w-54">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
|
</div>
|
||||||
<div v-if="shortcut.length" class="<sm:hidden">
|
<div v-if="shortcut.length" class="<sm:hidden">
|
||||||
<kbd v-for="(key, index) in shortcut" :key="`key-${index}`" class="shortcut-key !bg-inherit">
|
<kbd v-for="(key, index) in shortcut" :key="`key-${index}`" class="shortcut-key !bg-inherit">
|
||||||
{{ key }}
|
{{ key }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="chip">
|
<span class="chip">
|
||||||
<component :is="IconFile" class="opacity-75 svg-icons" />
|
<component :is="IconFile" class="opacity-75 svg-icons" />
|
||||||
<span class="px-2 truncate max-w-32"><slot></slot></span>
|
<span class="px-2 truncate max-w-54"><slot></slot></span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -19,15 +19,19 @@
|
|||||||
</span>
|
</span>
|
||||||
<HoppSmartSpinner v-else class="mr-4 text-secondaryDark" />
|
<HoppSmartSpinner v-else class="mr-4 text-secondaryDark" />
|
||||||
<div class="inline-flex items-start flex-1 truncate" :class="{ 'flex-col': description }">
|
<div class="inline-flex items-start flex-1 truncate" :class="{ 'flex-col': description }">
|
||||||
<div class="font-semibold truncate">
|
<div class="font-semibold truncate max-w-54">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</div>
|
</div>
|
||||||
<p v-if="description" class="my-2 text-left text-secondaryLight">
|
<p v-if="description" class="my-2 text-left text-secondaryLight">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<component :is="infoIcon" v-if="infoIcon" class="items-center self-center ml-4 svg-icons"
|
<component
|
||||||
:class="{ 'text-accent': activeInfoIcon }" />
|
:is="infoIcon"
|
||||||
|
v-if="infoIcon"
|
||||||
|
class="items-center self-center ml-6 -mr-2 svg-icons"
|
||||||
|
:class="{ 'text-accent': activeInfoIcon }"
|
||||||
|
/>
|
||||||
<div v-if="shortcut.length" class="ml-4 <sm:hidden font-medium">
|
<div v-if="shortcut.length" class="ml-4 <sm:hidden font-medium">
|
||||||
<kbd v-for="(key, index) in shortcut" :key="`key-${index}`" class="-mr-2 shortcut-key">
|
<kbd v-for="(key, index) in shortcut" :key="`key-${index}`" class="-mr-2 shortcut-key">
|
||||||
{{ key }}
|
{{ key }}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import path from "path"
|
|||||||
import Icons from "unplugin-icons/vite"
|
import Icons from "unplugin-icons/vite"
|
||||||
import { defineConfig } from "vite"
|
import { defineConfig } from "vite"
|
||||||
import WindiCSS from "vite-plugin-windicss"
|
import WindiCSS from "vite-plugin-windicss"
|
||||||
|
import { VitePluginFonts } from "vite-plugin-fonts"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -19,6 +20,15 @@ export default defineConfig({
|
|||||||
Icons({
|
Icons({
|
||||||
compiler: "vue3"
|
compiler: "vue3"
|
||||||
}),
|
}),
|
||||||
|
VitePluginFonts({
|
||||||
|
google: {
|
||||||
|
families: [
|
||||||
|
"Inter:wght@400;500;600;700;800",
|
||||||
|
"Roboto+Mono:wght@400;500",
|
||||||
|
"Material+Icons",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
], // to process SFC
|
], // to process SFC
|
||||||
build: {
|
build: {
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export default defineConfig({
|
|||||||
lowerSecondaryStickyFold: "var(--lower-secondary-sticky-fold)",
|
lowerSecondaryStickyFold: "var(--lower-secondary-sticky-fold)",
|
||||||
lowerTertiaryStickyFold: "var(--lower-tertiary-sticky-fold)",
|
lowerTertiaryStickyFold: "var(--lower-tertiary-sticky-fold)",
|
||||||
sidebarPrimaryStickyFold: "var(--sidebar-primary-sticky-fold)",
|
sidebarPrimaryStickyFold: "var(--sidebar-primary-sticky-fold)",
|
||||||
|
sidebarSecondaryStickyFold: "var(--line-height-body)",
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
primary: "var(--primary-color)",
|
primary: "var(--primary-color)",
|
||||||
|
|||||||
@@ -6,14 +6,18 @@ import {
|
|||||||
onSnapshot,
|
onSnapshot,
|
||||||
setDoc,
|
setDoc,
|
||||||
} from "firebase/firestore"
|
} from "firebase/firestore"
|
||||||
import { platform } from "~/platform"
|
import { def as platformAuth } from "./firebase/auth"
|
||||||
import {
|
import {
|
||||||
environments$,
|
environments$,
|
||||||
globalEnv$,
|
globalEnv$,
|
||||||
replaceEnvironments,
|
replaceEnvironments,
|
||||||
setGlobalEnvVariables,
|
setGlobalEnvVariables,
|
||||||
} from "~/newstore/environments"
|
} from "@hoppscotch/common/newstore/environments"
|
||||||
import { getSettingSubject, settingsStore } from "~/newstore/settings"
|
import {
|
||||||
|
getSettingSubject,
|
||||||
|
settingsStore,
|
||||||
|
} from "@hoppscotch/common/newstore/settings"
|
||||||
|
import { EnvironmentsPlatformDef } from "@hoppscotch/common/platform/environments"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used locally to prevent infinite loop when environment sync update
|
* Used locally to prevent infinite loop when environment sync update
|
||||||
@@ -32,7 +36,7 @@ let loadedEnvironments = false
|
|||||||
let loadedGlobals = true
|
let loadedGlobals = true
|
||||||
|
|
||||||
async function writeEnvironments(environment: Environment[]) {
|
async function writeEnvironments(environment: Environment[]) {
|
||||||
const currentUser = platform.auth.getCurrentUser()
|
const currentUser = platformAuth.getCurrentUser()
|
||||||
|
|
||||||
if (currentUser === null)
|
if (currentUser === null)
|
||||||
throw new Error("Cannot write environments when signed out")
|
throw new Error("Cannot write environments when signed out")
|
||||||
@@ -57,7 +61,7 @@ async function writeEnvironments(environment: Environment[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function writeGlobalEnvironment(variables: Environment["variables"]) {
|
async function writeGlobalEnvironment(variables: Environment["variables"]) {
|
||||||
const currentUser = platform.auth.getCurrentUser()
|
const currentUser = platformAuth.getCurrentUser()
|
||||||
|
|
||||||
if (currentUser === null)
|
if (currentUser === null)
|
||||||
throw new Error("Cannot write global environment when signed out")
|
throw new Error("Cannot write global environment when signed out")
|
||||||
@@ -81,11 +85,11 @@ async function writeGlobalEnvironment(variables: Environment["variables"]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initEnvironments() {
|
export function initEnvironmentsSync() {
|
||||||
const currentUser$ = platform.auth.getCurrentUserStream()
|
const currentUser$ = platformAuth.getCurrentUserStream()
|
||||||
|
|
||||||
const envListenSub = environments$.subscribe((envs) => {
|
const envListenSub = environments$.subscribe((envs) => {
|
||||||
const currentUser = platform.auth.getCurrentUser()
|
const currentUser = platformAuth.getCurrentUser()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentUser &&
|
currentUser &&
|
||||||
@@ -97,7 +101,7 @@ export function initEnvironments() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const globalListenSub = globalEnv$.subscribe((vars) => {
|
const globalListenSub = globalEnv$.subscribe((vars) => {
|
||||||
const currentUser = platform.auth.getCurrentUser()
|
const currentUser = platformAuth.getCurrentUser()
|
||||||
|
|
||||||
if (currentUser && settingsStore.value.syncEnvironments && loadedGlobals) {
|
if (currentUser && settingsStore.value.syncEnvironments && loadedGlobals) {
|
||||||
writeGlobalEnvironment(vars)
|
writeGlobalEnvironment(vars)
|
||||||
@@ -171,8 +175,12 @@ export function initEnvironments() {
|
|||||||
globalListenSub.unsubscribe()
|
globalListenSub.unsubscribe()
|
||||||
currentUserSub.unsubscribe()
|
currentUserSub.unsubscribe()
|
||||||
|
|
||||||
initEnvironments()
|
initEnvironmentsSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const def: EnvironmentsPlatformDef = {
|
||||||
|
initEnvironmentsSync,
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import { createHoppApp } from "@hoppscotch/common"
|
import { createHoppApp } from "@hoppscotch/common"
|
||||||
import { def as authDef } from "./firebase/auth"
|
import { def as authDef } from "./firebase/auth"
|
||||||
|
import { def as envDef } from "./environments"
|
||||||
|
|
||||||
createHoppApp("#app", {
|
createHoppApp("#app", {
|
||||||
auth: authDef,
|
auth: authDef,
|
||||||
|
sync: {
|
||||||
|
environments: envDef,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
72
pnpm-lock.yaml
generated
72
pnpm-lock.yaml
generated
@@ -58,6 +58,7 @@ importers:
|
|||||||
'@relmify/jest-fp-ts': ^2.0.2
|
'@relmify/jest-fp-ts': ^2.0.2
|
||||||
'@types/argon2': ^0.15.0
|
'@types/argon2': ^0.15.0
|
||||||
'@types/bcrypt': ^5.0.0
|
'@types/bcrypt': ^5.0.0
|
||||||
|
'@types/cookie': ^0.5.1
|
||||||
'@types/cookie-parser': ^1.4.3
|
'@types/cookie-parser': ^1.4.3
|
||||||
'@types/express': ^4.17.14
|
'@types/express': ^4.17.14
|
||||||
'@types/jest': ^29.4.0
|
'@types/jest': ^29.4.0
|
||||||
@@ -74,6 +75,7 @@ importers:
|
|||||||
apollo-server-plugin-base: ^3.7.1
|
apollo-server-plugin-base: ^3.7.1
|
||||||
argon2: ^0.30.3
|
argon2: ^0.30.3
|
||||||
bcrypt: ^5.1.0
|
bcrypt: ^5.1.0
|
||||||
|
cookie: ^0.5.0
|
||||||
cookie-parser: ^1.4.6
|
cookie-parser: ^1.4.6
|
||||||
eslint: ^8.29.0
|
eslint: ^8.29.0
|
||||||
eslint-config-prettier: ^8.5.0
|
eslint-config-prettier: ^8.5.0
|
||||||
@@ -122,6 +124,7 @@ importers:
|
|||||||
apollo-server-plugin-base: 3.7.1_graphql@15.8.0
|
apollo-server-plugin-base: 3.7.1_graphql@15.8.0
|
||||||
argon2: 0.30.3
|
argon2: 0.30.3
|
||||||
bcrypt: 5.1.0
|
bcrypt: 5.1.0
|
||||||
|
cookie: 0.5.0
|
||||||
cookie-parser: 1.4.6
|
cookie-parser: 1.4.6
|
||||||
express: 4.18.2
|
express: 4.18.2
|
||||||
fp-ts: 2.13.1
|
fp-ts: 2.13.1
|
||||||
@@ -150,6 +153,7 @@ importers:
|
|||||||
'@relmify/jest-fp-ts': 2.0.2_fp-ts@2.13.1+io-ts@2.2.16
|
'@relmify/jest-fp-ts': 2.0.2_fp-ts@2.13.1+io-ts@2.2.16
|
||||||
'@types/argon2': 0.15.0
|
'@types/argon2': 0.15.0
|
||||||
'@types/bcrypt': 5.0.0
|
'@types/bcrypt': 5.0.0
|
||||||
|
'@types/cookie': 0.5.1
|
||||||
'@types/cookie-parser': 1.4.3
|
'@types/cookie-parser': 1.4.3
|
||||||
'@types/express': 4.17.14
|
'@types/express': 4.17.14
|
||||||
'@types/jest': 29.4.0
|
'@types/jest': 29.4.0
|
||||||
@@ -4670,12 +4674,12 @@ packages:
|
|||||||
extract-files: 11.0.0
|
extract-files: 11.0.0
|
||||||
graphql: 15.8.0
|
graphql: 15.8.0
|
||||||
graphql-ws: 5.9.1_graphql@15.8.0
|
graphql-ws: 5.9.1_graphql@15.8.0
|
||||||
isomorphic-ws: 5.0.0_ws@8.12.0
|
isomorphic-ws: 5.0.0_ws@8.12.1
|
||||||
meros: 1.2.0
|
meros: 1.2.0
|
||||||
sync-fetch: 0.4.1
|
sync-fetch: 0.4.1
|
||||||
tslib: 2.4.0
|
tslib: 2.4.0
|
||||||
value-or-promise: 1.0.11
|
value-or-promise: 1.0.11
|
||||||
ws: 8.12.0
|
ws: 8.12.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -4697,10 +4701,10 @@ packages:
|
|||||||
'@types/ws': 8.5.3
|
'@types/ws': 8.5.3
|
||||||
'@whatwg-node/fetch': 0.8.1
|
'@whatwg-node/fetch': 0.8.1
|
||||||
graphql: 16.6.0
|
graphql: 16.6.0
|
||||||
isomorphic-ws: 5.0.0_ws@8.12.0
|
isomorphic-ws: 5.0.0_ws@8.12.1
|
||||||
tslib: 2.5.0
|
tslib: 2.5.0
|
||||||
value-or-promise: 1.0.11
|
value-or-promise: 1.0.11
|
||||||
ws: 8.12.0
|
ws: 8.12.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -5113,7 +5117,7 @@ packages:
|
|||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
fast-glob: 3.2.11
|
fast-glob: 3.2.11
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
vite: 3.2.4_sass@1.53.0
|
vite: 3.2.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6712,6 +6716,10 @@ packages:
|
|||||||
'@types/express': 4.17.14
|
'@types/express': 4.17.14
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/cookie/0.5.1:
|
||||||
|
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/cookiejar/2.1.2:
|
/@types/cookiejar/2.1.2:
|
||||||
resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==}
|
resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -7824,7 +7832,7 @@ packages:
|
|||||||
magic-string: 0.26.7
|
magic-string: 0.26.7
|
||||||
regenerator-runtime: 0.13.10
|
regenerator-runtime: 0.13.10
|
||||||
systemjs: 6.13.0
|
systemjs: 6.13.0
|
||||||
vite: 3.2.4_sass@1.53.0
|
vite: 3.2.4
|
||||||
|
|
||||||
/@vitejs/plugin-vue/1.10.2_vite@2.9.15:
|
/@vitejs/plugin-vue/1.10.2_vite@2.9.15:
|
||||||
resolution: {integrity: sha512-/QJ0Z9qfhAFtKRY+r57ziY4BSbGUTGsPRMpB/Ron3QPwBZM4OZAZHdTa4a8PafCwU5DTatXG8TMDoP8z+oDqJw==}
|
resolution: {integrity: sha512-/QJ0Z9qfhAFtKRY+r57ziY4BSbGUTGsPRMpB/Ron3QPwBZM4OZAZHdTa4a8PafCwU5DTatXG8TMDoP8z+oDqJw==}
|
||||||
@@ -7853,7 +7861,7 @@ packages:
|
|||||||
vite: ^3.0.0
|
vite: ^3.0.0
|
||||||
vue: ^3.2.25
|
vue: ^3.2.25
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 3.2.4_sass@1.53.0
|
vite: 3.2.4
|
||||||
vue: 3.2.45
|
vue: 3.2.45
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -8673,7 +8681,7 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
/after/0.8.2:
|
/after/0.8.2:
|
||||||
resolution: {integrity: sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==}
|
resolution: {integrity: sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/agent-base/6.0.2:
|
/agent-base/6.0.2:
|
||||||
@@ -9280,7 +9288,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
/base64-arraybuffer/0.1.4:
|
/base64-arraybuffer/0.1.4:
|
||||||
resolution: {integrity: sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==}
|
resolution: {integrity: sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -9836,14 +9844,14 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/component-bind/1.0.0:
|
/component-bind/1.0.0:
|
||||||
resolution: {integrity: sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==}
|
resolution: {integrity: sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/component-emitter/1.3.0:
|
/component-emitter/1.3.0:
|
||||||
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
|
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
|
||||||
|
|
||||||
/component-inherit/0.0.3:
|
/component-inherit/0.0.3:
|
||||||
resolution: {integrity: sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==}
|
resolution: {integrity: sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/concat-map/0.0.1:
|
/concat-map/0.0.1:
|
||||||
@@ -12876,7 +12884,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/has-cors/1.1.0:
|
/has-cors/1.1.0:
|
||||||
resolution: {integrity: sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==}
|
resolution: {integrity: sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/has-flag/3.0.0:
|
/has-flag/3.0.0:
|
||||||
@@ -13222,7 +13230,7 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
/indexof/0.0.1:
|
/indexof/0.0.1:
|
||||||
resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==}
|
resolution: {integrity: sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/inflight/1.0.6:
|
/inflight/1.0.6:
|
||||||
@@ -13642,20 +13650,12 @@ packages:
|
|||||||
- encoding
|
- encoding
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/isomorphic-ws/5.0.0_ws@8.12.0:
|
|
||||||
resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==}
|
|
||||||
peerDependencies:
|
|
||||||
ws: '*'
|
|
||||||
dependencies:
|
|
||||||
ws: 8.12.0
|
|
||||||
|
|
||||||
/isomorphic-ws/5.0.0_ws@8.12.1:
|
/isomorphic-ws/5.0.0_ws@8.12.1:
|
||||||
resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==}
|
resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
ws: '*'
|
ws: '*'
|
||||||
dependencies:
|
dependencies:
|
||||||
ws: 8.12.1
|
ws: 8.12.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/istanbul-lib-coverage/3.2.0:
|
/istanbul-lib-coverage/3.2.0:
|
||||||
resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
|
resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
|
||||||
@@ -14829,7 +14829,7 @@ packages:
|
|||||||
whatwg-encoding: 2.0.0
|
whatwg-encoding: 2.0.0
|
||||||
whatwg-mimetype: 3.0.0
|
whatwg-mimetype: 3.0.0
|
||||||
whatwg-url: 11.0.0
|
whatwg-url: 11.0.0
|
||||||
ws: 8.11.0
|
ws: 8.12.1
|
||||||
xml-name-validator: 4.0.0
|
xml-name-validator: 4.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -18110,7 +18110,7 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/to-array/0.1.4:
|
/to-array/0.1.4:
|
||||||
resolution: {integrity: sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==}
|
resolution: {integrity: sha1-F+bBH3PdTz10zaek/zI46a2b+JA=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/to-fast-properties/2.0.0:
|
/to-fast-properties/2.0.0:
|
||||||
@@ -19039,7 +19039,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.8.0
|
acorn: 8.8.0
|
||||||
chokidar: 3.5.3
|
chokidar: 3.5.3
|
||||||
vite: 3.2.4_sass@1.53.0
|
vite: 3.2.4
|
||||||
webpack-sources: 3.2.3
|
webpack-sources: 3.2.3
|
||||||
webpack-virtual-modules: 0.4.4
|
webpack-virtual-modules: 0.4.4
|
||||||
dev: true
|
dev: true
|
||||||
@@ -19417,7 +19417,7 @@ packages:
|
|||||||
vite: ^2.0.0 || ^3.0.0
|
vite: ^2.0.0 || ^3.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-glob: 3.2.11
|
fast-glob: 3.2.11
|
||||||
vite: 3.2.4_sass@1.53.0
|
vite: 3.2.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite-plugin-html-config/1.0.10_vite@3.1.4:
|
/vite-plugin-html-config/1.0.10_vite@3.1.4:
|
||||||
@@ -19435,7 +19435,7 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: '>=2.0.0'
|
vite: '>=2.0.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 3.2.4_sass@1.53.0
|
vite: 3.2.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite-plugin-inspect/0.7.4_vite@3.1.4:
|
/vite-plugin-inspect/0.7.4_vite@3.1.4:
|
||||||
@@ -19467,7 +19467,7 @@ packages:
|
|||||||
kolorist: 1.5.1
|
kolorist: 1.5.1
|
||||||
sirv: 2.0.2
|
sirv: 2.0.2
|
||||||
ufo: 0.8.5
|
ufo: 0.8.5
|
||||||
vite: 3.2.4_sass@1.53.0
|
vite: 3.2.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
@@ -19741,7 +19741,7 @@ packages:
|
|||||||
'@windicss/plugin-utils': 1.8.8
|
'@windicss/plugin-utils': 1.8.8
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
kolorist: 1.5.1
|
kolorist: 1.5.1
|
||||||
vite: 3.2.4_sass@1.53.0
|
vite: 3.2.4
|
||||||
windicss: 3.5.6
|
windicss: 3.5.6
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -19829,7 +19829,6 @@ packages:
|
|||||||
rollup: 2.79.1
|
rollup: 2.79.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/vite/3.2.4_sass@1.53.0:
|
/vite/3.2.4_sass@1.53.0:
|
||||||
resolution: {integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==}
|
resolution: {integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==}
|
||||||
@@ -20789,18 +20788,7 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
utf-8-validate:
|
utf-8-validate:
|
||||||
optional: true
|
optional: true
|
||||||
|
dev: false
|
||||||
/ws/8.12.0:
|
|
||||||
resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
bufferutil: ^4.0.1
|
|
||||||
utf-8-validate: '>=5.0.2'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
bufferutil:
|
|
||||||
optional: true
|
|
||||||
utf-8-validate:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/ws/8.12.1:
|
/ws/8.12.1:
|
||||||
resolution: {integrity: sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==}
|
resolution: {integrity: sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==}
|
||||||
@@ -20995,7 +20983,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/yeast/0.1.2:
|
/yeast/0.1.2:
|
||||||
resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==}
|
resolution: {integrity: sha1-AI4G2AlDIMNy28L47XagymyKxBk=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/yn/3.1.1:
|
/yn/3.1.1:
|
||||||
|
|||||||
Reference in New Issue
Block a user