Skip to content

Commit

Permalink
refactor: introduce writable handles to signify updates to handle ref…
Browse files Browse the repository at this point in the history
…erences

A special list of writable handles is compiled in a list while issuing handles (request/collection creation, etc). Instead of manually computing the tab and toggling the dirty state, the writable handle is updated (changing the type to invalid on request deletion) and the tab with the request open can infer it via the update reflected in the request handle under the tab save context (reactive update trigger).
  • Loading branch information
jamesgeorge007 committed Apr 24, 2024
1 parent 854ffa2 commit 3c0af78
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 106 deletions.
Expand Up @@ -1118,15 +1118,6 @@ const onRemoveRequest = async () => {
return
}
const { providerID, requestID, workspaceID } = requestHandle.value.data
const possibleTab = tabs.getTabRefWithSaveContext({
originLocation: "workspace-user-collection",
workspaceID,
providerID,
requestID,
})
if (
isSelected({
requestIndex: parseInt(requestIndexPath.split("/").pop() ?? ""),
Expand All @@ -1143,12 +1134,6 @@ const onRemoveRequest = async () => {
return
}
// If there is a tab attached to this request, dissociate its state and mark it dirty
if (possibleTab) {
possibleTab.value.document.saveContext = null
possibleTab.value.document.isDirty = true
}
toast.success(t("state.deleted"))
displayConfirmModal(false)
}
Expand Down
38 changes: 19 additions & 19 deletions packages/hoppscotch-common/src/pages/index.vue
Expand Up @@ -13,7 +13,7 @@
<HoppSmartWindow
v-for="tab in activeTabs"
:id="tab.id"
:key="tab.id"
:key="`${tab.id}-${tab.document.isDirty}`"
:label="tab.document.request.name"
:is-removable="activeTabs.length > 1"
:close-visibility="'hover'"
Expand Down Expand Up @@ -94,40 +94,40 @@
</template>

<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from "vue"
import { safelyExtractRESTRequest } from "@hoppscotch/data"
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
import { useRoute } from "vue-router"
import { useI18n } from "@composables/i18n"
import { getDefaultRESTRequest } from "~/helpers/rest/default"
import { defineActionHandler, invokeAction } from "~/helpers/actions"
import { onLoggedIn } from "~/composables/auth"
import { platform } from "~/platform"
import { safelyExtractRESTRequest } from "@hoppscotch/data"
import { watchDebounced } from "@vueuse/core"
import { useService } from "dioc/vue"
import { cloneDeep } from "lodash-es"
import {
audit,
BehaviorSubject,
combineLatest,
EMPTY,
Subscription,
audit,
combineLatest,
from,
map,
Subscription,
} from "rxjs"
import { useToast } from "~/composables/toast"
import { watchDebounced } from "@vueuse/core"
import { onBeforeUnmount, onMounted, ref } from "vue"
import { useRoute } from "vue-router"
import { onLoggedIn } from "~/composables/auth"
import { useReadonlyStream } from "~/composables/stream"
import { useToast } from "~/composables/toast"
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
import { defineActionHandler, invokeAction } from "~/helpers/actions"
import { getDefaultRESTRequest } from "~/helpers/rest/default"
import { HoppRESTDocument } from "~/helpers/rest/document"
import {
changeCurrentSyncStatus,
currentSyncingStatus$,
} from "~/newstore/syncing"
import { useService } from "dioc/vue"
import { platform } from "~/platform"
import { InspectionService } from "~/services/inspection"
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
import { cloneDeep } from "lodash-es"
import { RESTTabService } from "~/services/tab/rest"
import { HoppTab, PersistableTabState } from "~/services/tab"
import { HoppRESTDocument } from "~/helpers/rest/document"
import { RESTTabService } from "~/services/tab/rest"
const savingRequest = ref(false)
const confirmingCloseForTabID = ref<string | null>(null)
Expand Down
5 changes: 5 additions & 0 deletions packages/hoppscotch-common/src/services/inspection/index.ts
Expand Up @@ -123,6 +123,11 @@ export class InspectionService extends Service {
}

private initializeListeners() {
console.log(
`Current active tab from inspection service is `,
this.restTab.currentActiveTab.value
)

watch(
() => [this.inspectors.entries(), this.restTab.currentActiveTab.value.id],
() => {
Expand Down
@@ -1,5 +1,12 @@
import { Ref } from "vue"
import { Ref, WritableComputedRef } from "vue"

export type HandleRef<T, InvalidateReason = unknown> = Ref<
{ type: "ok"; data: T } | { type: "invalid"; reason: InvalidateReason }
>

export type WritableHandleRef<
T,
InvalidateReason = unknown,
> = WritableComputedRef<
{ type: "ok"; data: T } | { type: "invalid"; reason: InvalidateReason }
>
Expand Up @@ -39,7 +39,7 @@ import {
} from "~/newstore/collections"
import { platform } from "~/platform"

import { HandleRef } from "~/services/new-workspace/handle"
import { HandleRef, WritableHandleRef } from "~/services/new-workspace/handle"
import { WorkspaceProvider } from "~/services/new-workspace/provider"
import {
RESTCollectionChildrenView,
Expand Down Expand Up @@ -87,6 +87,10 @@ export class PersonalWorkspaceProviderService

private restCollectionState: Ref<{ state: HoppCollection[] }>

private issuedHandles: WritableHandleRef<
WorkspaceCollection | WorkspaceRequest
>[] = []

public constructor() {
super()

Expand Down Expand Up @@ -329,35 +333,44 @@ export class PersonalWorkspaceProviderService
platform: "rest",
})

return Promise.resolve(
E.right(
computed(() => {
if (
!isValidCollectionHandle(
parentCollectionHandle,
this.providerID,
"personal"
)
) {
return {
type: "invalid" as const,
reason: "COLLECTION_INVALIDATED" as const,
}
}
const handle: HandleRef<WorkspaceRequest> = computed(() => {
if (
!isValidCollectionHandle(
parentCollectionHandle,
this.providerID,
"personal"
)
) {
return {
type: "invalid" as const,
reason: "COLLECTION_INVALIDATED" as const,
}
}

return {
type: "ok",
data: {
providerID,
workspaceID,
collectionID,
requestID,
request: newRequest,
},
}
})
)
)
return {
type: "ok",
data: {
providerID,
workspaceID,
collectionID,
requestID,
request: newRequest,
},
}
})

const writableHandle = computed({
get() {
return handle.value
},
set(newValue) {
handle.value = newValue
},
})

this.issuedHandles.push(writableHandle)

return Promise.resolve(E.right(handle))
}

public removeRESTRequest(
Expand All @@ -377,6 +390,19 @@ export class PersonalWorkspaceProviderService

removeRESTRequest(collectionID, requestIndex, requestToRemove?.id)

for (const handle of this.issuedHandles) {
if (handle.value.type === "invalid") continue

if ("requestID" in handle.value.data) {
if (handle.value.data.requestID === requestID) {
handle.value = {
type: "invalid",
reason: "REQUEST_INVALIDATED",
}
}
}
}

return Promise.resolve(E.right(undefined))
}

Expand Down Expand Up @@ -647,35 +673,42 @@ export class PersonalWorkspaceProviderService
return Promise.resolve(E.left("REQUEST_NOT_FOUND" as const))
}

return Promise.resolve(
E.right(
computed(() => {
if (
!isValidWorkspaceHandle(
workspaceHandle,
this.providerID,
"personal"
)
) {
return {
type: "invalid" as const,
reason: "WORKSPACE_INVALIDATED" as const,
}
}
const handleRefData = ref({
type: "ok" as const,
data: {
providerID,
workspaceID,
collectionID,
requestID,
request,
},
})

return {
type: "ok",
data: {
providerID,
workspaceID,
collectionID,
requestID,
request,
},
}
})
)
)
const handle: HandleRef<WorkspaceRequest> = computed(() => {
if (
!isValidWorkspaceHandle(workspaceHandle, this.providerID, "personal")
) {
return {
type: "invalid" as const,
reason: "WORKSPACE_INVALIDATED" as const,
}
}

return handleRefData.value
})

const writableHandle = computed({
get() {
return handleRefData.value
},
set(newValue) {
handleRefData.value = newValue
},
})

this.issuedHandles.push(writableHandle)

return Promise.resolve(E.right(handle))
}

public getRESTCollectionChildrenView(
Expand Down
6 changes: 4 additions & 2 deletions packages/hoppscotch-common/src/services/tab/rest.ts
Expand Up @@ -29,17 +29,19 @@ export class RESTTabService extends TabService<HoppRESTDocument> {
lastActiveTabID: this.currentTabID.value,
orderedDocs: this.tabOrdering.value.map((tabID) => {
const tab = this.tabMap.get(tabID)! // tab ordering is guaranteed to have value for this key
const resolvedTabData = this.getResolvedTabData(tab)

return {
tabID: tab.id,
doc: {
...tab.document,
...this.getPersistedDocument(resolvedTabData.document),
response: null,
},
}
}),
}))

public getTabRefWithSaveContext(ctx: HoppRESTSaveContext) {
public getTabRefWithSaveContext(ctx: Partial<HoppRESTSaveContext>) {
for (const tab of this.tabMap.values()) {
// For `team-collection` request id can be considered unique
if (ctx?.originLocation === "team-collection") {
Expand Down

0 comments on commit 3c0af78

Please sign in to comment.