feat: added reordering and moving for collection (#2916)

This commit is contained in:
Nivedin
2023-02-24 19:09:07 +05:30
committed by GitHub
parent dcd441f15e
commit 4ca6e9ec3a
24 changed files with 1721 additions and 359 deletions

View File

@@ -117,12 +117,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,6 +393,7 @@
"text": "Text" "text": "Text"
}, },
"copy_link": "Copy link", "copy_link": "Copy link",
"different_collection": "Cannot reorder requests from different collections",
"duration": "Duration", "duration": "Duration",
"enter_curl": "Enter cURL command", "enter_curl": "Enter cURL command",
"duplicated": "Request duplicated", "duplicated": "Request duplicated",
@@ -397,8 +402,10 @@
"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",
@@ -655,6 +662,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",
@@ -683,10 +691,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",

View File

@@ -1,170 +1,162 @@
// generated by unplugin-vue-components // generated by unplugin-vue-components
// We suggest you to commit this file into source control // We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core' import "@vue/runtime-core"
export {} export {}
declare module '@vue/runtime-core' { declare module "@vue/runtime-core" {
export interface GlobalComponents { export interface GlobalComponents {
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default'] AppActionHandler: typeof import("./components/app/ActionHandler.vue")["default"]
AppAnnouncement: typeof import('./components/app/Announcement.vue')['default'] AppAnnouncement: typeof import("./components/app/Announcement.vue")["default"]
AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default'] AppDeveloperOptions: typeof import("./components/app/DeveloperOptions.vue")["default"]
AppFooter: typeof import('./components/app/Footer.vue')['default'] AppFooter: typeof import("./components/app/Footer.vue")["default"]
AppFuse: typeof import('./components/app/Fuse.vue')['default'] AppFuse: typeof import("./components/app/Fuse.vue")["default"]
AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default'] AppGitHubStarButton: typeof import("./components/app/GitHubStarButton.vue")["default"]
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"]
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"]
AppPowerSearchEntry: typeof import('./components/app/PowerSearchEntry.vue')['default'] AppPowerSearchEntry: typeof import("./components/app/PowerSearchEntry.vue")["default"]
AppShare: typeof import('./components/app/Share.vue')['default'] AppShare: typeof import("./components/app/Share.vue")["default"]
AppShortcuts: typeof import('./components/app/Shortcuts.vue')['default'] AppShortcuts: typeof import("./components/app/Shortcuts.vue")["default"]
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default'] AppShortcutsEntry: typeof import("./components/app/ShortcutsEntry.vue")["default"]
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"]
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"]
CollectionsAddRequest: typeof import('./components/collections/AddRequest.vue')['default'] CollectionsAddRequest: typeof import("./components/collections/AddRequest.vue")["default"]
CollectionsCollection: typeof import('./components/collections/Collection.vue')['default'] CollectionsCollection: typeof import("./components/collections/Collection.vue")["default"]
CollectionsEdit: typeof import('./components/collections/Edit.vue')['default'] CollectionsEdit: typeof import("./components/collections/Edit.vue")["default"]
CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default'] CollectionsEditFolder: typeof import("./components/collections/EditFolder.vue")["default"]
CollectionsEditRequest: typeof import('./components/collections/EditRequest.vue')['default'] CollectionsEditRequest: typeof import("./components/collections/EditRequest.vue")["default"]
CollectionsGraphql: typeof import('./components/collections/graphql/index.vue')['default'] CollectionsGraphql: typeof import("./components/collections/graphql/index.vue")["default"]
CollectionsGraphqlAdd: typeof import('./components/collections/graphql/Add.vue')['default'] CollectionsGraphqlAdd: typeof import("./components/collections/graphql/Add.vue")["default"]
CollectionsGraphqlAddFolder: typeof import('./components/collections/graphql/AddFolder.vue')['default'] CollectionsGraphqlAddFolder: typeof import("./components/collections/graphql/AddFolder.vue")["default"]
CollectionsGraphqlAddRequest: typeof import('./components/collections/graphql/AddRequest.vue')['default'] CollectionsGraphqlAddRequest: typeof import("./components/collections/graphql/AddRequest.vue")["default"]
CollectionsGraphqlCollection: typeof import('./components/collections/graphql/Collection.vue')['default'] CollectionsGraphqlCollection: typeof import("./components/collections/graphql/Collection.vue")["default"]
CollectionsGraphqlEdit: typeof import('./components/collections/graphql/Edit.vue')['default'] CollectionsGraphqlEdit: typeof import("./components/collections/graphql/Edit.vue")["default"]
CollectionsGraphqlEditFolder: typeof import('./components/collections/graphql/EditFolder.vue')['default'] CollectionsGraphqlEditFolder: typeof import("./components/collections/graphql/EditFolder.vue")["default"]
CollectionsGraphqlEditRequest: typeof import('./components/collections/graphql/EditRequest.vue')['default'] CollectionsGraphqlEditRequest: typeof import("./components/collections/graphql/EditRequest.vue")["default"]
CollectionsGraphqlFolder: typeof import('./components/collections/graphql/Folder.vue')['default'] CollectionsGraphqlFolder: typeof import("./components/collections/graphql/Folder.vue")["default"]
CollectionsGraphqlImportExport: typeof import('./components/collections/graphql/ImportExport.vue')['default'] CollectionsGraphqlImportExport: typeof import("./components/collections/graphql/ImportExport.vue")["default"]
CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default'] CollectionsGraphqlRequest: typeof import("./components/collections/graphql/Request.vue")["default"]
CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default'] CollectionsImportExport: typeof import("./components/collections/ImportExport.vue")["default"]
CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default'] CollectionsMyCollections: typeof import("./components/collections/MyCollections.vue")["default"]
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'] 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'] 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"]
EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default'] EnvironmentsMyEnvironment: typeof import("./components/environments/my/Environment.vue")["default"]
EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default'] EnvironmentsTeams: typeof import("./components/environments/teams/index.vue")["default"]
EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default'] EnvironmentsTeamsDetails: typeof import("./components/environments/teams/Details.vue")["default"]
EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default'] EnvironmentsTeamsEnvironment: typeof import("./components/environments/teams/Environment.vue")["default"]
FirebaseLogin: typeof import('./components/firebase/Login.vue')['default'] FirebaseLogin: typeof import("./components/firebase/Login.vue")["default"]
FirebaseLogout: typeof import('./components/firebase/Logout.vue')['default'] FirebaseLogout: typeof import("./components/firebase/Logout.vue")["default"]
GraphqlAuthorization: typeof import('./components/graphql/Authorization.vue')['default'] GraphqlAuthorization: typeof import("./components/graphql/Authorization.vue")["default"]
GraphqlField: typeof import('./components/graphql/Field.vue')['default'] GraphqlField: typeof import("./components/graphql/Field.vue")["default"]
GraphqlRequest: typeof import('./components/graphql/Request.vue')['default'] GraphqlRequest: typeof import("./components/graphql/Request.vue")["default"]
GraphqlRequestOptions: typeof import('./components/graphql/RequestOptions.vue')['default'] GraphqlRequestOptions: typeof import("./components/graphql/RequestOptions.vue")["default"]
GraphqlResponse: typeof import('./components/graphql/Response.vue')['default'] GraphqlResponse: typeof import("./components/graphql/Response.vue")["default"]
GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default'] GraphqlSidebar: typeof import("./components/graphql/Sidebar.vue")["default"]
GraphqlType: typeof import('./components/graphql/Type.vue')['default'] GraphqlType: typeof import("./components/graphql/Type.vue")["default"]
GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default'] GraphqlTypeLink: typeof import("./components/graphql/TypeLink.vue")["default"]
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'] HoppButtonPrimary: typeof import("@hoppscotch/ui")["HoppButtonPrimary"]
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] HoppButtonSecondary: typeof import("@hoppscotch/ui")["HoppButtonSecondary"]
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] HoppSmartAnchor: typeof import("@hoppscotch/ui")["HoppSmartAnchor"]
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete'] HoppSmartConfirmModal: typeof import("@hoppscotch/ui")["HoppSmartConfirmModal"]
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox'] HoppSmartExpand: typeof import("@hoppscotch/ui")["HoppSmartExpand"]
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] HoppSmartFileChip: typeof import("@hoppscotch/ui")["HoppSmartFileChip"]
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand'] HoppSmartIntersection: typeof import("@hoppscotch/ui")["HoppSmartIntersection"]
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] HoppSmartExpand: typeof import("@hoppscotch/ui")["HoppSmartExpand"]
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection'] HoppSmartFileChip: typeof import("@hoppscotch/ui")["HoppSmartFileChip"]
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] HoppSmartIntersection: typeof import("@hoppscotch/ui")["HoppSmartIntersection"]
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] HoppSmartItem: typeof import("@hoppscotch/ui")["HoppSmartItem"]
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'] HoppSmartLink: typeof import("@hoppscotch/ui")["HoppSmartLink"]
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing'] HoppSmartModal: typeof import("@hoppscotch/ui")["HoppSmartModal"]
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup'] HoppSmartRadioGroup: typeof import("@hoppscotch/ui")["HoppSmartRadioGroup"]
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver'] HoppSmartSlideOver: typeof import("@hoppscotch/ui")["HoppSmartSlideOver"]
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] HoppSmartSpinner: typeof import("@hoppscotch/ui")["HoppSmartSpinner"]
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'] HoppSmartTab: typeof import("@hoppscotch/ui")["HoppSmartTab"]
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'] HoppSmartTabs: typeof import("@hoppscotch/ui")["HoppSmartTabs"]
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'] HoppSmartToggle: typeof import("@hoppscotch/ui")["HoppSmartToggle"]
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow'] HoppSmartWindow: typeof import("@hoppscotch/ui")["HoppSmartWindow"]
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows'] HoppSmartWindows: typeof import("@hoppscotch/ui")["HoppSmartWindows"]
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default'] HoppSmartTab: typeof import("@hoppscotch/ui")["HoppSmartTab"]
HttpBody: typeof import('./components/http/Body.vue')['default'] HoppSmartTabs: typeof import("@hoppscotch/ui")["HoppSmartTabs"]
HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default'] HttpAuthorization: typeof import("./components/http/Authorization.vue")["default"]
HttpCodegenModal: typeof import('./components/http/CodegenModal.vue')['default'] HttpBody: typeof import("./components/http/Body.vue")["default"]
HttpHeaders: typeof import('./components/http/Headers.vue')['default'] HttpBodyParameters: typeof import("./components/http/BodyParameters.vue")["default"]
HttpImportCurl: typeof import('./components/http/ImportCurl.vue')['default'] HttpCodegenModal: typeof import("./components/http/CodegenModal.vue")["default"]
HttpOAuth2Authorization: typeof import('./components/http/OAuth2Authorization.vue')['default'] HttpHeaders: typeof import("./components/http/Headers.vue")["default"]
HttpParameters: typeof import('./components/http/Parameters.vue')['default'] HttpImportCurl: typeof import("./components/http/ImportCurl.vue")["default"]
HttpPreRequestScript: typeof import('./components/http/PreRequestScript.vue')['default'] HttpOAuth2Authorization: typeof import("./components/http/OAuth2Authorization.vue")["default"]
HttpRawBody: typeof import('./components/http/RawBody.vue')['default'] HttpParameters: typeof import("./components/http/Parameters.vue")["default"]
HttpReqChangeConfirmModal: typeof import('./components/http/ReqChangeConfirmModal.vue')['default'] HttpPreRequestScript: typeof import("./components/http/PreRequestScript.vue")["default"]
HttpRequest: typeof import('./components/http/Request.vue')['default'] HttpRawBody: typeof import("./components/http/RawBody.vue")["default"]
HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default'] HttpReqChangeConfirmModal: typeof import("./components/http/ReqChangeConfirmModal.vue")["default"]
HttpResponse: typeof import('./components/http/Response.vue')['default'] HttpRequest: typeof import("./components/http/Request.vue")["default"]
HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default'] HttpRequestOptions: typeof import("./components/http/RequestOptions.vue")["default"]
HttpSidebar: typeof import('./components/http/Sidebar.vue')['default'] HttpResponse: typeof import("./components/http/Response.vue")["default"]
HttpTestResult: typeof import('./components/http/TestResult.vue')['default'] HttpResponseMeta: typeof import("./components/http/ResponseMeta.vue")["default"]
HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default'] HttpSidebar: typeof import("./components/http/Sidebar.vue")["default"]
HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default'] HttpTestResult: typeof import("./components/http/TestResult.vue")["default"]
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default'] HttpTestResultEntry: typeof import("./components/http/TestResultEntry.vue")["default"]
HttpTests: typeof import('./components/http/Tests.vue')['default'] HttpTestResultEnv: typeof import("./components/http/TestResultEnv.vue")["default"]
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default'] HttpTestResultReport: typeof import("./components/http/TestResultReport.vue")["default"]
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] HttpTests: typeof import("./components/http/Tests.vue")["default"]
IconLucideBrush: typeof import('~icons/lucide/brush')['default'] HttpURLEncodedParams: typeof import("./components/http/URLEncodedParams.vue")["default"]
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] IconLucideChevronRight: typeof import("~icons/lucide/chevron-right")["default"]
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] IconLucideInbox: typeof import("~icons/lucide/inbox")["default"]
IconLucideGlobe: typeof import('~icons/lucide/globe')['default'] IconLucideInfo: typeof import("~icons/lucide/info")["default"]
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default'] IconLucideSearch: typeof import("~icons/lucide/search")["default"]
IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] IconLucideUser: typeof import("~icons/lucide/user")["default"]
IconLucideInfo: typeof import('~icons/lucide/info')['default'] IconLucideUsers: typeof import("~icons/lucide/users")["default"]
IconLucideLayers: typeof import('~icons/lucide/layers')['default'] LensesHeadersRenderer: typeof import("./components/lenses/HeadersRenderer.vue")["default"]
IconLucideMinus: typeof import('~icons/lucide/minus')['default'] LensesHeadersRendererEntry: typeof import("./components/lenses/HeadersRendererEntry.vue")["default"]
IconLucideRss: typeof import('~icons/lucide/rss')['default'] LensesRenderersHTMLLensRenderer: typeof import("./components/lenses/renderers/HTMLLensRenderer.vue")["default"]
IconLucideSearch: typeof import('~icons/lucide/search')['default'] LensesRenderersImageLensRenderer: typeof import("./components/lenses/renderers/ImageLensRenderer.vue")["default"]
IconLucideUser: typeof import('~icons/lucide/user')['default'] LensesRenderersJSONLensRenderer: typeof import("./components/lenses/renderers/JSONLensRenderer.vue")["default"]
IconLucideUsers: typeof import('~icons/lucide/users')['default'] LensesRenderersPDFLensRenderer: typeof import("./components/lenses/renderers/PDFLensRenderer.vue")["default"]
IconLucideVerified: typeof import('~icons/lucide/verified')['default'] LensesRenderersRawLensRenderer: typeof import("./components/lenses/renderers/RawLensRenderer.vue")["default"]
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] LensesRenderersXMLLensRenderer: typeof import("./components/lenses/renderers/XMLLensRenderer.vue")["default"]
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default'] LensesResponseBodyRenderer: typeof import("./components/lenses/ResponseBodyRenderer.vue")["default"]
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default'] ProfilePicture: typeof import("./components/profile/Picture.vue")["default"]
LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default'] ProfileShortcode: typeof import("./components/profile/Shortcode.vue")["default"]
LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default'] ProfileShortcodes: typeof import("./components/profile/Shortcodes.vue")["default"]
LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default'] ProfileUserDelete: typeof import("./components/profile/UserDelete.vue")["default"]
LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default'] RealtimeCommunication: typeof import("./components/realtime/Communication.vue")["default"]
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default'] RealtimeConnectionConfig: typeof import("./components/realtime/ConnectionConfig.vue")["default"]
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default'] RealtimeLog: typeof import("./components/realtime/Log.vue")["default"]
ProfilePicture: typeof import('./components/profile/Picture.vue')['default'] RealtimeLogEntry: typeof import("./components/realtime/LogEntry.vue")["default"]
ProfileShortcode: typeof import('./components/profile/Shortcode.vue')['default'] RealtimeSubscription: typeof import("./components/realtime/Subscription.vue")["default"]
ProfileShortcodes: typeof import('./components/profile/Shortcodes.vue')['default'] SmartAccentModePicker: typeof import("./components/smart/AccentModePicker.vue")["default"]
ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default'] SmartChangeLanguage: typeof import("./components/smart/ChangeLanguage.vue")["default"]
RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default'] SmartColorModePicker: typeof import("./components/smart/ColorModePicker.vue")["default"]
RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default'] SmartEnvInput: typeof import("./components/smart/EnvInput.vue")["default"]
RealtimeLog: typeof import('./components/realtime/Log.vue')['default'] SmartFontSizePicker: typeof import("./components/smart/FontSizePicker.vue")["default"]
RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default'] SmartTree: typeof import("./components/smart/Tree.vue")["default"]
RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default'] SmartTreeBranch: typeof import("./components/smart/TreeBranch.vue")["default"]
SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default'] TabPrimary: typeof import("./components/tab/Primary.vue")["default"]
SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default'] TabSecondary: typeof import("./components/tab/Secondary.vue")["default"]
SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default'] Teams: typeof import("./components/teams/index.vue")["default"]
SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default'] TeamsAdd: typeof import("./components/teams/Add.vue")["default"]
SmartFontSizePicker: typeof import('./components/smart/FontSizePicker.vue')['default'] TeamsEdit: typeof import("./components/teams/Edit.vue")["default"]
SmartTree: typeof import('./components/smart/Tree.vue')['default'] TeamsInvite: typeof import("./components/teams/Invite.vue")["default"]
SmartTreeBranch: typeof import('./components/smart/TreeBranch.vue')['default'] TeamsModal: typeof import("./components/teams/Modal.vue")["default"]
TabPrimary: typeof import('./components/tab/Primary.vue')['default'] TeamsTeam: typeof import("./components/teams/Team.vue")["default"]
TabSecondary: typeof import('./components/tab/Secondary.vue')['default'] Tippy: typeof import("vue-tippy")["Tippy"]
Teams: typeof import('./components/teams/index.vue')['default']
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
TeamsEdit: typeof import('./components/teams/Edit.vue')['default']
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
TeamsModal: typeof import('./components/teams/Modal.vue')['default']
TeamsTeam: typeof import('./components/teams/Team.vue')['default']
Tippy: typeof import('vue-tippy')['Tippy']
} }
} }

View File

@@ -1,138 +1,160 @@
<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" {
@dragover="dragging = true" 'bg-accentDark': ordering && notSameDestination,
@drop="dragging = false" },
@dragleave="dragging = false" ]"
@dragend="dragging = false" @drop="orderUpdateCollectionEvent"
@contextmenu.prevent="options?.tippy.show()" @dragover.prevent="ordering = true"
> @dragleave="ordering = false"
<span @dragend="resetDragState"
class="flex items-center justify-center px-4 cursor-pointer" ></div>
@click="emit('toggle-children')" <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"
@dragleave="dragging = false"
@dragend="resetDragState"
@contextmenu.prevent="options?.tippy.show()"
> >
<component <span
:is="collectionIcon" class="flex items-center justify-center px-4 cursor-pointer"
class="svg-icons" @click="emit('toggle-children')"
:class="{ 'text-accent': isSelected }" >
/> <HoppSmartSpinner v-if="isCollLoading" />
</span> <component
<span :is="collectionIcon"
class="flex flex-1 min-w-0 py-2 pr-2 transition cursor-pointer group-hover:text-secondaryDark" v-else
@click="emit('toggle-children')" class="svg-icons"
> :class="{ 'text-accent': isSelected }"
<span class="truncate" :class="{ 'text-accent': isSelected }"> />
{{ collectionName }}
</span> </span>
</span> <span
<div v-if="!hasNoTeamAccess" class="flex"> class="flex flex-1 min-w-0 py-2 pr-2 transition cursor-pointer group-hover:text-secondaryDark"
<HoppButtonSecondary @click="emit('toggle-children')"
v-tippy="{ theme: 'tooltip' }" >
:icon="IconFilePlus" <span class="truncate" :class="{ 'text-accent': isSelected }">
:title="t('request.new')" {{ collectionName }}
class="hidden group-hover:inline-flex" </span>
@click="emit('add-request')"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:icon="IconFolderPlus"
:title="t('folder.new')"
class="hidden group-hover:inline-flex"
@click="emit('add-folder')"
/>
<span>
<tippy
ref="options"
interactive
trigger="click"
theme="popover"
:on-shown="() => tippyActions!.focus()"
>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.more')"
:icon="IconMoreVertical"
/>
<template #content="{ hide }">
<div
ref="tippyActions"
class="flex flex-col focus:outline-none"
tabindex="0"
@keyup.r="requestAction?.$el.click()"
@keyup.n="folderAction?.$el.click()"
@keyup.e="edit?.$el.click()"
@keyup.delete="deleteAction?.$el.click()"
@keyup.x="exportAction?.$el.click()"
@keyup.escape="hide()"
>
<HoppSmartItem
ref="requestAction"
:icon="IconFilePlus"
:label="t('request.new')"
:shortcut="['R']"
@click="
() => {
emit('add-request')
hide()
}
"
/>
<HoppSmartItem
ref="folderAction"
:icon="IconFolderPlus"
:label="t('folder.new')"
:shortcut="['N']"
@click="
() => {
emit('add-folder')
hide()
}
"
/>
<HoppSmartItem
ref="edit"
:icon="IconEdit"
:label="t('action.edit')"
:shortcut="['E']"
@click="
() => {
emit('edit-collection')
hide()
}
"
/>
<HoppSmartItem
ref="exportAction"
:icon="IconDownload"
:label="t('export.title')"
:shortcut="['X']"
:loading="exportLoading"
@click="
() => {
emit('export-data'),
collectionsType === 'my-collections' ? hide() : null
}
"
/>
<HoppSmartItem
ref="deleteAction"
:icon="IconTrash2"
:label="t('action.delete')"
:shortcut="['⌫']"
@click="
() => {
emit('remove-collection')
hide()
}
"
/>
</div>
</template>
</tippy>
</span> </span>
<div v-if="!hasNoTeamAccess" class="flex">
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:icon="IconFilePlus"
:title="t('request.new')"
class="hidden group-hover:inline-flex"
@click="emit('add-request')"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:icon="IconFolderPlus"
:title="t('folder.new')"
class="hidden group-hover:inline-flex"
@click="emit('add-folder')"
/>
<span>
<tippy
ref="options"
interactive
trigger="click"
theme="popover"
:on-shown="() => tippyActions!.focus()"
>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.more')"
:icon="IconMoreVertical"
/>
<template #content="{ hide }">
<div
ref="tippyActions"
class="flex flex-col focus:outline-none"
tabindex="0"
@keyup.r="requestAction?.$el.click()"
@keyup.n="folderAction?.$el.click()"
@keyup.e="edit?.$el.click()"
@keyup.delete="deleteAction?.$el.click()"
@keyup.x="exportAction?.$el.click()"
@keyup.escape="hide()"
>
<HoppSmartItem
ref="requestAction"
:icon="IconFilePlus"
:label="t('request.new')"
:shortcut="['R']"
@click="
() => {
emit('add-request')
hide()
}
"
/>
<HoppSmartItem
ref="folderAction"
:icon="IconFolderPlus"
:label="t('folder.new')"
:shortcut="['N']"
@click="
() => {
emit('add-folder')
hide()
}
"
/>
<HoppSmartItem
ref="edit"
:icon="IconEdit"
:label="t('action.edit')"
:shortcut="['E']"
@click="
() => {
emit('edit-collection')
hide()
}
"
/>
<HoppSmartItem
ref="exportAction"
:icon="IconDownload"
:label="t('export.title')"
:shortcut="['X']"
:loading="exportLoading"
@click="
() => {
emit('export-data'),
collectionsType === 'my-collections' ? hide() : null
}
"
/>
<HoppSmartItem
ref="deleteAction"
:icon="IconTrash2"
:label="t('action.delete')"
:shortcut="['⌫']"
@click="
() => {
emit('remove-collection')
hide()
}
"
/>
</div>
</template>
</tippy>
</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -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>

View File

@@ -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")
emit("drop-request", { 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, folderPath,
requestIndex, requestIndex,
collectionIndex, }: { 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,
}) })
} }

View File

@@ -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>

View File

@@ -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="
@@ -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: {

View File

@@ -1,5 +1,14 @@
<template> <template>
<div :class="{ 'rounded border border-divider': saveRequest }"> <div
:class="{
'rounded border border-divider': saveRequest,
'bg-primaryDark': draggingToRoot,
}"
class="flex-1"
@drop.prevent="dropToRoot"
@dragover.prevent="draggingToRoot = true"
@dragend="draggingToRoot = false"
>
<div <div
class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto rounded-t bg-primary" class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto rounded-t bg-primary"
:style=" :style="
@@ -44,6 +53,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"
@@ -83,6 +95,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 +109,22 @@
@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> </HoppSmartTab>
</HoppSmartTabs> </HoppSmartTabs>
<div
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"
@@ -195,12 +219,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 +253,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"
@@ -244,6 +275,7 @@ 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 { invokeAction } from "~/helpers/actions"
import IconListEnd from "~icons/lucide/list-end"
const t = useI18n() const t = useI18n()
const toast = useToast() const toast = useToast()
@@ -324,6 +356,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)
@@ -1333,16 +1370,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 +1860,38 @@ const resetSelectedData = () => {
} }
const getErrorMessage = (err: GQLError<string>) => { const getErrorMessage = (err: GQLError<string>) => {
console.error(err)
if (err.type === "network_error") { if (err.type === "network_error") {
console.error(err)
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")
} }
} }

View File

@@ -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 }">

View File

@@ -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>

View File

@@ -0,0 +1,8 @@
mutation MoveRESTTeamCollection($collectionID: ID!, $parentCollectionID: ID) {
moveCollection(
collectionID: $collectionID
parentCollectionID: $parentCollectionID
) {
id
}
}

View File

@@ -0,0 +1,5 @@
mutation MoveRESTTeamRequest($collectionID: ID!, $requestID: ID!) {
moveRequest(destCollID: $collectionID, requestID: $requestID) {
id
}
}

View File

@@ -1,5 +0,0 @@
mutation MoveRESTTeamRequest($requestID: ID!, $collectionID: ID!) {
moveRequest(requestID: $requestID, destCollID: $collectionID) {
id
}
}

View File

@@ -0,0 +1,3 @@
mutation UpdateCollectionOrder($collectionID: ID!, $destCollID: ID!) {
updateCollectionOrder(collectionID: $collectionID, destCollID: $destCollID)
}

View File

@@ -0,0 +1,11 @@
mutation UpdateLookUpRequestOrder(
$requestID: ID!
$nextRequestID: ID
$collectionID: ID!
) {
updateLookUpRequestOrder(
requestID: $requestID
nextRequestID: $nextRequestID
collectionID: $collectionID
)
}

View File

@@ -0,0 +1,9 @@
query GetSingleCollection($collectionID: ID!) {
collection(collectionID: $collectionID) {
id
title
parent {
id
}
}
}

View File

@@ -0,0 +1,8 @@
query GetSingleRequest($requestID: ID!) {
request(requestID: $requestID) {
id
collectionID
title
request
}
}

View File

@@ -0,0 +1,9 @@
subscription TeamCollectionMoved($teamID: ID!) {
teamCollectionMoved(teamID: $teamID) {
id
title
parent {
id
}
}
}

View File

@@ -0,0 +1,18 @@
subscription TeamCollectionOrderUpdated($teamID: ID!) {
collectionOrderUpdated(teamID: $teamID) {
collection {
id
title
parent {
id
}
}
nextCollection {
id
title
parent {
id
}
}
}
}

View File

@@ -0,0 +1,8 @@
subscription TeamRequestMoved($teamID: ID!) {
requestMoved(teamID: $teamID) {
id
collectionID
request
title
}
}

View File

@@ -0,0 +1,16 @@
subscription TeamRequestOrderUpdated($teamID: ID!) {
requestOrderUpdated(teamID: $teamID) {
request {
id
collectionID
request
title
}
nextRequest {
id
collectionID
request
title
}
}
}

View File

@@ -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,

View File

@@ -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,
}) })

View File

@@ -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() {
@@ -328,7 +356,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 +484,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 +740,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 +749,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)
}
)
} }
/** /**

View File

@@ -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>[]
) { ) {