From 085fbb2a9bccbc2f5fc14ad77840803b70e5c4bc Mon Sep 17 00:00:00 2001 From: Nivedin <53208152+nivedin@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:23:11 +0530 Subject: [PATCH] feat: tippy menu for history and tab (#3220) Co-authored-by: Liyas Thomas --- packages/hoppscotch-common/locales/en.json | 7 + .../hoppscotch-common/src/components.d.ts | 41 +++++- .../components/collections/SaveRequest.vue | 31 +++-- .../src/components/history/index.vue | 11 +- .../src/components/history/rest/Card.vue | 48 ++++++- .../src/components/http/Request.vue | 21 ++- .../src/components/http/TabHead.vue | 126 ++++++++++++++++++ .../hoppscotch-common/src/helpers/actions.ts | 11 +- .../hoppscotch-common/src/helpers/rest/tab.ts | 27 ++++ .../hoppscotch-common/src/pages/index.vue | 74 +++++++--- 10 files changed, 361 insertions(+), 36 deletions(-) create mode 100644 packages/hoppscotch-common/src/components/http/TabHead.vue diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index cb30e4ebe..3b38789b1 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -31,6 +31,7 @@ "open_workspace": "Open workspace", "paste": "Paste", "prettify": "Prettify", + "rename": "Rename", "remove": "Remove", "restore": "Restore", "save": "Save", @@ -132,6 +133,7 @@ "renamed": "Collection renamed", "request_in_use": "Request in use", "save_as": "Save as", + "save_to_collection": "Save to Collection", "select": "Select a Collection", "select_location": "Select location", "select_team": "Select a team", @@ -149,6 +151,7 @@ "remove_telemetry": "Are you sure you want to opt-out of Telemetry?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", + "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", "sync": "Would you like to restore your workspace from cloud? This will discard your local progress." }, "context_menu": { @@ -432,6 +435,7 @@ "payload": "Payload", "query": "Query", "raw_body": "Raw Request Body", + "rename": "Rename Request", "renamed": "Request renamed", "run": "Run", "save": "Save", @@ -658,8 +662,11 @@ "tab": { "authorization": "Authorization", "body": "Body", + "close": "Close Tab", + "close_others": "Close other Tabs", "collections": "Collections", "documentation": "Documentation", + "duplicate": "Duplicate Tab", "environments": "Environments", "headers": "Headers", "history": "History", diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 7ceddf987..d8037387c 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -7,7 +7,6 @@ export {} declare module "@vue/runtime-core" { export interface GlobalComponents { -<<<<<<< HEAD AppActionHandler: typeof import("./components/app/ActionHandler.vue")["default"] AppAnnouncement: typeof import("./components/app/Announcement.vue")["default"] AppDeveloperOptions: typeof import("./components/app/DeveloperOptions.vue")["default"] @@ -200,7 +199,6 @@ declare module "@vue/runtime-core" { Tippy: typeof import("vue-tippy")["Tippy"] WorkspaceCurrent: typeof import("./components/workspace/Current.vue")["default"] WorkspaceSelector: typeof import("./components/workspace/Selector.vue")["default"] -======= AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default'] AppAnnouncement: typeof import('./components/app/Announcement.vue')['default'] AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default'] @@ -268,6 +266,29 @@ declare module "@vue/runtime-core" { History: typeof import('./components/history/index.vue')['default'] HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default'] HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default'] + HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'] + HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] + HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] + HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete'] + HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox'] + HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] + HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand'] + HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] + HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection'] + HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] + HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] + HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'] + HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture'] + HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder'] + HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing'] + HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup'] + HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver'] + HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] + HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'] + HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'] + HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'] + HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow'] + HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows'] HttpAuthorization: typeof import('./components/http/Authorization.vue')['default'] HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default'] HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default'] @@ -287,12 +308,27 @@ declare module "@vue/runtime-core" { HttpResponse: typeof import('./components/http/Response.vue')['default'] HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default'] HttpSidebar: typeof import('./components/http/Sidebar.vue')['default'] + HttpTabHead: typeof import('./components/http/TabHead.vue')['default'] HttpTestResult: typeof import('./components/http/TestResult.vue')['default'] HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default'] HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default'] HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default'] HttpTests: typeof import('./components/http/Tests.vue')['default'] HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default'] + IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] + IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] + IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] + IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] + IconLucideGlobe: typeof import('~icons/lucide/globe')['default'] + IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default'] + IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] + IconLucideInfo: typeof import('~icons/lucide/info')['default'] + IconLucideLayers: typeof import('~icons/lucide/layers')['default'] + IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] + IconLucideMinus: typeof import('~icons/lucide/minus')['default'] + IconLucideSearch: typeof import('~icons/lucide/search')['default'] + IconLucideUsers: typeof import('~icons/lucide/users')['default'] + IconLucideVerified: typeof import('~icons/lucide/verified')['default'] LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default'] LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default'] @@ -352,6 +388,5 @@ declare module "@vue/runtime-core" { Tippy: typeof import('vue-tippy')['Tippy'] WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default'] WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default'] ->>>>>>> upstream/main } } diff --git a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue index 4484c93c6..5dd260d95 100644 --- a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue @@ -56,7 +56,7 @@ diff --git a/packages/hoppscotch-common/src/helpers/actions.ts b/packages/hoppscotch-common/src/helpers/actions.ts index 00ec882e4..2c85347fc 100644 --- a/packages/hoppscotch-common/src/helpers/actions.ts +++ b/packages/hoppscotch-common/src/helpers/actions.ts @@ -5,7 +5,7 @@ import { Ref, onBeforeUnmount, onMounted, watch } from "vue" import { BehaviorSubject } from "rxjs" import { HoppRESTDocument } from "./rest/document" -import { HoppGQLRequest } from "@hoppscotch/data" +import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data" export type HoppAction = | "contextmenu.open" // Send/Cancel a Hoppscotch Request @@ -76,6 +76,15 @@ type HoppActionArgsMap = { "rest.request.open": { doc: HoppRESTDocument } + "request.save-as": + | { + requestType: "rest" + request: HoppRESTRequest + } + | { + requestType: "gql" + request: HoppGQLRequest + } "gql.request.open": { request: HoppGQLRequest } diff --git a/packages/hoppscotch-common/src/helpers/rest/tab.ts b/packages/hoppscotch-common/src/helpers/rest/tab.ts index 734dd0cbd..612a6e93e 100644 --- a/packages/hoppscotch-common/src/helpers/rest/tab.ts +++ b/packages/hoppscotch-common/src/helpers/rest/tab.ts @@ -181,6 +181,33 @@ export function closeTab(tabID: string) { tabMap.delete(tabID) } +export function closeOtherTabs(tabID: string) { + if (!tabMap.has(tabID)) { + console.warn( + `The tab to close other tabs does not exist (tab id: ${tabID})` + ) + return + } + + tabOrdering.value = [tabID] + + tabMap.forEach((_, id) => { + if (id !== tabID) tabMap.delete(id) + }) + + currentTabID.value = tabID +} + +export function getDirtyTabsCount() { + let count = 0 + + for (const tab of tabMap.values()) { + if (tab.document.isDirty) count++ + } + + return count +} + export function getTabRefWithSaveContext(ctx: HoppRESTSaveContext) { for (const tab of tabMap.values()) { // For `team-collection` request id can be considered unique diff --git a/packages/hoppscotch-common/src/pages/index.vue b/packages/hoppscotch-common/src/pages/index.vue index dcb8cfd35..b1d2fac74 100644 --- a/packages/hoppscotch-common/src/pages/index.vue +++ b/packages/hoppscotch-common/src/pages/index.vue @@ -19,22 +19,14 @@ :close-visibility="'hover'" >