refactor: refactor collection to not use mapper (#80)

This commit is contained in:
Akash K
2023-04-11 15:09:32 +05:30
committed by GitHub
parent 5d1337f15d
commit c353d60ddc
7 changed files with 593 additions and 2604 deletions

View File

@@ -1,456 +0,0 @@
import {
graphqlCollectionStore,
navigateToFolderWithIndexPath,
restCollectionStore,
} from "@hoppscotch/common/newstore/collections"
import { createMapper } from "../../lib/sync/mapper"
import {
restCollectionsMapper,
collectionReorderOrMovingOperations,
restRequestsMapper,
} from "./collections.sync"
import { gqlCollectionsMapper, gqlRequestsMapper } from "./gqlCollections.sync"
function reorderItems(array: unknown[], from: number, to: number) {
const item = array.splice(from, 1)[0]
if (from < to) {
array.splice(to - 1, 0, item)
} else {
array.splice(to, 0, item)
}
}
type RequestType = "REST" | "GQL"
export function moveCollectionInMapper(
folderPath: string,
destinationPath?: string,
collectionType: RequestType = "REST"
) {
const indexes = folderPath.split("/")
indexes.pop()
const collectionPath = indexes.join("/")
const { collectionsMapper, requestsMapper, collectionStore } =
getMappersAndStoreByType(collectionType)
// Store the backend id of the folder to move for adding it to the destinationPath
const collectionToMoveBackendID =
collectionsMapper.getBackendIDByLocalID(folderPath)
// Remove the request from its current position
collectionsMapper.removeEntry(undefined, folderPath)
// We are assuming moveRequestInMapper is called after the item is moved in the store,
// so we'll fetch the index of the last added item + 1 to add to the mapper
// but in the case of the same parent, the destinationPath will change
// eg:
// 0. Collection 0
// 1. Collection 1
// in the above example, if we move Collection 0 to Collection 1 ( folderPath: 0, destinationPath: 1 ),
// the effective index of Collection 1, when using navigateToFolderWithIndexPath will be 0
// so we check if the moving is between same parent folders / collections and adds a workaround for this
const isSameParentPath =
getParentPathFromPath(folderPath) == getParentPathFromPath(destinationPath)
let changedDestinationPath: string | undefined
if (isSameParentPath) {
const lastFolderPathIndex = folderPath.split("/").pop()
const folderIndex = lastFolderPathIndex && parseInt(lastFolderPathIndex)
const lastDestinationPathIndex =
destinationPath && destinationPath.split("/").pop()
const destinationIndex =
destinationPath &&
lastDestinationPathIndex &&
parseInt(lastDestinationPathIndex)
if (
(folderIndex == 0 || folderIndex) &&
(destinationIndex == 0 || destinationIndex) &&
folderIndex < destinationIndex
) {
const destinationParentPath = getParentPathFromPath(destinationPath)
changedDestinationPath = destinationParentPath
? `${destinationParentPath}/${destinationIndex - 1}`
: `${destinationIndex - 1}`
}
}
const destinationFolder =
changedDestinationPath &&
navigateToFolderWithIndexPath(
collectionStore.value.state,
changedDestinationPath.split("/").map((pathIndex) => parseInt(pathIndex))
)
const destinationCollectionID =
destinationPath && collectionsMapper.getBackendIDByLocalID(destinationPath)
if (destinationFolder && collectionToMoveBackendID) {
const destinationIndex = destinationFolder.folders.length
const newPath = `${destinationPath}/${destinationIndex}`
collectionsMapper.addEntry(newPath, collectionToMoveBackendID)
changeParentForAllChildrenFromMapper(folderPath, newPath, collectionType)
collectionToMoveBackendID &&
collectionReorderOrMovingOperations.push({
sourceCollectionID: collectionToMoveBackendID,
destinationCollectionID,
reorderOperation: {
fromPath: folderPath,
toPath: `${changedDestinationPath}/${destinationIndex}`,
},
})
}
// destinationPath won't be there, when moving to the root
if (!destinationPath && collectionToMoveBackendID) {
const destinationIndex = collectionStore.value.state.length
const newPath = `${destinationIndex}`
collectionsMapper.addEntry(newPath, collectionToMoveBackendID)
changeParentForAllChildrenFromMapper(folderPath, newPath, collectionType)
}
reorderIndexesAfterEntryRemoval(
collectionPath,
collectionsMapper,
collectionType
)
reorderIndexesAfterEntryRemoval(
collectionPath,
requestsMapper,
collectionType
)
}
export function moveRequestInMapper(
requestIndex: number,
path: string,
destinationPath: string,
requestType: RequestType
) {
const { collectionStore, requestsMapper } =
getMappersAndStoreByType(requestType)
// Store the backend id of the request to move for adding it to the destinationPath
const requestToMoveBackendID = requestsMapper.getBackendIDByLocalID(
`${path}/${requestIndex}`
)
// Remove the request from its current position
requestsMapper.removeEntry(undefined, `${path}/${requestIndex}`)
reorderIndexesAfterEntryRemoval(path, requestsMapper, requestType)
// We are assuming moveRequestInMapper is called after the item is moved in the store,
// so we'll fetch the index of the last added item + 1 to add to the mapper
const destinationFolder = navigateToFolderWithIndexPath(
collectionStore.value.state,
destinationPath.split("/").map((pathIndex) => parseInt(pathIndex))
)
if (destinationFolder && requestToMoveBackendID) {
const destinationIndex = destinationFolder.requests.length
requestsMapper.addEntry(
`${destinationPath}/${destinationIndex}`,
requestToMoveBackendID
)
}
}
// we allow reordering in the same parent collection right now
export function reorderRequestsMapper(
requestIndex: number,
path: string,
nextRequestIndex: number,
requestType: RequestType
) {
const { requestsMapper } = getMappersAndStoreByType(requestType)
const directChildren = getDirectChildrenEntriesFromMapper(
path,
requestsMapper
)
reorderItems(directChildren, requestIndex, nextRequestIndex)
directChildren.forEach((item, index) => {
item[1] && requestsMapper.addEntry(`${path}/${index}`, item[1])
})
}
// we allow reordering in the same parent collection right now
export function reorderCollectionsInMapper(
collectionPath: string,
destinationCollectionPath: string,
requestType: RequestType
) {
const { requestsMapper, collectionsMapper } =
getMappersAndStoreByType(requestType)
const indexes = collectionPath.split("/")
indexes.pop()
const parentCollectionPath = indexes.join("/")
const directChildren = getDirectChildrenEntriesFromMapper(
parentCollectionPath,
collectionsMapper
)
const collectionIndex = collectionPath.split("/").pop()
const destinationIndex = destinationCollectionPath.split("/").pop()
collectionIndex &&
destinationIndex &&
reorderItems(
directChildren,
parentCollectionPath
? parseInt(collectionIndex)
: parseInt(collectionPath),
parentCollectionPath
? parseInt(destinationIndex)
: parseInt(destinationCollectionPath)
)
const previousCollectionEntries: Record<
string,
[string, string | undefined][]
> = {}
const previousRequestEntries: Record<string, [string, string | undefined][]> =
{}
directChildren.forEach(([path, backendID], index) => {
const newPath = parentCollectionPath
? `${parentCollectionPath}/${index}`
: `${index}`
const indexes = path.split("/")
const childIndex = indexes.pop()
if (childIndex && index != parseInt(childIndex)) {
backendID && collectionsMapper.addEntry(newPath, backendID)
const existingCollectionsOnNewPath = getChildrenEntriesFromMapper(
newPath,
collectionsMapper
)
const existingRequestsOnNewPath = getChildrenEntriesFromMapper(
newPath,
requestsMapper
)
previousCollectionEntries[newPath] = existingCollectionsOnNewPath
previousRequestEntries[newPath] = existingRequestsOnNewPath
removeAllChildCollectionsFromMapper(newPath, requestType)
removeAllChildRequestsFromMapper(newPath, requestType)
if (path in previousCollectionEntries && path in previousRequestEntries) {
previousCollectionEntries[path].forEach(([previousPath, backendID]) => {
const pattern = new RegExp(`^(${path})\/`)
const updatedPath = previousPath.replace(pattern, `${newPath}/`)
backendID && collectionsMapper.addEntry(updatedPath, backendID)
})
previousRequestEntries[path].forEach(([previousPath, backendID]) => {
const pattern = new RegExp(`^(${path})\/`)
const updatedPath = previousPath.replace(pattern, `${newPath}/`)
backendID && requestsMapper.addEntry(updatedPath, backendID)
})
} else {
changeParentForAllChildrenFromMapper(path, newPath, requestType)
}
}
})
}
export function removeAndReorderEntries(
localIndex: string,
collectionType: RequestType
) {
const { collectionsMapper, requestsMapper } =
getMappersAndStoreByType(collectionType)
// get the collectionPath from the localIndex
const indexes = localIndex.split("/")
indexes.pop()
const collectionPath = indexes.join("/")
collectionsMapper.removeEntry(undefined, localIndex)
removeAllChildCollectionsFromMapper(localIndex, collectionType)
removeAllChildRequestsFromMapper(localIndex, collectionType)
reorderIndexesAfterEntryRemoval(
collectionPath,
collectionsMapper,
collectionType
)
reorderIndexesAfterEntryRemoval(
collectionPath,
requestsMapper,
collectionType
)
}
export function removeAllChildRequestsFromMapper(
collectionPath: string,
requestType: RequestType
) {
const { requestsMapper } = getMappersAndStoreByType(requestType)
const childRequestMapperEntries = getChildrenEntriesFromMapper(
collectionPath,
requestsMapper
)
childRequestMapperEntries.forEach(([path]) => {
typeof path == "string" && requestsMapper.removeEntry(undefined, path)
})
}
export function removeAllChildCollectionsFromMapper(
collectionPath: string,
collectionType: RequestType
) {
const { collectionsMapper } = getMappersAndStoreByType(collectionType)
const childCollectionMapperEntries = getChildrenEntriesFromMapper(
collectionPath,
collectionsMapper
)
childCollectionMapperEntries.forEach(([path]) => {
typeof path == "string" && collectionsMapper.removeEntry(undefined, path)
})
}
export function changeParentForAllChildrenFromMapper(
currentParentPath: string,
newParentPath: string,
collectionType: RequestType
) {
const { collectionsMapper, requestsMapper } =
getMappersAndStoreByType(collectionType)
const childCollectionsMapperEntries = getChildrenEntriesFromMapper(
currentParentPath,
collectionsMapper
)
const childRequestsMapperEntries = getChildrenEntriesFromMapper(
currentParentPath,
requestsMapper
)
const pattern = new RegExp(`^(${currentParentPath})`)
childCollectionsMapperEntries.forEach(([path, backendID]) => {
const newPath =
typeof path == "string" && path.replace(pattern, newParentPath)
if (newPath && typeof backendID == "string") {
collectionsMapper.removeEntry(undefined, path)
collectionsMapper.addEntry(newPath, backendID)
}
})
childRequestsMapperEntries.forEach(([path, backendID]) => {
const newPath =
typeof path == "string" && path.replace(pattern, newParentPath)
if (newPath && typeof backendID == "string") {
requestsMapper.removeEntry(undefined, path)
requestsMapper.addEntry(newPath, backendID)
}
})
}
export function getChildrenEntriesFromMapper(
path: string,
mapper: ReturnType<typeof createMapper<string, string>>
) {
let mapperEntries = Array.from(mapper.getValue().entries())
// if there are no path( eg: "" ), all the entries are children, so return the entire mapperEntries without filtering
if (!path) return mapperEntries
mapperEntries = mapperEntries.filter((entry) => {
const pattern = new RegExp(`^${path}\/(\\w+)\/?.*$`)
return !!(typeof entry[0] == "string" && entry[0].match(pattern))
})
return mapperEntries
}
export function getDirectChildrenEntriesFromMapper(
path: string,
mapper: ReturnType<typeof createMapper<string, string>>
) {
let mapperEntries = Array.from(mapper.getValue().entries())
mapperEntries = mapperEntries.filter((entry) => {
const pattern = new RegExp(path ? `^${path}\/\\d+$` : `^\\d+$`)
return !!(typeof entry[0] == "string" && entry[0].match(pattern))
})
return mapperEntries
}
export function reorderIndexesAfterEntryRemoval(
pathToReorder: string,
mapper: ReturnType<typeof createMapper<string, string>>,
requestType: RequestType
) {
const directChildren = getDirectChildrenEntriesFromMapper(
pathToReorder,
mapper
)
directChildren.forEach(([path, backendID], index) => {
const indexes = path.split("/").map((index) => parseInt(index))
const childIndex = indexes.pop()
const collectionPath = indexes.join("/")
if (childIndex != index && backendID) {
const newPath = collectionPath ? `${collectionPath}/${index}` : `${index}`
mapper.removeEntry(undefined, path)
mapper.addEntry(newPath, backendID)
changeParentForAllChildrenFromMapper(path, newPath, requestType)
}
})
}
function getParentPathFromPath(path: string | undefined) {
const indexes = path ? path.split("/") : []
indexes.pop()
return indexes.join("/")
}
export function getMappersAndStoreByType(type: "GQL" | "REST") {
const isGQL = type == "GQL"
const collectionsMapper = isGQL ? gqlCollectionsMapper : restCollectionsMapper
const requestsMapper = isGQL ? gqlRequestsMapper : restRequestsMapper
const collectionStore = isGQL ? graphqlCollectionStore : restCollectionStore
return { collectionsMapper, requestsMapper, collectionStore }
}

View File

@@ -14,20 +14,7 @@ import {
runUserRequestMovedSubscription,
runUserRequestUpdatedSubscription,
} from "./collections.api"
import {
collectionReorderOrMovingOperations,
collectionsSyncer,
restCollectionsOperations,
restCollectionsMapper,
restRequestsMapper,
} from "./collections.sync"
import {
moveCollectionInMapper,
removeAndReorderEntries,
reorderIndexesAfterEntryRemoval,
reorderCollectionsInMapper,
getMappersAndStoreByType,
} from "./collections.mapper"
import { collectionsSyncer, getStoreByCollectionType } from "./collections.sync"
import * as E from "fp-ts/Either"
import {
@@ -57,6 +44,7 @@ import {
moveGraphqlRequest,
removeGraphqlRequest,
setGraphqlCollections,
restCollectionStore,
} from "@hoppscotch/common/newstore/collections"
import { runGQLSubscription } from "@hoppscotch/common/helpers/backend/GQLClient"
import {
@@ -64,10 +52,7 @@ import {
HoppGQLRequest,
HoppRESTRequest,
} from "@hoppscotch/data"
import {
gqlCollectionsOperations,
gqlCollectionsSyncer,
} from "./gqlCollections.sync"
import { gqlCollectionsSyncer } from "./gqlCollections.sync"
import { ReqType } from "../../api/generated/graphql"
function initCollectionsSync() {
@@ -77,14 +62,14 @@ function initCollectionsSync() {
gqlCollectionsSyncer.startStoreSync()
loadUserRootCollections("REST")
loadUserRootCollections("GQL")
loadUserCollections("REST")
loadUserCollections("GQL")
// TODO: test & make sure the auth thing is working properly
currentUser$.subscribe(async (user) => {
if (user) {
loadUserRootCollections("REST")
loadUserRootCollections("GQL")
loadUserCollections("REST")
loadUserCollections("GQL")
}
})
@@ -121,6 +106,7 @@ function exportedCollectionToHoppCollection(
const restCollection = collection as ExportedUserCollectionREST
return {
id: restCollection.id,
v: 1,
name: restCollection.name,
folders: restCollection.folders.map((folder) =>
@@ -128,6 +114,7 @@ function exportedCollectionToHoppCollection(
),
requests: restCollection.requests.map(
({
id,
v,
auth,
body,
@@ -139,6 +126,7 @@ function exportedCollectionToHoppCollection(
preRequestScript,
testScript,
}) => ({
id,
v,
auth,
body,
@@ -156,49 +144,26 @@ function exportedCollectionToHoppCollection(
const gqlCollection = collection as ExportedUserCollectionGQL
return {
id: gqlCollection.id,
v: 1,
name: gqlCollection.name,
folders: gqlCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType)
),
requests: gqlCollection.requests.map(({ v, auth, headers, name }) => ({
v,
auth,
headers,
name,
})) as HoppGQLRequest[],
requests: gqlCollection.requests.map(
({ v, auth, headers, name, id }) => ({
id,
v,
auth,
headers,
name,
})
) as HoppGQLRequest[],
}
}
}
function addMapperEntriesForExportedCollection(
collection: ExportedUserCollectionREST | ExportedUserCollectionGQL,
localPath: string,
collectionType: "REST" | "GQL"
) {
const { collectionsMapper, requestsMapper } =
getMappersAndStoreByType(collectionType)
if (collection.id) {
collectionsMapper.addEntry(localPath, collection.id)
collection.folders.forEach((folder, index) => {
addMapperEntriesForExportedCollection(
folder,
`${localPath}/${index}`,
collectionType
)
})
collection.requests.forEach((request, index) => {
const requestID = request.id
requestID && requestsMapper.addEntry(`${localPath}/${index}`, requestID)
})
}
}
async function loadUserRootCollections(collectionType: "REST" | "GQL") {
async function loadUserCollections(collectionType: "REST" | "GQL") {
const res = await exportUserCollectionsToJSON(
undefined,
collectionType == "REST" ? ReqType.Rest : ReqType.Gql
@@ -233,14 +198,6 @@ async function loadUserRootCollections(collectionType: "REST" | "GQL") {
) as HoppCollection<HoppGQLRequest>
)
)
exportedCollections.forEach((collection, index) =>
addMapperEntriesForExportedCollection(
collection,
`${index}`,
collectionType
)
)
})
}
}
@@ -284,14 +241,14 @@ function setupUserCollectionCreatedSubscription() {
if (E.isRight(res)) {
const collectionType = res.right.userCollectionCreated.type
const { collectionsMapper, collectionStore } =
getMappersAndStoreByType(collectionType)
const { collectionStore } = getStoreByCollectionType(collectionType)
const userCollectionBackendID = res.right.userCollectionCreated.id
const parentCollectionID = res.right.userCollectionCreated.parent?.id
const userCollectionLocalID = collectionsMapper.getLocalIDByBackendID(
userCollectionBackendID
const userCollectionLocalID = getCollectionPathFromCollectionID(
userCollectionBackendID,
collectionStore.value.state
)
// collection already exists in store ( this instance created it )
@@ -301,7 +258,10 @@ function setupUserCollectionCreatedSubscription() {
const parentCollectionPath =
parentCollectionID &&
collectionsMapper.getLocalIDByBackendID(parentCollectionID)
getCollectionPathFromCollectionID(
parentCollectionID,
collectionStore.value.state
)
// only folders will have parent collection id
if (parentCollectionID && parentCollectionPath) {
@@ -325,10 +285,9 @@ function setupUserCollectionCreatedSubscription() {
if (parentCollection) {
const folderIndex = parentCollection.folders.length - 1
collectionsMapper.addEntry(
`${parentCollectionPath}/${folderIndex}`,
userCollectionBackendID
)
const addedFolder = parentCollection.folders[folderIndex]
addedFolder.id = userCollectionBackendID
}
})
} else {
@@ -349,7 +308,9 @@ function setupUserCollectionCreatedSubscription() {
})
const localIndex = collectionStore.value.state.length - 1
collectionsMapper.addEntry(`${localIndex}`, userCollectionBackendID)
const addedCollection = collectionStore.value.state[localIndex]
addedCollection.id = userCollectionBackendID
})
}
}
@@ -366,11 +327,13 @@ function setupUserCollectionUpdatedSubscription() {
if (E.isRight(res)) {
const collectionType = res.right.userCollectionUpdated.type
const { collectionsMapper } = getMappersAndStoreByType(collectionType)
const { collectionStore } = getStoreByCollectionType(collectionType)
const updatedCollectionBackendID = res.right.userCollectionUpdated.id
const updatedCollectionLocalPath =
collectionsMapper.getLocalIDByBackendID(updatedCollectionBackendID)
const updatedCollectionLocalPath = getCollectionPathFromCollectionID(
updatedCollectionBackendID,
collectionStore.value.state
)
const isFolder =
updatedCollectionLocalPath &&
@@ -415,37 +378,25 @@ function setupUserCollectionMovedSubscription() {
if (E.isRight(res)) {
const movedMetadata = res.right.userCollectionMoved
const sourcePath = restCollectionsMapper.getLocalIDByBackendID(
movedMetadata.id
const sourcePath = getCollectionPathFromCollectionID(
movedMetadata.id,
restCollectionStore.value.state
)
let destinationPath: string | undefined
if (movedMetadata.parent?.id) {
destinationPath = restCollectionsMapper.getLocalIDByBackendID(
movedMetadata.parent?.id
)
destinationPath =
getCollectionPathFromCollectionID(
movedMetadata.parent?.id,
restCollectionStore.value.state
) ?? undefined
}
const hasAlreadyHappened = hasReorderingOrMovingAlreadyHappened(
{
sourceCollectionID: movedMetadata.id,
destinationCollectionID: movedMetadata.parent?.id,
sourcePath,
destinationPath,
},
"MOVING"
)
if (!hasAlreadyHappened) {
sourcePath &&
runDispatchWithOutSyncing(() => {
moveRESTFolder(sourcePath, destinationPath ?? null)
})
sourcePath &&
moveCollectionInMapper(sourcePath, destinationPath, "REST")
}
sourcePath &&
runDispatchWithOutSyncing(() => {
moveRESTFolder(sourcePath, destinationPath ?? null)
})
}
})
@@ -461,28 +412,13 @@ function setupUserCollectionRemovedSubscription() {
const removedCollectionBackendID = res.right.userCollectionRemoved.id
const collectionType = res.right.userCollectionRemoved.type
const { collectionsMapper } = getMappersAndStoreByType(collectionType)
const { collectionStore } = getStoreByCollectionType(collectionType)
const collectionsOperations =
collectionType == "REST"
? restCollectionsOperations
: gqlCollectionsOperations
const removedCollectionLocalPath =
collectionsMapper.getLocalIDByBackendID(removedCollectionBackendID)
// TODO: seperate operations for rest and gql
const isInOperations = !!collectionsOperations.find(
(operation) =>
operation.type == "COLLECTION_REMOVED" &&
operation.collectionBackendID == removedCollectionBackendID
const removedCollectionLocalPath = getCollectionPathFromCollectionID(
removedCollectionBackendID,
collectionStore.value.state
)
// the collection is already removed
if (!removedCollectionLocalPath || isInOperations) {
return
}
const isFolder =
removedCollectionLocalPath &&
removedCollectionLocalPath.split("/").length > 1
@@ -502,9 +438,6 @@ function setupUserCollectionRemovedSubscription() {
: removeGraphqlCollection(parseInt(removedCollectionLocalPath))
})
}
removedCollectionLocalPath &&
removeAndReorderEntries(removedCollectionLocalPath, collectionType)
}
})
@@ -523,40 +456,25 @@ function setupUserCollectionOrderUpdatedSubscription() {
const sourceCollectionID = userCollection.id
const destinationCollectionID = nextUserCollection?.id
const sourcePath =
restCollectionsMapper.getLocalIDByBackendID(sourceCollectionID)
const sourcePath = getCollectionPathFromCollectionID(
sourceCollectionID,
restCollectionStore.value.state
)
let destinationPath: string | undefined
let destinationPath: string | null | undefined
if (destinationCollectionID) {
destinationPath = restCollectionsMapper.getLocalIDByBackendID(
destinationCollectionID
destinationPath = getCollectionPathFromCollectionID(
destinationCollectionID,
restCollectionStore.value.state
)
}
const hasAlreadyHappened = hasReorderingOrMovingAlreadyHappened(
{
sourceCollectionID,
destinationCollectionID,
sourcePath,
destinationPath,
},
"REORDERING"
)
if (!hasAlreadyHappened) {
runDispatchWithOutSyncing(() => {
if (
sourcePath &&
destinationPath &&
sourceCollectionID &&
destinationCollectionID
) {
updateRESTCollectionOrder(sourcePath, destinationPath)
reorderCollectionsInMapper(sourcePath, destinationPath, "REST")
}
})
}
runDispatchWithOutSyncing(() => {
if (sourcePath) {
updateRESTCollectionOrder(sourcePath, destinationPath ?? null)
}
})
}
})
@@ -575,18 +493,21 @@ function setupUserRequestCreatedSubscription() {
const requestType = res.right.userRequestCreated.type
const { collectionsMapper, requestsMapper, collectionStore } =
getMappersAndStoreByType(requestType)
const { collectionStore } = getStoreByCollectionType(requestType)
const hasAlreadyHappened =
!!requestsMapper.getLocalIDByBackendID(requestID)
const hasAlreadyHappened = getRequestPathFromRequestID(
requestID,
collectionStore.value.state
)
if (hasAlreadyHappened) {
if (!!hasAlreadyHappened) {
return
}
const collectionPath =
collectionsMapper.getLocalIDByBackendID(collectionID)
const collectionPath = getCollectionPathFromCollectionID(
collectionID,
collectionStore.value.state
)
if (collectionID && collectionPath) {
runDispatchWithOutSyncing(() => {
@@ -599,10 +520,11 @@ function setupUserRequestCreatedSubscription() {
collectionPath.split("/").map((index) => parseInt(index))
)
const requestPath =
target && `${collectionPath}/${target?.requests.length - 1}`
const targetRequest = target?.requests[target?.requests.length - 1]
requestPath && requestsMapper.addEntry(requestPath, requestID)
if (targetRequest) {
targetRequest.id = requestID
}
})
}
}
@@ -619,31 +541,28 @@ function setupUserRequestUpdatedSubscription() {
if (E.isRight(res)) {
const requestType = res.right.userRequestUpdated.type
const { requestsMapper, collectionsMapper } =
getMappersAndStoreByType(requestType)
const { collectionStore } = getStoreByCollectionType(requestType)
const requestPath = requestsMapper.getLocalIDByBackendID(
res.right.userRequestUpdated.id
const requestPath = getRequestPathFromRequestID(
res.right.userRequestUpdated.id,
collectionStore.value.state
)
const indexes = requestPath?.split("/")
const requestIndex = indexes && indexes[indexes?.length - 1]
const requestParentPath = collectionsMapper.getLocalIDByBackendID(
res.right.userRequestUpdated.collectionID
)
const collectionPath = requestPath?.collectionPath
const requestIndex = requestPath?.requestIndex
requestIndex &&
requestParentPath &&
;(requestIndex || requestIndex == 0) &&
collectionPath &&
runDispatchWithOutSyncing(() => {
requestType == "REST"
? editRESTRequest(
requestParentPath,
parseInt(requestIndex),
collectionPath,
requestIndex,
JSON.parse(res.right.userRequestUpdated.request)
)
: editGraphqlRequest(
requestParentPath,
parseInt(requestIndex),
collectionPath,
requestIndex,
JSON.parse(res.right.userRequestUpdated.request)
)
})
@@ -659,40 +578,58 @@ function setupUserRequestMovedSubscription() {
userRequestMoved$.subscribe((res) => {
if (E.isRight(res)) {
const requestType = res.right.userRequestMoved.request.type
const { request, nextRequest } = res.right.userRequestMoved
const { collectionsMapper } = getMappersAndStoreByType(requestType)
const {
collectionID: destinationCollectionID,
id: sourceRequestID,
type: requestType,
} = request
const requestID = res.right.userRequestMoved.request.id
const requestIndex = getRequestIndexFromRequestID(requestID)
const { collectionStore } = getStoreByCollectionType(requestType)
const sourceCollectionPath = getCollectionPathFromRequestID(requestID)
const destinationCollectionID =
res.right.userRequestMoved.request.collectionID
const destinationCollectionPath = collectionsMapper.getLocalIDByBackendID(
destinationCollectionID
const sourceRequestPath = getRequestPathFromRequestID(
sourceRequestID,
collectionStore.value.state
)
const nextRequest = res.right.userRequestMoved.nextRequest
const destinationCollectionPath = getCollectionPathFromCollectionID(
destinationCollectionID,
collectionStore.value.state
)
const destinationRequestIndex = destinationCollectionPath
? (() => {
const requestsLength = navigateToFolderWithIndexPath(
collectionStore.value.state,
destinationCollectionPath
.split("/")
.map((index) => parseInt(index))
)?.requests.length
return requestsLength || requestsLength == 0
? requestsLength - 1
: undefined
})()
: undefined
// there is no nextRequest, so request is moved
if (
requestIndex &&
sourceCollectionPath &&
(destinationRequestIndex || destinationRequestIndex == 0) &&
destinationCollectionPath &&
sourceRequestPath &&
!nextRequest
) {
runDispatchWithOutSyncing(() => {
requestType == "REST"
? moveRESTRequest(
sourceCollectionPath,
parseInt(requestIndex),
sourceRequestPath.collectionPath,
sourceRequestPath.requestIndex,
destinationCollectionPath
)
: moveGraphqlRequest(
sourceCollectionPath,
parseInt(requestIndex),
sourceRequestPath.collectionPath,
sourceRequestPath.requestIndex,
destinationCollectionPath
)
})
@@ -700,21 +637,37 @@ function setupUserRequestMovedSubscription() {
// there is nextRequest, so request is reordered
if (
requestIndex &&
sourceCollectionPath &&
(destinationRequestIndex || destinationRequestIndex == 0) &&
destinationCollectionPath &&
nextRequest &&
// we don't have request reordering for graphql yet
requestType == "REST"
) {
const nextRequestIndex = getRequestIndexFromRequestID(nextRequest.id)
const { collectionID: nextCollectionID, id: nextRequestID } =
nextRequest
const nextCollectionPath =
getCollectionPathFromCollectionID(
nextCollectionID,
collectionStore.value.state
) ?? undefined
const nextRequestIndex = nextCollectionPath
? getRequestIndex(
nextRequestID,
nextCollectionPath,
collectionStore.value.state
)
: undefined
nextRequestIndex &&
nextCollectionPath &&
sourceRequestPath &&
runDispatchWithOutSyncing(() => {
updateRESTRequestOrder(
parseInt(requestIndex),
parseInt(nextRequestIndex),
destinationCollectionPath
sourceRequestPath?.requestIndex,
nextRequestIndex,
nextCollectionPath
)
})
}
@@ -732,33 +685,27 @@ function setupUserRequestDeletedSubscription() {
if (E.isRight(res)) {
const requestType = res.right.userRequestDeleted.type
const { requestsMapper, collectionsMapper } =
getMappersAndStoreByType(requestType)
const { collectionStore } = getStoreByCollectionType(requestType)
const deletedRequestPath = requestsMapper.getLocalIDByBackendID(
res.right.userRequestDeleted.id
const deletedRequestPath = getRequestPathFromRequestID(
res.right.userRequestDeleted.id,
collectionStore.value.state
)
const indexes = deletedRequestPath?.split("/")
const requestIndex = indexes && indexes[indexes?.length - 1]
const requestParentPath = collectionsMapper.getLocalIDByBackendID(
res.right.userRequestDeleted.collectionID
)
requestIndex &&
requestParentPath &&
;(deletedRequestPath?.requestIndex ||
deletedRequestPath?.requestIndex == 0) &&
deletedRequestPath.collectionPath &&
runDispatchWithOutSyncing(() => {
requestType == "REST"
? removeRESTRequest(requestParentPath, parseInt(requestIndex))
: removeGraphqlRequest(requestParentPath, parseInt(requestIndex))
? removeRESTRequest(
deletedRequestPath.collectionPath,
deletedRequestPath.requestIndex
)
: removeGraphqlRequest(
deletedRequestPath.collectionPath,
deletedRequestPath.requestIndex
)
})
deletedRequestPath &&
reorderIndexesAfterEntryRemoval(
deletedRequestPath,
requestsMapper,
requestType
)
}
})
@@ -769,56 +716,74 @@ export const def: CollectionsPlatformDef = {
initCollectionsSync,
}
function getRequestIndexFromRequestID(requestID: string) {
const requestPath = restRequestsMapper.getLocalIDByBackendID(requestID)
function getCollectionPathFromCollectionID(
collectionID: string,
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
parentPath?: string
): string | null {
for (const collectionIndex in collections) {
if (collections[collectionIndex].id == collectionID) {
return parentPath
? `${parentPath}/${collectionIndex}`
: `${collectionIndex}`
} else {
const collectionPath = getCollectionPathFromCollectionID(
collectionID,
collections[collectionIndex].folders,
parentPath ? `${parentPath}/${collectionIndex}` : `${collectionIndex}`
)
/**
* requestPath is in the form collectionPath/requestIndex,
* so to get requestIndex we just split the requestPath with / and get the last element
*/
const requestPathIndexes = requestPath?.split("/")
const requestIndex =
requestPathIndexes && requestPathIndexes[requestPathIndexes?.length - 1]
if (collectionPath) return collectionPath
}
}
return null
}
function getRequestPathFromRequestID(
requestID: string,
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[],
parentPath?: string
): { collectionPath: string; requestIndex: number } | null {
for (const collectionIndex in collections) {
const requestIndex = collections[collectionIndex].requests.findIndex(
(request) => request.id == requestID
)
if (requestIndex != -1) {
return {
collectionPath: parentPath
? `${parentPath}/${collectionIndex}`
: `${collectionIndex}`,
requestIndex,
}
} else {
const requestPath = getRequestPathFromRequestID(
requestID,
collections[collectionIndex].folders,
parentPath ? `${parentPath}/${collectionIndex}` : `${collectionIndex}`
)
if (requestPath) return requestPath
}
}
return null
}
function getRequestIndex(
requestID: string,
parentCollectionPath: string,
collections: HoppCollection<HoppRESTRequest | HoppGQLRequest>[]
) {
const collection = navigateToFolderWithIndexPath(
collections,
parentCollectionPath?.split("/").map((index) => parseInt(index))
)
const requestIndex = collection?.requests.findIndex(
(request) => request.id == requestID
)
return requestIndex
}
function getCollectionPathFromRequestID(requestID: string) {
const requestPath = restRequestsMapper.getLocalIDByBackendID(requestID)
const requestPathIndexes = requestPath?.split("/")
// requestIndex will be the last element, remove it
requestPathIndexes?.pop()
return requestPathIndexes?.join("/")
}
function hasReorderingOrMovingAlreadyHappened(
incomingOperation: {
sourceCollectionID: string
destinationCollectionID: string | undefined
sourcePath: string | undefined
destinationPath: string | undefined
},
type: "REORDERING" | "MOVING"
) {
const {
sourcePath,
sourceCollectionID,
destinationCollectionID,
destinationPath,
} = incomingOperation
// TODO: implement this as a module
// Something like, SyncOperations.hasAlreadyHappened( type: "REORDER_COLLECTIONS", payload )
return !!collectionReorderOrMovingOperations.find((reorderOperation) =>
reorderOperation.sourceCollectionID == sourceCollectionID &&
reorderOperation.destinationCollectionID == destinationCollectionID &&
type == "MOVING"
? reorderOperation.reorderOperation.fromPath == destinationPath
: reorderOperation.reorderOperation.fromPath == sourcePath &&
type == "MOVING"
? reorderOperation.reorderOperation.toPath == sourcePath
: reorderOperation.reorderOperation.toPath == destinationPath
)
}

View File

@@ -1,8 +1,7 @@
import {
graphqlCollectionStore,
navigateToFolderWithIndexPath,
removeGraphqlCollection,
removeRESTCollection,
removeRESTRequest,
removeDuplicateRESTCollectionOrFolder,
restCollectionStore,
} from "@hoppscotch/common/newstore/collections"
import {
@@ -12,7 +11,7 @@ import {
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
import { getSyncInitFunction, runDispatchWithOutSyncing } from "../../lib/sync"
import { getSyncInitFunction } from "../../lib/sync"
import { StoreSyncDefinitionOf } from "../../lib/sync"
import { createMapper } from "../../lib/sync/mapper"
@@ -30,15 +29,6 @@ import {
} from "./collections.api"
import * as E from "fp-ts/Either"
import {
removeAndReorderEntries,
moveCollectionInMapper,
reorderIndexesAfterEntryRemoval,
reorderCollectionsInMapper,
reorderRequestsMapper,
moveRequestInMapper,
} from "./collections.mapper"
import { gqlCollectionsMapper } from "./gqlCollections.sync"
// restCollectionsMapper uses the collectionPath as the local identifier
export const restCollectionsMapper = createMapper<string, string>()
@@ -61,7 +51,9 @@ const recursivelySyncCollections = async (
if (E.isRight(res)) {
parentCollectionID = res.right.createRESTRootUserCollection.id
restCollectionsMapper.addEntry(collectionPath, parentCollectionID)
collection.id = parentCollectionID
removeDuplicateRESTCollectionOrFolder(parentCollectionID, collectionPath)
} else {
parentCollectionID = undefined
}
@@ -74,13 +66,19 @@ const recursivelySyncCollections = async (
if (E.isRight(res)) {
const childCollectionId = res.right.createRESTChildUserCollection.id
restCollectionsMapper.addEntry(collectionPath, childCollectionId)
collection.id = childCollectionId
removeDuplicateRESTCollectionOrFolder(
childCollectionId,
`${collectionPath}`
)
}
}
// create the requests
if (parentCollectionID) {
collection.requests.forEach(async (request, index) => {
collection.requests.forEach(async (request) => {
const res =
parentCollectionID &&
(await createRESTUserRequest(
@@ -91,7 +89,8 @@ const recursivelySyncCollections = async (
if (res && E.isRight(res)) {
const requestId = res.right.createRESTUserRequest.id
restRequestsMapper.addEntry(`${collectionPath}/${index}`, requestId)
request.id = requestId
}
})
}
@@ -143,109 +142,101 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
const lastCreatedCollectionIndex =
restCollectionStore.value.state.length - 1
await recursivelySyncCollections(
collection,
`${lastCreatedCollectionIndex}`
)
removeDuplicateCollectionsFromStore("REST")
recursivelySyncCollections(collection, `${lastCreatedCollectionIndex}`)
},
async removeCollection({ collectionIndex }) {
const backendIdentifier = restCollectionsMapper.getBackendIDByLocalID(
`${collectionIndex}`
)
if (backendIdentifier) {
restCollectionsOperations.push({
collectionBackendID: backendIdentifier,
type: "COLLECTION_REMOVED",
status: "pending",
})
await deleteUserCollection(backendIdentifier)
removeAndReorderEntries(`${collectionIndex}`, "REST")
async removeCollection({ collectionID }) {
if (collectionID) {
await deleteUserCollection(collectionID)
}
},
editCollection({ partialCollection: collection, collectionIndex }) {
const backendIdentifier = restCollectionsMapper.getBackendIDByLocalID(
`${collectionIndex}`
)
const collectionID = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
[collectionIndex]
)?.id
if (backendIdentifier && collection.name) {
renameUserCollection(backendIdentifier, collection.name)
if (collectionID && collection.name) {
renameUserCollection(collectionID, collection.name)
}
},
async addFolder({ name, path }) {
const parentCollectionBackendID =
restCollectionsMapper.getBackendIDByLocalID(path)
const parentCollection = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const parentCollectionBackendID = parentCollection?.id
if (parentCollectionBackendID) {
// TODO: remove this replaceAll thing when updating the mapper
const foldersLength = parentCollection.folders.length
const res = await createRESTChildUserCollection(
name,
parentCollectionBackendID
)
// after the folder is created add the path of the folder with its backend id to the mapper
if (E.isRight(res)) {
const folderBackendID = res.right.createRESTChildUserCollection.id
const parentCollection = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const { id } = res.right.createRESTChildUserCollection
if (parentCollection && parentCollection.folders.length > 0) {
const folderIndex = parentCollection.folders.length - 1
restCollectionsMapper.addEntry(
`${path}/${folderIndex}`,
folderBackendID
if (foldersLength) {
parentCollection.folders[foldersLength - 1].id = id
removeDuplicateRESTCollectionOrFolder(
id,
`${path}/${foldersLength - 1}`
)
}
}
}
},
editFolder({ folder, path }) {
const folderBackendId = restCollectionsMapper.getBackendIDByLocalID(
`${path}`
)
const folderID = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)?.id
const folderName = folder.name
if (folderBackendId && folderName) {
renameUserCollection(folderBackendId, folderName)
if (folderID && folderName) {
renameUserCollection(folderID, folderName)
}
},
async removeFolder({ path }) {
const folderBackendId = restCollectionsMapper.getBackendIDByLocalID(
`${path}`
)
if (folderBackendId) {
await deleteUserCollection(folderBackendId)
removeAndReorderEntries(path, "REST")
async removeFolder({ folderID }) {
if (folderID) {
await deleteUserCollection(folderID)
}
},
async moveFolder({ destinationPath, path }) {
const sourceCollectionBackendID =
restCollectionsMapper.getBackendIDByLocalID(path)
const { newSourcePath, newDestinationPath } = getPathsAfterMoving(
path,
destinationPath ?? undefined
)
const destinationCollectionBackendID = destinationPath
? restCollectionsMapper.getBackendIDByLocalID(destinationPath)
: undefined
if (newSourcePath) {
const sourceCollectionID = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
newSourcePath.split("/").map((index) => parseInt(index))
)?.id
if (sourceCollectionBackendID) {
await moveUserCollection(
sourceCollectionBackendID,
destinationCollectionBackendID
)
const destinationCollectionID = destinationPath
? newDestinationPath &&
navigateToFolderWithIndexPath(
restCollectionStore.value.state,
newDestinationPath.split("/").map((index) => parseInt(index))
)?.id
: undefined
moveCollectionInMapper(path, destinationPath ?? undefined, "REST")
if (sourceCollectionID) {
await moveUserCollection(sourceCollectionID, destinationCollectionID)
}
}
},
editRequest({ path, requestIndex, requestNew }) {
const requestPath = `${path}/${requestIndex}`
const request = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)?.requests[requestIndex]
const requestBackendID =
restRequestsMapper.getBackendIDByLocalID(requestPath)
const requestBackendID = request?.id
if (requestBackendID) {
editUserRequest(
@@ -256,63 +247,37 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
}
},
async saveRequestAs({ path, request }) {
const parentCollectionBackendID =
restCollectionsMapper.getBackendIDByLocalID(path)
const folder = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const parentCollectionBackendID = folder?.id
if (parentCollectionBackendID) {
const newRequest = folder.requests[folder.requests.length - 1]
const res = await createRESTUserRequest(
(request as HoppRESTRequest).name,
JSON.stringify(request),
parentCollectionBackendID
)
const existingPath =
E.isRight(res) &&
restRequestsMapper.getLocalIDByBackendID(
res.right.createRESTUserRequest.id
if (E.isRight(res)) {
const { id } = res.right.createRESTUserRequest
newRequest.id = id
removeDuplicateRESTCollectionOrFolder(
id,
`${path}/${folder.requests.length - 1}`,
"request"
)
// remove the request if it is already existing ( can happen when the subscription fired before the mutation is resolved )
if (existingPath) {
const indexes = existingPath.split("/")
const existingRequestIndex = indexes.pop()
const existingRequestParentPath = indexes.join("/")
runDispatchWithOutSyncing(() => {
existingRequestIndex &&
removeRESTRequest(
existingRequestParentPath,
parseInt(existingRequestIndex)
)
})
}
const parentCollection = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
if (parentCollection) {
const lastCreatedRequestIndex = parentCollection.requests.length - 1
if (E.isRight(res)) {
restRequestsMapper.addEntry(
`${path}/${lastCreatedRequestIndex}`,
res.right.createRESTUserRequest.id
)
}
}
}
},
async removeRequest({ path, requestIndex }) {
const requestPath = `${path}/${requestIndex}`
const requestBackendID =
restRequestsMapper.getBackendIDByLocalID(requestPath)
if (requestBackendID) {
await deleteUserRequest(requestBackendID)
restRequestsMapper.removeEntry(requestPath)
reorderIndexesAfterEntryRemoval(path, restRequestsMapper, "REST")
async removeRequest({ requestID }) {
if (requestID) {
await deleteUserRequest(requestID)
}
},
moveRequest({ destinationPath, path, requestIndex }) {
@@ -331,47 +296,74 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
requestIndex,
destinationCollectionPath,
destinationCollectionPath,
destinationRequestIndex
destinationRequestIndex ?? undefined
)
},
async updateCollectionOrder({
collectionIndex: collectionPath,
destinationCollectionIndex: destinationCollectionPath,
}) {
const sourceBackendID =
restCollectionsMapper.getBackendIDByLocalID(collectionPath)
const collections = restCollectionStore.value.state
const destinationBackendID = restCollectionsMapper.getBackendIDByLocalID(
destinationCollectionPath
)
const sourcePathIndexes = getParentPathIndexesFromPath(collectionPath)
const sourceCollectionIndex = getCollectionIndexFromPath(collectionPath)
if (sourceBackendID) {
collectionReorderOrMovingOperations.push({
sourceCollectionID: sourceBackendID,
destinationCollectionID: destinationBackendID,
reorderOperation: {
fromPath: `${parseInt(destinationCollectionPath) - 1}`,
toPath: destinationCollectionPath,
},
})
const destinationCollectionIndex = !!destinationCollectionPath
? getCollectionIndexFromPath(destinationCollectionPath)
: undefined
await updateUserCollectionOrder(sourceBackendID, destinationBackendID)
let updatedCollectionIndexs:
| [newSourceIndex: number, newDestinationIndex: number | undefined]
| undefined
const currentSourcePath =
restCollectionsMapper.getLocalIDByBackendID(sourceBackendID)
const hasAlreadyHappened = !!(
currentSourcePath == `${parseInt(destinationCollectionPath) - 1}`
if (
(sourceCollectionIndex || sourceCollectionIndex == 0) &&
(destinationCollectionIndex || destinationCollectionIndex == 0)
) {
updatedCollectionIndexs = getIndexesAfterReorder(
sourceCollectionIndex,
destinationCollectionIndex
)
} else if (sourceCollectionIndex || sourceCollectionIndex == 0) {
if (sourcePathIndexes.length == 0) {
// we're reordering root collections
updatedCollectionIndexs = [collections.length - 1, undefined]
} else {
const sourceCollection = navigateToFolderWithIndexPath(collections, [
...sourcePathIndexes,
])
if (!hasAlreadyHappened) {
reorderCollectionsInMapper(
collectionPath,
destinationCollectionPath,
"REST"
)
if (sourceCollection && sourceCollection.folders.length > 0) {
updatedCollectionIndexs = [
sourceCollection.folders.length - 1,
undefined,
]
}
}
}
const sourceCollectionID =
updatedCollectionIndexs &&
navigateToFolderWithIndexPath(collections, [
...sourcePathIndexes,
updatedCollectionIndexs[0],
])?.id
const destinationCollectionID =
updatedCollectionIndexs &&
(updatedCollectionIndexs[1] || updatedCollectionIndexs[1] == 0)
? navigateToFolderWithIndexPath(collections, [
...sourcePathIndexes,
updatedCollectionIndexs[1],
])?.id
: undefined
if (sourceCollectionID) {
await updateUserCollectionOrder(
sourceCollectionID,
destinationCollectionID
)
}
},
}
@@ -382,28 +374,51 @@ export const collectionsSyncer = getSyncInitFunction(
getSettingSubject("syncCollections")
)
async function moveOrReorderRequests(
export async function moveOrReorderRequests(
requestIndex: number,
path: string,
destinationPath: string,
nextRequestIndex?: number
nextRequestIndex?: number,
requestType: "REST" | "GQL" = "REST"
) {
const sourceCollectionBackendID =
restCollectionsMapper.getBackendIDByLocalID(path)
const destinationCollectionBackendID =
restCollectionsMapper.getBackendIDByLocalID(destinationPath)
const { collectionStore } = getStoreByCollectionType(requestType)
const requestBackendID = restRequestsMapper.getBackendIDByLocalID(
`${path}/${requestIndex}`
const sourceCollectionBackendID = navigateToFolderWithIndexPath(
collectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)?.id
const destinationCollection = navigateToFolderWithIndexPath(
collectionStore.value.state,
destinationPath.split("/").map((index) => parseInt(index))
)
const destinationCollectionBackendID = destinationCollection?.id
let requestBackendID: string | undefined
let nextRequestBackendID: string | undefined
// we only need this for reordering requests, not for moving requests
if (nextRequestIndex) {
nextRequestBackendID = restRequestsMapper.getBackendIDByLocalID(
`${destinationPath}/${nextRequestIndex}`
// reordering
const [newRequestIndex, newDestinationIndex] = getIndexesAfterReorder(
requestIndex,
nextRequestIndex
)
requestBackendID =
destinationCollection?.requests[newRequestIndex]?.id ?? undefined
nextRequestBackendID =
destinationCollection?.requests[newDestinationIndex]?.id ?? undefined
} else {
// moving
const requests = destinationCollection?.requests
requestBackendID =
requests && requests.length > 0
? requests[requests.length - 1]?.id
: undefined
}
if (
@@ -417,46 +432,112 @@ async function moveOrReorderRequests(
requestBackendID,
nextRequestBackendID
)
if (nextRequestBackendID && nextRequestIndex) {
reorderRequestsMapper(requestIndex, path, nextRequestIndex, "REST")
} else {
moveRequestInMapper(requestIndex, path, destinationPath, "REST")
}
}
}
export function removeDuplicateCollectionsFromStore(
collectionType: "REST" | "GQL"
) {
const collectionsMapper =
collectionType === "REST" ? restCollectionsMapper : gqlCollectionsMapper
const mapperEntries = Array.from(collectionsMapper.getValue().entries())
const seenBackendIDs = new Set<string>()
const localIDsToRemove = new Set<string>()
mapperEntries.forEach(([localID, backendID]) => {
if (backendID && seenBackendIDs.has(backendID)) {
localIDsToRemove.add(localID)
} else {
backendID && seenBackendIDs.add(backendID)
}
})
localIDsToRemove.forEach((localID) => {
collectionType === "REST"
? removeRESTCollection(parseInt(localID))
: removeGraphqlCollection(parseInt(localID))
collectionsMapper.removeEntry(undefined, localID)
const indexes = localID.split("/")
indexes.pop()
const parentPath = indexes.join("/")
reorderIndexesAfterEntryRemoval(parentPath, collectionsMapper, "REST")
})
function getParentPathIndexesFromPath(path: string) {
const indexes = path.split("/")
indexes.pop()
return indexes.map((index) => parseInt(index))
}
export function getCollectionIndexFromPath(collectionPath: string) {
const sourceCollectionIndexString = collectionPath.split("/").pop()
const sourceCollectionIndex = sourceCollectionIndexString
? parseInt(sourceCollectionIndexString)
: undefined
return sourceCollectionIndex
}
/**
* the sync function is called after the reordering has happened on the store
* because of this we need to find the new source and destination indexes after the reordering
*/
function getIndexesAfterReorder(
oldSourceIndex: number,
oldDestinationIndex: number
): [newSourceIndex: number, newDestinationIndex: number] {
// Source Becomes Destination -1
// Destination Remains Same
if (oldSourceIndex < oldDestinationIndex) {
return [oldDestinationIndex - 1, oldDestinationIndex]
}
// Source Becomes The Destination
// Destintion Becomes Source + 1
if (oldSourceIndex > oldDestinationIndex) {
return [oldDestinationIndex, oldDestinationIndex + 1]
}
throw new Error("Source and Destination are the same")
}
/**
* the sync function is called after moving a folder has happened on the store,
* because of this the source index given to the sync function is not the live one
* we need to find the new source index after the moving
*/
function getPathsAfterMoving(sourcePath: string, destinationPath?: string) {
if (!destinationPath) {
return {
newSourcePath: `${restCollectionStore.value.state.length - 1}`,
newDestinationPath: destinationPath,
}
}
const sourceParentPath = getParentPathFromPath(sourcePath)
const destinationParentPath = getParentPathFromPath(destinationPath)
const isSameParentPath = sourceParentPath === destinationParentPath
let newDestinationPath: string
if (isSameParentPath) {
const sourceIndex = getCollectionIndexFromPath(sourcePath)
const destinationIndex = getCollectionIndexFromPath(destinationPath)
if (
(sourceIndex || sourceIndex == 0) &&
(destinationIndex || destinationIndex == 0) &&
sourceIndex < destinationIndex
) {
newDestinationPath = destinationParentPath
? `${destinationParentPath}/${destinationIndex - 1}`
: `${destinationIndex - 1}`
} else {
newDestinationPath = destinationPath
}
} else {
newDestinationPath = destinationPath
}
const destinationFolder = navigateToFolderWithIndexPath(
restCollectionStore.value.state,
newDestinationPath.split("/").map((index) => parseInt(index))
)
const newSourcePath = destinationFolder
? `${newDestinationPath}/${destinationFolder?.folders.length - 1}`
: undefined
return {
newSourcePath,
newDestinationPath,
}
}
function getParentPathFromPath(path: string | undefined) {
const indexes = path ? path.split("/") : []
indexes.pop()
return indexes.join("/")
}
export function getStoreByCollectionType(type: "GQL" | "REST") {
const isGQL = type == "GQL"
const collectionStore = isGQL ? graphqlCollectionStore : restCollectionStore
return { collectionStore }
}

View File

@@ -1,7 +1,7 @@
import {
graphqlCollectionStore,
navigateToFolderWithIndexPath,
removeGraphqlRequest,
removeDuplicateGraphqlCollectionOrFolder,
} from "@hoppscotch/common/newstore/collections"
import {
getSettingSubject,
@@ -10,7 +10,7 @@ import {
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"
import { getSyncInitFunction, runDispatchWithOutSyncing } from "../../lib/sync"
import { getSyncInitFunction } from "../../lib/sync"
import { StoreSyncDefinitionOf } from "../../lib/sync"
import { createMapper } from "../../lib/sync/mapper"
@@ -21,18 +21,11 @@ import {
deleteUserCollection,
deleteUserRequest,
editGQLUserRequest,
moveUserRequest,
renameUserCollection,
} from "./collections.api"
import * as E from "fp-ts/Either"
import {
moveRequestInMapper,
removeAndReorderEntries,
reorderIndexesAfterEntryRemoval,
reorderRequestsMapper,
} from "./collections.mapper"
import { removeDuplicateCollectionsFromStore } from "./collections.sync"
import { moveOrReorderRequests } from "./collections.sync"
// gqlCollectionsMapper uses the collectionPath as the local identifier
export const gqlCollectionsMapper = createMapper<string, string>()
@@ -55,7 +48,12 @@ const recursivelySyncCollections = async (
if (E.isRight(res)) {
parentCollectionID = res.right.createGQLRootUserCollection.id
gqlCollectionsMapper.addEntry(collectionPath, parentCollectionID)
collection.id = parentCollectionID
removeDuplicateGraphqlCollectionOrFolder(
parentCollectionID,
collectionPath
)
} else {
parentCollectionID = undefined
}
@@ -68,13 +66,19 @@ const recursivelySyncCollections = async (
if (E.isRight(res)) {
const childCollectionId = res.right.createGQLChildUserCollection.id
gqlCollectionsMapper.addEntry(collectionPath, childCollectionId)
collection.id = childCollectionId
removeDuplicateGraphqlCollectionOrFolder(
childCollectionId,
`${collectionPath}`
)
}
}
// create the requests
if (parentCollectionID) {
collection.requests.forEach(async (request, index) => {
collection.requests.forEach(async (request) => {
const res =
parentCollectionID &&
(await createGQLUserRequest(
@@ -85,7 +89,8 @@ const recursivelySyncCollections = async (
if (res && E.isRight(res)) {
const requestId = res.right.createGQLUserRequest.id
gqlRequestsMapper.addEntry(`${collectionPath}/${index}`, requestId)
request.id = requestId
}
})
}
@@ -141,86 +146,73 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
collection,
`${lastCreatedCollectionIndex}`
)
removeDuplicateCollectionsFromStore("GQL")
},
async removeCollection({ collectionIndex }) {
const backendIdentifier = gqlCollectionsMapper.getBackendIDByLocalID(
`${collectionIndex}`
)
if (backendIdentifier) {
gqlCollectionsOperations.push({
collectionBackendID: backendIdentifier,
type: "COLLECTION_REMOVED",
status: "pending",
})
await deleteUserCollection(backendIdentifier)
removeAndReorderEntries(`${collectionIndex}`, "GQL")
async removeCollection({ collectionID }) {
if (collectionID) {
await deleteUserCollection(collectionID)
}
},
editCollection({ collection, collectionIndex }) {
const backendIdentifier = gqlCollectionsMapper.getBackendIDByLocalID(
`${collectionIndex}`
)
const collectionID = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
[collectionIndex]
)?.id
if (backendIdentifier && collection.name) {
renameUserCollection(backendIdentifier, collection.name)
if (collectionID && collection.name) {
renameUserCollection(collectionID, collection.name)
}
},
async addFolder({ name, path }) {
const parentCollectionBackendID =
gqlCollectionsMapper.getBackendIDByLocalID(path)
const parentCollection = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const parentCollectionBackendID = parentCollection?.id
if (parentCollectionBackendID) {
// TODO: remove this replaceAll thing when updating the mapper
const foldersLength = parentCollection.folders.length
const res = await createGQLChildUserCollection(
name,
parentCollectionBackendID
)
// after the folder is created add the path of the folder with its backend id to the mapper
if (E.isRight(res)) {
const folderBackendID = res.right.createGQLChildUserCollection.id
const parentCollection = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const { id } = res.right.createGQLChildUserCollection
if (parentCollection && parentCollection.folders.length > 0) {
const folderIndex = parentCollection.folders.length - 1
gqlCollectionsMapper.addEntry(
`${path}/${folderIndex}`,
folderBackendID
if (foldersLength) {
parentCollection.folders[foldersLength - 1].id = id
removeDuplicateGraphqlCollectionOrFolder(
id,
`${path}/${foldersLength - 1}`
)
}
}
}
},
editFolder({ folder, path }) {
const folderBackendId = gqlCollectionsMapper.getBackendIDByLocalID(
`${path}`
)
const folderBackendId = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)?.id
if (folderBackendId) {
if (folderBackendId && folder.name) {
renameUserCollection(folderBackendId, folder.name)
}
},
async removeFolder({ path }) {
const folderBackendId = gqlCollectionsMapper.getBackendIDByLocalID(
`${path}`
)
if (folderBackendId) {
await deleteUserCollection(folderBackendId)
removeAndReorderEntries(path, "GQL")
async removeFolder({ folderID }) {
if (folderID) {
await deleteUserCollection(folderID)
}
},
editRequest({ path, requestIndex, requestNew }) {
const requestPath = `${path}/${requestIndex}`
const request = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)?.requests[requestIndex]
const requestBackendID =
gqlRequestsMapper.getBackendIDByLocalID(requestPath)
const requestBackendID = request?.id
if (requestBackendID) {
editGQLUserRequest(
@@ -231,67 +223,41 @@ export const storeSyncDefinition: StoreSyncDefinitionOf<
}
},
async saveRequestAs({ path, request }) {
const parentCollectionBackendID =
gqlCollectionsMapper.getBackendIDByLocalID(path)
const folder = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
const parentCollectionBackendID = folder?.id
if (parentCollectionBackendID) {
const newRequest = folder.requests[folder.requests.length - 1]
const res = await createGQLUserRequest(
(request as HoppRESTRequest).name,
JSON.stringify(request),
parentCollectionBackendID
)
const existingPath =
E.isRight(res) &&
gqlRequestsMapper.getLocalIDByBackendID(
res.right.createGQLUserRequest.id
if (E.isRight(res)) {
const { id } = res.right.createGQLUserRequest
newRequest.id = id
removeDuplicateGraphqlCollectionOrFolder(
id,
`${path}/${folder.requests.length - 1}`,
"request"
)
// remove the request if it is already existing ( can happen when the subscription fired before the mutation is resolved )
if (existingPath) {
const indexes = existingPath.split("/")
const existingRequestIndex = indexes.pop()
const existingRequestParentPath = indexes.join("/")
runDispatchWithOutSyncing(() => {
existingRequestIndex &&
removeGraphqlRequest(
existingRequestParentPath,
parseInt(existingRequestIndex)
)
})
}
const parentCollection = navigateToFolderWithIndexPath(
graphqlCollectionStore.value.state,
path.split("/").map((index) => parseInt(index))
)
if (parentCollection) {
const lastCreatedRequestIndex = parentCollection.requests.length - 1
if (E.isRight(res)) {
gqlRequestsMapper.addEntry(
`${path}/${lastCreatedRequestIndex}`,
res.right.createGQLUserRequest.id
)
}
}
}
},
async removeRequest({ path, requestIndex }) {
const requestPath = `${path}/${requestIndex}`
const requestBackendID =
gqlRequestsMapper.getBackendIDByLocalID(requestPath)
if (requestBackendID) {
await deleteUserRequest(requestBackendID)
gqlRequestsMapper.removeEntry(requestPath)
reorderIndexesAfterEntryRemoval(path, gqlRequestsMapper, "GQL")
async removeRequest({ requestID }) {
if (requestID) {
await deleteUserRequest(requestID)
}
},
moveRequest({ destinationPath, path, requestIndex }) {
moveOrReorderRequests(requestIndex, path, destinationPath)
moveOrReorderRequests(requestIndex, path, destinationPath, undefined, "GQL")
},
}
@@ -301,47 +267,3 @@ export const gqlCollectionsSyncer = getSyncInitFunction(
() => settingsStore.value.syncCollections,
getSettingSubject("syncCollections")
)
async function moveOrReorderRequests(
requestIndex: number,
path: string,
destinationPath: string,
nextRequestIndex?: number
) {
const sourceCollectionBackendID =
gqlCollectionsMapper.getBackendIDByLocalID(path)
const destinationCollectionBackendID =
gqlCollectionsMapper.getBackendIDByLocalID(destinationPath)
const requestBackendID = gqlRequestsMapper.getBackendIDByLocalID(
`${path}/${requestIndex}`
)
let nextRequestBackendID: string | undefined
// we only need this for reordering requests, not for moving requests
if (nextRequestIndex) {
nextRequestBackendID = gqlRequestsMapper.getBackendIDByLocalID(
`${destinationPath}/${nextRequestIndex}`
)
}
if (
sourceCollectionBackendID &&
destinationCollectionBackendID &&
requestBackendID
) {
await moveUserRequest(
sourceCollectionBackendID,
destinationCollectionBackendID,
requestBackendID,
nextRequestBackendID
)
if (nextRequestBackendID && nextRequestIndex) {
reorderRequestsMapper(requestIndex, path, nextRequestIndex, "GQL")
} else {
moveRequestInMapper(requestIndex, path, destinationPath, "GQL")
}
}
}

View File

@@ -192,12 +192,6 @@ function setupUserEnvironmentDeletedSubscription() {
runDispatchWithOutSyncing(() => {
deleteEnvironment(localIndex)
})
} else {
console.log("could not find the localIndex")
// TODO:
// handle order of events
// eg: update coming before create
// skipping for this release
}
}
})

View File

@@ -26,7 +26,6 @@ async function loadTabStateFromSync(): Promise<PersistableRESTTabState | null> {
return currentRESTSession ? JSON.parse(currentRESTSession) : null
} else {
console.log(res)
}
return null