refactor: monorepo+pnpm (removed husky)
This commit is contained in:
74
packages/hoppscotch-app/newstore/DispatchingStore.ts
Normal file
74
packages/hoppscotch-app/newstore/DispatchingStore.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Subject, BehaviorSubject } from "rxjs"
|
||||
import { map } from "rxjs/operators"
|
||||
import assign from "lodash/assign"
|
||||
import clone from "lodash/clone"
|
||||
|
||||
type dispatcherFunc<StoreType> = (
|
||||
currentVal: StoreType,
|
||||
payload: any
|
||||
) => Partial<StoreType>
|
||||
|
||||
/**
|
||||
* Defines a dispatcher.
|
||||
*
|
||||
* This function exists to provide better typing for dispatch function.
|
||||
* As you can see, its pretty much an identity function.
|
||||
*/
|
||||
export const defineDispatchers = <StoreType, T>(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
dispatchers: { [_ in keyof T]: dispatcherFunc<StoreType> }
|
||||
) => dispatchers
|
||||
|
||||
type Dispatch<
|
||||
StoreType,
|
||||
DispatchersType extends Record<string, dispatcherFunc<StoreType>>
|
||||
> = {
|
||||
dispatcher: keyof DispatchersType
|
||||
payload: any
|
||||
}
|
||||
|
||||
export default class DispatchingStore<
|
||||
StoreType,
|
||||
DispatchersType extends Record<string, dispatcherFunc<StoreType>>
|
||||
> {
|
||||
#state$: BehaviorSubject<StoreType>
|
||||
#dispatchers: DispatchersType
|
||||
#dispatches$: Subject<Dispatch<StoreType, DispatchersType>> = new Subject()
|
||||
|
||||
constructor(initialValue: StoreType, dispatchers: DispatchersType) {
|
||||
this.#state$ = new BehaviorSubject(initialValue)
|
||||
this.#dispatchers = dispatchers
|
||||
|
||||
this.#dispatches$
|
||||
.pipe(
|
||||
map(({ dispatcher, payload }) =>
|
||||
this.#dispatchers[dispatcher](this.value, payload)
|
||||
)
|
||||
)
|
||||
.subscribe((val) => {
|
||||
const data = clone(this.value)
|
||||
assign(data, val)
|
||||
|
||||
this.#state$.next(data)
|
||||
})
|
||||
}
|
||||
|
||||
get subject$() {
|
||||
return this.#state$
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.subject$.value
|
||||
}
|
||||
|
||||
get dispatches$() {
|
||||
return this.#dispatches$
|
||||
}
|
||||
|
||||
dispatch({ dispatcher, payload }: Dispatch<StoreType, DispatchersType>) {
|
||||
if (!this.#dispatchers[dispatcher])
|
||||
throw new Error(`Undefined dispatch type '${dispatcher}'`)
|
||||
|
||||
this.#dispatches$.next({ dispatcher, payload })
|
||||
}
|
||||
}
|
||||
249
packages/hoppscotch-app/newstore/GQLSession.ts
Normal file
249
packages/hoppscotch-app/newstore/GQLSession.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
import { distinctUntilChanged, pluck } from "rxjs/operators"
|
||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||
import { useStream } from "~/helpers/utils/composables"
|
||||
import {
|
||||
GQLHeader,
|
||||
HoppGQLRequest,
|
||||
makeGQLRequest,
|
||||
} from "~/helpers/types/HoppGQLRequest"
|
||||
|
||||
type GQLSession = {
|
||||
request: HoppGQLRequest
|
||||
schema: string
|
||||
response: string
|
||||
}
|
||||
|
||||
export const defaultGQLSession: GQLSession = {
|
||||
request: makeGQLRequest({
|
||||
name: "",
|
||||
url: "https://rickandmortyapi.com/graphql",
|
||||
headers: [],
|
||||
variables: `{ "id": "1" }`,
|
||||
query: `query GetCharacter($id: ID!) {
|
||||
character(id: $id) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}`,
|
||||
}),
|
||||
schema: "",
|
||||
response: "",
|
||||
}
|
||||
|
||||
const dispatchers = defineDispatchers({
|
||||
setSession(_: GQLSession, { session }: { session: GQLSession }) {
|
||||
return session
|
||||
},
|
||||
setName(curr: GQLSession, { newName }: { newName: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
name: newName,
|
||||
},
|
||||
}
|
||||
},
|
||||
setURL(curr: GQLSession, { newURL }: { newURL: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
url: newURL,
|
||||
},
|
||||
}
|
||||
},
|
||||
setHeaders(curr: GQLSession, { headers }: { headers: GQLHeader[] }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers,
|
||||
},
|
||||
}
|
||||
},
|
||||
addHeader(curr: GQLSession, { header }: { header: GQLHeader }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers: [...curr.request.headers, header],
|
||||
},
|
||||
}
|
||||
},
|
||||
removeHeader(curr: GQLSession, { headerIndex }: { headerIndex: number }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers: curr.request.headers.filter((_x, i) => i !== headerIndex),
|
||||
},
|
||||
}
|
||||
},
|
||||
updateHeader(
|
||||
curr: GQLSession,
|
||||
{
|
||||
headerIndex,
|
||||
updatedHeader,
|
||||
}: { headerIndex: number; updatedHeader: GQLHeader }
|
||||
) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers: curr.request.headers.map((x, i) =>
|
||||
i === headerIndex ? updatedHeader : x
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
setQuery(curr: GQLSession, { newQuery }: { newQuery: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
query: newQuery,
|
||||
},
|
||||
}
|
||||
},
|
||||
setVariables(curr: GQLSession, { newVariables }: { newVariables: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
variables: newVariables,
|
||||
},
|
||||
}
|
||||
},
|
||||
setResponse(_: GQLSession, { newResponse }: { newResponse: string }) {
|
||||
return {
|
||||
response: newResponse,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const gqlSessionStore = new DispatchingStore(
|
||||
defaultGQLSession,
|
||||
dispatchers
|
||||
)
|
||||
|
||||
export function setGQLURL(newURL: string) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "setURL",
|
||||
payload: {
|
||||
newURL,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setGQLHeaders(headers: GQLHeader[]) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "setHeaders",
|
||||
payload: {
|
||||
headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addGQLHeader(header: GQLHeader) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "addHeader",
|
||||
payload: {
|
||||
header,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateGQLHeader(headerIndex: number, updatedHeader: GQLHeader) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "updateHeader",
|
||||
payload: {
|
||||
headerIndex,
|
||||
updatedHeader,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeGQLHeader(headerIndex: number) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "removeHeader",
|
||||
payload: {
|
||||
headerIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function clearGQLHeaders() {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "setHeaders",
|
||||
payload: {
|
||||
headers: [],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setGQLQuery(newQuery: string) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "setQuery",
|
||||
payload: {
|
||||
newQuery,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setGQLVariables(newVariables: string) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "setVariables",
|
||||
payload: {
|
||||
newVariables,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setGQLResponse(newResponse: string) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "setResponse",
|
||||
payload: {
|
||||
newResponse,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getGQLSession() {
|
||||
return gqlSessionStore.value
|
||||
}
|
||||
|
||||
export function setGQLSession(session: GQLSession) {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "setSession",
|
||||
payload: {
|
||||
session,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useGQLRequestName() {
|
||||
return useStream(gqlName$, "", (newName) => {
|
||||
gqlSessionStore.dispatch({
|
||||
dispatcher: "setName",
|
||||
payload: { newName },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const gqlName$ = gqlSessionStore.subject$.pipe(
|
||||
pluck("request", "name"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
export const gqlURL$ = gqlSessionStore.subject$.pipe(
|
||||
pluck("request", "url"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
export const gqlQuery$ = gqlSessionStore.subject$.pipe(
|
||||
pluck("request", "query"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
export const gqlVariables$ = gqlSessionStore.subject$.pipe(
|
||||
pluck("request", "variables"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
export const gqlHeaders$ = gqlSessionStore.subject$.pipe(
|
||||
pluck("request", "headers"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const gqlResponse$ = gqlSessionStore.subject$.pipe(
|
||||
pluck("response"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
749
packages/hoppscotch-app/newstore/RESTSession.ts
Normal file
749
packages/hoppscotch-app/newstore/RESTSession.ts
Normal file
@@ -0,0 +1,749 @@
|
||||
import { pluck, distinctUntilChanged, map, filter } from "rxjs/operators"
|
||||
import { Ref } from "@nuxtjs/composition-api"
|
||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||
import {
|
||||
FormDataKeyValue,
|
||||
HoppRESTHeader,
|
||||
HoppRESTParam,
|
||||
HoppRESTReqBody,
|
||||
HoppRESTRequest,
|
||||
RESTReqSchemaVersion,
|
||||
} from "~/helpers/types/HoppRESTRequest"
|
||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
import { useStream } from "~/helpers/utils/composables"
|
||||
import { HoppTestResult } from "~/helpers/types/HoppTestResult"
|
||||
import { HoppRESTAuth } from "~/helpers/types/HoppRESTAuth"
|
||||
import { ValidContentTypes } from "~/helpers/utils/contenttypes"
|
||||
import { HoppRequestSaveContext } from "~/helpers/types/HoppRequestSaveContext"
|
||||
|
||||
type RESTSession = {
|
||||
request: HoppRESTRequest
|
||||
response: HoppRESTResponse | null
|
||||
testResults: HoppTestResult | null
|
||||
saveContext: HoppRequestSaveContext | null
|
||||
}
|
||||
|
||||
export const defaultRESTRequest: HoppRESTRequest = {
|
||||
v: RESTReqSchemaVersion,
|
||||
endpoint: "https://echo.hoppscotch.io",
|
||||
name: "Untitled request",
|
||||
params: [{ key: "", value: "", active: true }],
|
||||
headers: [{ key: "", value: "", active: true }],
|
||||
method: "GET",
|
||||
auth: {
|
||||
authType: "none",
|
||||
authActive: true,
|
||||
},
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
body: {
|
||||
contentType: null,
|
||||
body: null,
|
||||
},
|
||||
}
|
||||
|
||||
const defaultRESTSession: RESTSession = {
|
||||
request: defaultRESTRequest,
|
||||
response: null,
|
||||
testResults: null,
|
||||
saveContext: null,
|
||||
}
|
||||
|
||||
const dispatchers = defineDispatchers({
|
||||
setRequest(_: RESTSession, { req }: { req: HoppRESTRequest }) {
|
||||
return {
|
||||
request: req,
|
||||
}
|
||||
},
|
||||
setRequestName(curr: RESTSession, { newName }: { newName: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
name: newName,
|
||||
},
|
||||
}
|
||||
},
|
||||
setEndpoint(curr: RESTSession, { newEndpoint }: { newEndpoint: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
endpoint: newEndpoint,
|
||||
},
|
||||
}
|
||||
},
|
||||
setParams(curr: RESTSession, { entries }: { entries: HoppRESTParam[] }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
params: entries,
|
||||
},
|
||||
}
|
||||
},
|
||||
addParam(curr: RESTSession, { newParam }: { newParam: HoppRESTParam }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
params: [...curr.request.params, newParam],
|
||||
},
|
||||
}
|
||||
},
|
||||
updateParam(
|
||||
curr: RESTSession,
|
||||
{ index, updatedParam }: { index: number; updatedParam: HoppRESTParam }
|
||||
) {
|
||||
const newParams = curr.request.params.map((param, i) => {
|
||||
if (i === index) return updatedParam
|
||||
else return param
|
||||
})
|
||||
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
params: newParams,
|
||||
},
|
||||
}
|
||||
},
|
||||
deleteParam(curr: RESTSession, { index }: { index: number }) {
|
||||
const newParams = curr.request.params.filter((_x, i) => i !== index)
|
||||
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
params: newParams,
|
||||
},
|
||||
}
|
||||
},
|
||||
deleteAllParams(curr: RESTSession) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
params: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
updateMethod(curr: RESTSession, { newMethod }: { newMethod: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
method: newMethod,
|
||||
},
|
||||
}
|
||||
},
|
||||
setHeaders(curr: RESTSession, { entries }: { entries: HoppRESTHeader[] }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers: entries,
|
||||
},
|
||||
}
|
||||
},
|
||||
addHeader(curr: RESTSession, { entry }: { entry: HoppRESTHeader }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers: [...curr.request.headers, entry],
|
||||
},
|
||||
}
|
||||
},
|
||||
updateHeader(
|
||||
curr: RESTSession,
|
||||
{ index, updatedEntry }: { index: number; updatedEntry: HoppRESTHeader }
|
||||
) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers: curr.request.headers.map((header, i) => {
|
||||
if (i === index) return updatedEntry
|
||||
else return header
|
||||
}),
|
||||
},
|
||||
}
|
||||
},
|
||||
deleteHeader(curr: RESTSession, { index }: { index: number }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers: curr.request.headers.filter((_, i) => i !== index),
|
||||
},
|
||||
}
|
||||
},
|
||||
deleteAllHeaders(curr: RESTSession) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
headers: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
setAuth(curr: RESTSession, { newAuth }: { newAuth: HoppRESTAuth }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
auth: newAuth,
|
||||
},
|
||||
}
|
||||
},
|
||||
setPreRequestScript(curr: RESTSession, { newScript }: { newScript: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
preRequestScript: newScript,
|
||||
},
|
||||
}
|
||||
},
|
||||
setTestScript(curr: RESTSession, { newScript }: { newScript: string }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
testScript: newScript,
|
||||
},
|
||||
}
|
||||
},
|
||||
setContentType(
|
||||
curr: RESTSession,
|
||||
{ newContentType }: { newContentType: ValidContentTypes | null }
|
||||
) {
|
||||
// TODO: persist body evenafter switching content typees
|
||||
if (curr.request.body.contentType !== "multipart/form-data") {
|
||||
if (newContentType === "multipart/form-data") {
|
||||
// Going from non-formdata to form-data, discard contents and set empty array as body
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: <HoppRESTReqBody>{
|
||||
contentType: "multipart/form-data",
|
||||
body: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// non-formdata to non-formdata, keep body and set content type
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: <HoppRESTReqBody>{
|
||||
contentType: newContentType,
|
||||
body:
|
||||
newContentType === null
|
||||
? null
|
||||
: (curr.request.body as any)?.body ?? "",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if (newContentType !== "multipart/form-data") {
|
||||
// Going from formdata to non-formdata, discard contents and set empty string
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: <HoppRESTReqBody>{
|
||||
contentType: newContentType,
|
||||
body: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// form-data to form-data ? just set the content type ¯\_(ツ)_/¯
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: <HoppRESTReqBody>{
|
||||
contentType: newContentType,
|
||||
body: curr.request.body.body,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
addFormDataEntry(curr: RESTSession, { entry }: { entry: FormDataKeyValue }) {
|
||||
// Only perform update if the current content-type is formdata
|
||||
if (curr.request.body.contentType !== "multipart/form-data") return {}
|
||||
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: <HoppRESTReqBody>{
|
||||
contentType: "multipart/form-data",
|
||||
body: [...curr.request.body.body, entry],
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
deleteFormDataEntry(curr: RESTSession, { index }: { index: number }) {
|
||||
// Only perform update if the current content-type is formdata
|
||||
if (curr.request.body.contentType !== "multipart/form-data") return {}
|
||||
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: <HoppRESTReqBody>{
|
||||
contentType: "multipart/form-data",
|
||||
body: curr.request.body.body.filter((_, i) => i !== index),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
updateFormDataEntry(
|
||||
curr: RESTSession,
|
||||
{ index, entry }: { index: number; entry: FormDataKeyValue }
|
||||
) {
|
||||
// Only perform update if the current content-type is formdata
|
||||
if (curr.request.body.contentType !== "multipart/form-data") return {}
|
||||
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: <HoppRESTReqBody>{
|
||||
contentType: "multipart/form-data",
|
||||
body: curr.request.body.body.map((x, i) => (i !== index ? x : entry)),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
deleteAllFormDataEntries(curr: RESTSession) {
|
||||
// Only perform update if the current content-type is formdata
|
||||
if (curr.request.body.contentType !== "multipart/form-data") return {}
|
||||
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: <HoppRESTReqBody>{
|
||||
contentType: "multipart/form-data",
|
||||
body: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
setRequestBody(curr: RESTSession, { newBody }: { newBody: HoppRESTReqBody }) {
|
||||
return {
|
||||
request: {
|
||||
...curr.request,
|
||||
body: newBody,
|
||||
},
|
||||
}
|
||||
},
|
||||
updateResponse(
|
||||
_curr: RESTSession,
|
||||
{ updatedRes }: { updatedRes: HoppRESTResponse | null }
|
||||
) {
|
||||
return {
|
||||
response: updatedRes,
|
||||
}
|
||||
},
|
||||
clearResponse(_curr: RESTSession) {
|
||||
return {
|
||||
response: null,
|
||||
}
|
||||
},
|
||||
setTestResults(
|
||||
_curr: RESTSession,
|
||||
{ newResults }: { newResults: HoppTestResult | null }
|
||||
) {
|
||||
return {
|
||||
testResults: newResults,
|
||||
}
|
||||
},
|
||||
setSaveContext(
|
||||
_,
|
||||
{ newContext }: { newContext: HoppRequestSaveContext | null }
|
||||
) {
|
||||
return {
|
||||
saveContext: newContext,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const restSessionStore = new DispatchingStore(defaultRESTSession, dispatchers)
|
||||
|
||||
export function getRESTRequest() {
|
||||
return restSessionStore.subject$.value.request
|
||||
}
|
||||
|
||||
export function setRESTRequest(
|
||||
req: HoppRESTRequest,
|
||||
saveContext?: HoppRequestSaveContext | null
|
||||
) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setRequest",
|
||||
payload: {
|
||||
req,
|
||||
},
|
||||
})
|
||||
|
||||
if (saveContext) setRESTSaveContext(saveContext)
|
||||
}
|
||||
|
||||
export function setRESTSaveContext(saveContext: HoppRequestSaveContext | null) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setSaveContext",
|
||||
payload: {
|
||||
newContext: saveContext,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getRESTSaveContext() {
|
||||
return restSessionStore.value.saveContext
|
||||
}
|
||||
|
||||
export function resetRESTRequest() {
|
||||
setRESTRequest(defaultRESTRequest)
|
||||
}
|
||||
|
||||
export function setRESTEndpoint(newEndpoint: string) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setEndpoint",
|
||||
payload: {
|
||||
newEndpoint,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTRequestName(newName: string) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setRequestName",
|
||||
payload: {
|
||||
newName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTParams(entries: HoppRESTParam[]) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setParams",
|
||||
payload: {
|
||||
entries,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addRESTParam(newParam: HoppRESTParam) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "addParam",
|
||||
payload: {
|
||||
newParam,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRESTParam(index: number, updatedParam: HoppRESTParam) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "updateParam",
|
||||
payload: {
|
||||
updatedParam,
|
||||
index,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRESTParam(index: number) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "deleteParam",
|
||||
payload: {
|
||||
index,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteAllRESTParams() {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "deleteAllParams",
|
||||
payload: {},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRESTMethod(newMethod: string) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "updateMethod",
|
||||
payload: {
|
||||
newMethod,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTHeaders(entries: HoppRESTHeader[]) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setHeaders",
|
||||
payload: {
|
||||
entries,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addRESTHeader(entry: HoppRESTHeader) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "addHeader",
|
||||
payload: {
|
||||
entry,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRESTHeader(index: number, updatedEntry: HoppRESTHeader) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "updateHeader",
|
||||
payload: {
|
||||
index,
|
||||
updatedEntry,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRESTHeader(index: number) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "deleteHeader",
|
||||
payload: {
|
||||
index,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteAllRESTHeaders() {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "deleteAllHeaders",
|
||||
payload: {},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTAuth(newAuth: HoppRESTAuth) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setAuth",
|
||||
payload: {
|
||||
newAuth,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTPreRequestScript(newScript: string) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setPreRequestScript",
|
||||
payload: {
|
||||
newScript,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTTestScript(newScript: string) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setTestScript",
|
||||
payload: {
|
||||
newScript,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTReqBody(newBody: HoppRESTReqBody | null) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setRequestBody",
|
||||
payload: {
|
||||
newBody,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRESTResponse(updatedRes: HoppRESTResponse | null) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "updateResponse",
|
||||
payload: {
|
||||
updatedRes,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function clearRESTResponse() {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "clearResponse",
|
||||
payload: {},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTTestResults(newResults: HoppTestResult | null) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setTestResults",
|
||||
payload: {
|
||||
newResults,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addFormDataEntry(entry: FormDataKeyValue) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "addFormDataEntry",
|
||||
payload: {
|
||||
entry,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteFormDataEntry(index: number) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "deleteFormDataEntry",
|
||||
payload: {
|
||||
index,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateFormDataEntry(index: number, entry: FormDataKeyValue) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "updateFormDataEntry",
|
||||
payload: {
|
||||
index,
|
||||
entry,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setRESTContentType(newContentType: ValidContentTypes | null) {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "setContentType",
|
||||
payload: {
|
||||
newContentType,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteAllFormDataEntries() {
|
||||
restSessionStore.dispatch({
|
||||
dispatcher: "deleteAllFormDataEntries",
|
||||
payload: {},
|
||||
})
|
||||
}
|
||||
|
||||
export const restSaveContext$ = restSessionStore.subject$.pipe(
|
||||
pluck("saveContext"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restRequest$ = restSessionStore.subject$.pipe(
|
||||
pluck("request"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restRequestName$ = restRequest$.pipe(
|
||||
pluck("name"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restEndpoint$ = restSessionStore.subject$.pipe(
|
||||
pluck("request", "endpoint"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restParams$ = restSessionStore.subject$.pipe(
|
||||
pluck("request", "params"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restActiveParamsCount$ = restParams$.pipe(
|
||||
map(
|
||||
(params) =>
|
||||
params.filter((x) => x.active && (x.key !== "" || x.value !== "")).length
|
||||
)
|
||||
)
|
||||
|
||||
export const restMethod$ = restSessionStore.subject$.pipe(
|
||||
pluck("request", "method"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restHeaders$ = restSessionStore.subject$.pipe(
|
||||
pluck("request", "headers"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restActiveHeadersCount$ = restHeaders$.pipe(
|
||||
map(
|
||||
(params) =>
|
||||
params.filter((x) => x.active && (x.key !== "" || x.value !== "")).length
|
||||
)
|
||||
)
|
||||
|
||||
export const restAuth$ = restRequest$.pipe(pluck("auth"))
|
||||
|
||||
export const restPreRequestScript$ = restSessionStore.subject$.pipe(
|
||||
pluck("request", "preRequestScript"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restContentType$ = restRequest$.pipe(
|
||||
pluck("body", "contentType"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restTestScript$ = restSessionStore.subject$.pipe(
|
||||
pluck("request", "testScript"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restReqBody$ = restSessionStore.subject$.pipe(
|
||||
pluck("request", "body"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const restResponse$ = restSessionStore.subject$.pipe(
|
||||
pluck("response"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const completedRESTResponse$ = restResponse$.pipe(
|
||||
filter(
|
||||
(res) =>
|
||||
res !== null && res.type !== "loading" && res.type !== "network_fail"
|
||||
)
|
||||
)
|
||||
|
||||
export const restTestResults$ = restSessionStore.subject$.pipe(
|
||||
pluck("testResults"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
/**
|
||||
* A Vue 3 composable function that gives access to a ref
|
||||
* which is updated to the preRequestScript value in the store.
|
||||
* The ref value is kept in sync with the store and all writes
|
||||
* to the ref are dispatched to the store as `setPreRequestScript`
|
||||
* dispatches.
|
||||
*/
|
||||
export function usePreRequestScript(): Ref<string> {
|
||||
return useStream(
|
||||
restPreRequestScript$,
|
||||
restSessionStore.value.request.preRequestScript,
|
||||
(value) => {
|
||||
setRESTPreRequestScript(value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue 3 composable function that gives access to a ref
|
||||
* which is updated to the testScript value in the store.
|
||||
* The ref value is kept in sync with the store and all writes
|
||||
* to the ref are dispatched to the store as `setTestScript`
|
||||
* dispatches.
|
||||
*/
|
||||
export function useTestScript(): Ref<string> {
|
||||
return useStream(
|
||||
restTestScript$,
|
||||
restSessionStore.value.request.testScript,
|
||||
(value) => {
|
||||
setRESTTestScript(value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function useRESTRequestBody(): Ref<HoppRESTReqBody> {
|
||||
return useStream(
|
||||
restReqBody$,
|
||||
restSessionStore.value.request.body,
|
||||
setRESTReqBody
|
||||
)
|
||||
}
|
||||
|
||||
export function useRESTRequestName(): Ref<string> {
|
||||
return useStream(
|
||||
restRequestName$,
|
||||
restSessionStore.value.request.name,
|
||||
setRESTRequestName
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import { BehaviorSubject, Subject } from "rxjs"
|
||||
import isEqual from "lodash/isEqual"
|
||||
import DispatchingStore from "~/newstore/DispatchingStore"
|
||||
|
||||
describe("DispatchingStore", () => {
|
||||
test("'subject$' property properly returns an BehaviorSubject", () => {
|
||||
const store = new DispatchingStore({}, {})
|
||||
|
||||
expect(store.subject$ instanceof BehaviorSubject).toEqual(true)
|
||||
})
|
||||
|
||||
test("'value' property properly returns the current state value", () => {
|
||||
const store = new DispatchingStore({}, {})
|
||||
|
||||
expect(store.value).toEqual({})
|
||||
})
|
||||
|
||||
test("'dispatches$' property properly returns a Subject", () => {
|
||||
const store = new DispatchingStore({}, {})
|
||||
|
||||
expect(store.dispatches$ instanceof Subject).toEqual(true)
|
||||
})
|
||||
|
||||
test("dispatch with invalid dispatcher are thrown", () => {
|
||||
const store = new DispatchingStore({}, {})
|
||||
|
||||
expect(() => {
|
||||
store.dispatch({
|
||||
dispatcher: "non-existent",
|
||||
payload: {},
|
||||
})
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
test("valid dispatcher calls run without throwing", () => {
|
||||
const store = new DispatchingStore(
|
||||
{},
|
||||
{
|
||||
testDispatcher(_currentValue, _payload) {
|
||||
// Nothing here
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(() => {
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {},
|
||||
})
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
test("only correct dispatcher method is ran", () => {
|
||||
const dispatchFn = jest.fn().mockReturnValue({})
|
||||
const dontCallDispatchFn = jest.fn().mockReturnValue({})
|
||||
|
||||
const store = new DispatchingStore(
|
||||
{},
|
||||
{
|
||||
testDispatcher: dispatchFn,
|
||||
dontCallDispatcher: dontCallDispatchFn,
|
||||
}
|
||||
)
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {},
|
||||
})
|
||||
|
||||
expect(dispatchFn).toHaveBeenCalledTimes(1)
|
||||
expect(dontCallDispatchFn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("passes current value and the payload to the dispatcher", () => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testPayload = { name: "alice" }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue({})
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn,
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: testPayload,
|
||||
})
|
||||
|
||||
expect(testDispatchFn).toHaveBeenCalledWith(testInitValue, testPayload)
|
||||
})
|
||||
|
||||
test("dispatcher returns are used to update the store correctly", () => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testDispatchReturnVal = { name: "alice" }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue(testDispatchReturnVal)
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn,
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {}, // Payload doesn't matter because the function is mocked
|
||||
})
|
||||
|
||||
expect(store.value).toEqual(testDispatchReturnVal)
|
||||
})
|
||||
|
||||
test("dispatching patches in new values if not existing on the store", () => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testDispatchReturnVal = { age: 25 }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue(testDispatchReturnVal)
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn,
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {},
|
||||
})
|
||||
|
||||
expect(store.value).toEqual({
|
||||
name: "bob",
|
||||
age: 25,
|
||||
})
|
||||
})
|
||||
|
||||
test("emits the current store value to the new subscribers", (done) => {
|
||||
const testInitValue = { name: "bob" }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue({})
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn,
|
||||
})
|
||||
|
||||
store.subject$.subscribe((value) => {
|
||||
if (value === testInitValue) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test("emits the dispatched store value to the subscribers", (done) => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testDispatchReturnVal = { age: 25 }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue(testDispatchReturnVal)
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn,
|
||||
})
|
||||
|
||||
store.subject$.subscribe((value) => {
|
||||
if (isEqual(value, { name: "bob", age: 25 })) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {},
|
||||
})
|
||||
})
|
||||
|
||||
test("dispatching emits the new dispatch requests to the subscribers", () => {
|
||||
const testInitValue = { name: "bob" }
|
||||
const testPayload = { age: 25 }
|
||||
|
||||
const testDispatchFn = jest.fn().mockReturnValue({})
|
||||
|
||||
const store = new DispatchingStore(testInitValue, {
|
||||
testDispatcher: testDispatchFn,
|
||||
})
|
||||
|
||||
store.dispatches$.subscribe((value) => {
|
||||
if (
|
||||
isEqual(value, { dispatcher: "testDispatcher", payload: testPayload })
|
||||
) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
|
||||
store.dispatch({
|
||||
dispatcher: "testDispatcher",
|
||||
payload: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
943
packages/hoppscotch-app/newstore/collections.ts
Normal file
943
packages/hoppscotch-app/newstore/collections.ts
Normal file
@@ -0,0 +1,943 @@
|
||||
import { pluck } from "rxjs/operators"
|
||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||
import { getRESTSaveContext, setRESTSaveContext } from "./RESTSession"
|
||||
import {
|
||||
HoppRESTRequest,
|
||||
translateToNewRequest,
|
||||
} from "~/helpers/types/HoppRESTRequest"
|
||||
import {
|
||||
HoppGQLRequest,
|
||||
translateToGQLRequest,
|
||||
} from "~/helpers/types/HoppGQLRequest"
|
||||
|
||||
export interface Collection<T extends HoppRESTRequest | HoppGQLRequest> {
|
||||
v: number
|
||||
name: string
|
||||
folders: Collection<T>[]
|
||||
requests: T[]
|
||||
|
||||
id?: string // For Firestore ID
|
||||
}
|
||||
|
||||
const defaultRESTCollectionState = {
|
||||
state: [
|
||||
makeCollection<HoppRESTRequest>({
|
||||
name: "My Collection",
|
||||
folders: [],
|
||||
requests: [],
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const defaultGraphqlCollectionState = {
|
||||
state: [
|
||||
makeCollection<HoppGQLRequest>({
|
||||
name: "My GraphQL Collection",
|
||||
folders: [],
|
||||
requests: [],
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export function makeCollection<T extends HoppRESTRequest | HoppGQLRequest>(
|
||||
x: Omit<Collection<T>, "v">
|
||||
): Collection<T> {
|
||||
return {
|
||||
v: 1,
|
||||
...x,
|
||||
}
|
||||
}
|
||||
|
||||
export function translateToNewRESTCollection(
|
||||
x: any
|
||||
): Collection<HoppRESTRequest> {
|
||||
if (x.v && x.v === 1) return x
|
||||
|
||||
// Legacy
|
||||
const name = x.name ?? "Untitled"
|
||||
const folders = (x.folders ?? []).map(translateToNewRESTCollection)
|
||||
const requests = (x.requests ?? []).map(translateToNewRequest)
|
||||
|
||||
const obj = makeCollection<HoppRESTRequest>({
|
||||
name,
|
||||
folders,
|
||||
requests,
|
||||
})
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
export function translateToNewGQLCollection(
|
||||
x: any
|
||||
): Collection<HoppGQLRequest> {
|
||||
if (x.v && x.v === 1) return x
|
||||
|
||||
// Legacy
|
||||
const name = x.name ?? "Untitled"
|
||||
const folders = (x.folders ?? []).map(translateToNewGQLCollection)
|
||||
const requests = (x.requests ?? []).map(translateToGQLRequest)
|
||||
|
||||
const obj = makeCollection<HoppGQLRequest>({
|
||||
name,
|
||||
folders,
|
||||
requests,
|
||||
})
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
type RESTCollectionStoreType = typeof defaultRESTCollectionState
|
||||
type GraphqlCollectionStoreType = typeof defaultGraphqlCollectionState
|
||||
|
||||
function navigateToFolderWithIndexPath(
|
||||
collections: Collection<HoppRESTRequest | HoppGQLRequest>[],
|
||||
indexPaths: number[]
|
||||
) {
|
||||
if (indexPaths.length === 0) return null
|
||||
|
||||
let target = collections[indexPaths.shift() as number]
|
||||
|
||||
while (indexPaths.length > 0)
|
||||
target = target.folders[indexPaths.shift() as number]
|
||||
|
||||
return target !== undefined ? target : null
|
||||
}
|
||||
|
||||
const restCollectionDispatchers = defineDispatchers({
|
||||
setCollections(
|
||||
_: RESTCollectionStoreType,
|
||||
{ entries }: { entries: Collection<HoppRESTRequest>[] }
|
||||
) {
|
||||
return {
|
||||
state: entries,
|
||||
}
|
||||
},
|
||||
|
||||
appendCollections(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ entries }: { entries: Collection<HoppRESTRequest>[] }
|
||||
) {
|
||||
return {
|
||||
state: [...state, ...entries],
|
||||
}
|
||||
},
|
||||
|
||||
addCollection(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ collection }: { collection: Collection<any> }
|
||||
) {
|
||||
return {
|
||||
state: [...state, collection],
|
||||
}
|
||||
},
|
||||
|
||||
removeCollection(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ collectionIndex }: { collectionIndex: number }
|
||||
) {
|
||||
return {
|
||||
state: (state as any).filter(
|
||||
(_: any, i: number) => i !== collectionIndex
|
||||
),
|
||||
}
|
||||
},
|
||||
|
||||
editCollection(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{
|
||||
collectionIndex,
|
||||
collection,
|
||||
}: { collectionIndex: number; collection: Collection<any> }
|
||||
) {
|
||||
return {
|
||||
state: state.map((col, index) =>
|
||||
index === collectionIndex ? collection : col
|
||||
),
|
||||
}
|
||||
},
|
||||
|
||||
addFolder(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ name, path }: { name: string; path: string }
|
||||
) {
|
||||
const newFolder: Collection<HoppRESTRequest> = makeCollection({
|
||||
name,
|
||||
folders: [],
|
||||
requests: [],
|
||||
})
|
||||
|
||||
const newState = state
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const target = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (target === null) {
|
||||
console.log(`Could not parse path '${path}'. Ignoring add folder request`)
|
||||
return {}
|
||||
}
|
||||
|
||||
target.folders.push(newFolder)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
editFolder(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ path, folder }: { path: string; folder: string }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const target = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (target === null) {
|
||||
console.log(
|
||||
`Could not parse path '${path}'. Ignoring edit folder request`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
Object.assign(target, folder)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
removeFolder({ state }: RESTCollectionStoreType, { path }: { path: string }) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
if (indexPaths.length === 0) {
|
||||
console.log(
|
||||
"Given path too short. If this is a collection, use removeCollection dispatcher instead. Skipping request."
|
||||
)
|
||||
return {}
|
||||
}
|
||||
// We get the index path to the folder itself,
|
||||
// we have to find the folder containing the target folder,
|
||||
// so we pop the last path index
|
||||
const folderIndex = indexPaths.pop() as number
|
||||
|
||||
const containingFolder = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (containingFolder === null) {
|
||||
console.log(
|
||||
`Could not resolve path '${path}'. Skipping removeFolder dispatch.`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
containingFolder.folders.splice(folderIndex, 1)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
editRequest(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{
|
||||
path,
|
||||
requestIndex,
|
||||
requestNew,
|
||||
}: { path: string; requestIndex: number; requestNew: any }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (targetLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve path '${path}'. Ignoring editRequest dispatch.`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
targetLocation.requests = targetLocation.requests.map((req, index) =>
|
||||
index !== requestIndex ? req : requestNew
|
||||
)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
saveRequestAs(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ path, request }: { path: string; request: any }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (targetLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve path '${path}'. Ignoring saveRequestAs dispatch.`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
targetLocation.requests.push(request)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
removeRequest(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{ path, requestIndex }: { path: string; requestIndex: number }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (targetLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve path '${path}'. Ignoring removeRequest dispatch.`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
targetLocation.requests.splice(requestIndex, 1)
|
||||
|
||||
// If the save context is set and is set to the same source, we invalidate it
|
||||
const saveCtx = getRESTSaveContext()
|
||||
if (
|
||||
saveCtx?.originLocation === "user-collection" &&
|
||||
saveCtx.folderPath === path &&
|
||||
saveCtx.requestIndex === requestIndex
|
||||
) {
|
||||
setRESTSaveContext(null)
|
||||
}
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
moveRequest(
|
||||
{ state }: RESTCollectionStoreType,
|
||||
{
|
||||
path,
|
||||
requestIndex,
|
||||
destinationPath,
|
||||
}: { path: string; requestIndex: number; destinationPath: string }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (targetLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve source path '${path}'. Skipping moveRequest dispatch.`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
const req = targetLocation.requests[requestIndex]
|
||||
|
||||
const destIndexPaths = destinationPath.split("/").map((x) => parseInt(x))
|
||||
|
||||
const destLocation = navigateToFolderWithIndexPath(newState, destIndexPaths)
|
||||
|
||||
if (destLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve destination path '${destinationPath}'. Skipping moveRequest dispatch.`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
destLocation.requests.push(req)
|
||||
targetLocation.requests.splice(requestIndex, 1)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const gqlCollectionDispatchers = defineDispatchers({
|
||||
setCollections(
|
||||
_: GraphqlCollectionStoreType,
|
||||
{ entries }: { entries: Collection<any>[] }
|
||||
) {
|
||||
return {
|
||||
state: entries,
|
||||
}
|
||||
},
|
||||
|
||||
appendCollections(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ entries }: { entries: Collection<any>[] }
|
||||
) {
|
||||
return {
|
||||
state: [...state, ...entries],
|
||||
}
|
||||
},
|
||||
|
||||
addCollection(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ collection }: { collection: Collection<any> }
|
||||
) {
|
||||
return {
|
||||
state: [...state, collection],
|
||||
}
|
||||
},
|
||||
|
||||
removeCollection(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ collectionIndex }: { collectionIndex: number }
|
||||
) {
|
||||
return {
|
||||
state: (state as any).filter(
|
||||
(_: any, i: number) => i !== collectionIndex
|
||||
),
|
||||
}
|
||||
},
|
||||
|
||||
editCollection(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{
|
||||
collectionIndex,
|
||||
collection,
|
||||
}: { collectionIndex: number; collection: Collection<any> }
|
||||
) {
|
||||
return {
|
||||
state: state.map((col, index) =>
|
||||
index === collectionIndex ? collection : col
|
||||
),
|
||||
}
|
||||
},
|
||||
|
||||
addFolder(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ name, path }: { name: string; path: string }
|
||||
) {
|
||||
const newFolder: Collection<HoppGQLRequest> = makeCollection({
|
||||
name,
|
||||
folders: [],
|
||||
requests: [],
|
||||
})
|
||||
|
||||
const newState = state
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const target = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (target === null) {
|
||||
console.log(`Could not parse path '${path}'. Ignoring add folder request`)
|
||||
return {}
|
||||
}
|
||||
|
||||
target.folders.push(newFolder)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
editFolder(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ path, folder }: { path: string; folder: string }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const target = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (target === null) {
|
||||
console.log(
|
||||
`Could not parse path '${path}'. Ignoring edit folder request`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
Object.assign(target, folder)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
removeFolder(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ path }: { path: string }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
if (indexPaths.length === 0) {
|
||||
console.log(
|
||||
"Given path too short. If this is a collection, use removeCollection dispatcher instead. Skipping request."
|
||||
)
|
||||
return {}
|
||||
}
|
||||
// We get the index path to the folder itself,
|
||||
// we have to find the folder containing the target folder,
|
||||
// so we pop the last path index
|
||||
const folderIndex = indexPaths.pop() as number
|
||||
|
||||
const containingFolder = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (containingFolder === null) {
|
||||
console.log(
|
||||
`Could not resolve path '${path}'. Skipping removeFolder dispatch.`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
containingFolder.folders.splice(folderIndex, 1)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
editRequest(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{
|
||||
path,
|
||||
requestIndex,
|
||||
requestNew,
|
||||
}: { path: string; requestIndex: number; requestNew: any }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (targetLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve path '${path}'. Ignoring editRequest dispatch.`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
targetLocation.requests = targetLocation.requests.map((req, index) =>
|
||||
index !== requestIndex ? req : requestNew
|
||||
)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
saveRequestAs(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ path, request }: { path: string; request: any }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (targetLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve path '${path}'. Ignoring saveRequestAs dispatch.`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
targetLocation.requests.push(request)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
removeRequest(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{ path, requestIndex }: { path: string; requestIndex: number }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (targetLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve path '${path}'. Ignoring removeRequest dispatch.`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
targetLocation.requests.splice(requestIndex, 1)
|
||||
|
||||
// If the save context is set and is set to the same source, we invalidate it
|
||||
const saveCtx = getRESTSaveContext()
|
||||
if (
|
||||
saveCtx?.originLocation === "user-collection" &&
|
||||
saveCtx.folderPath === path &&
|
||||
saveCtx.requestIndex === requestIndex
|
||||
) {
|
||||
setRESTSaveContext(null)
|
||||
}
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
|
||||
moveRequest(
|
||||
{ state }: GraphqlCollectionStoreType,
|
||||
{
|
||||
path,
|
||||
requestIndex,
|
||||
destinationPath,
|
||||
}: { path: string; requestIndex: number; destinationPath: string }
|
||||
) {
|
||||
const newState = state
|
||||
|
||||
const indexPaths = path.split("/").map((x) => parseInt(x))
|
||||
|
||||
const targetLocation = navigateToFolderWithIndexPath(newState, indexPaths)
|
||||
|
||||
if (targetLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve source path '${path}'. Skipping moveRequest dispatch.`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
const req = targetLocation.requests[requestIndex]
|
||||
|
||||
const destIndexPaths = destinationPath.split("/").map((x) => parseInt(x))
|
||||
|
||||
const destLocation = navigateToFolderWithIndexPath(newState, destIndexPaths)
|
||||
|
||||
if (destLocation === null) {
|
||||
console.log(
|
||||
`Could not resolve destination path '${destinationPath}'. Skipping moveRequest dispatch.`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
destLocation.requests.push(req)
|
||||
targetLocation.requests.splice(requestIndex, 1)
|
||||
|
||||
return {
|
||||
state: newState,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const restCollectionStore = new DispatchingStore(
|
||||
defaultRESTCollectionState,
|
||||
restCollectionDispatchers
|
||||
)
|
||||
|
||||
export const graphqlCollectionStore = new DispatchingStore(
|
||||
defaultGraphqlCollectionState,
|
||||
gqlCollectionDispatchers
|
||||
)
|
||||
|
||||
export function setRESTCollections(entries: Collection<HoppRESTRequest>[]) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "setCollections",
|
||||
payload: {
|
||||
entries,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const restCollections$ = restCollectionStore.subject$.pipe(
|
||||
pluck("state")
|
||||
)
|
||||
|
||||
export const graphqlCollections$ = graphqlCollectionStore.subject$.pipe(
|
||||
pluck("state")
|
||||
)
|
||||
|
||||
export function appendRESTCollections(entries: Collection<HoppRESTRequest>[]) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "appendCollections",
|
||||
payload: {
|
||||
entries,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addRESTCollection(collection: Collection<HoppRESTRequest>) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "addCollection",
|
||||
payload: {
|
||||
collection,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeRESTCollection(collectionIndex: number) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "removeCollection",
|
||||
payload: {
|
||||
collectionIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function editRESTCollection(
|
||||
collectionIndex: number,
|
||||
collection: Collection<HoppRESTRequest>
|
||||
) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "editCollection",
|
||||
payload: {
|
||||
collectionIndex,
|
||||
collection,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addRESTFolder(name: string, path: string) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "addFolder",
|
||||
payload: {
|
||||
name,
|
||||
path,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function editRESTFolder(
|
||||
path: string,
|
||||
folder: Collection<HoppRESTRequest>
|
||||
) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "editFolder",
|
||||
payload: {
|
||||
path,
|
||||
folder,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeRESTFolder(path: string) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "removeFolder",
|
||||
payload: {
|
||||
path,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function editRESTRequest(
|
||||
path: string,
|
||||
requestIndex: number,
|
||||
requestNew: HoppRESTRequest
|
||||
) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "editRequest",
|
||||
payload: {
|
||||
path,
|
||||
requestIndex,
|
||||
requestNew,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function saveRESTRequestAs(path: string, request: HoppRESTRequest) {
|
||||
// For calculating the insertion request index
|
||||
const targetLocation = navigateToFolderWithIndexPath(
|
||||
restCollectionStore.value.state,
|
||||
path.split("/").map((x) => parseInt(x))
|
||||
)
|
||||
|
||||
const insertionIndex = targetLocation!.requests.length
|
||||
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "saveRequestAs",
|
||||
payload: {
|
||||
path,
|
||||
request,
|
||||
},
|
||||
})
|
||||
|
||||
return insertionIndex
|
||||
}
|
||||
|
||||
export function removeRESTRequest(path: string, requestIndex: number) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "removeRequest",
|
||||
payload: {
|
||||
path,
|
||||
requestIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function moveRESTRequest(
|
||||
path: string,
|
||||
requestIndex: number,
|
||||
destinationPath: string
|
||||
) {
|
||||
restCollectionStore.dispatch({
|
||||
dispatcher: "moveRequest",
|
||||
payload: {
|
||||
path,
|
||||
requestIndex,
|
||||
destinationPath,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setGraphqlCollections(entries: Collection<HoppGQLRequest>[]) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "setCollections",
|
||||
payload: {
|
||||
entries,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function appendGraphqlCollections(
|
||||
entries: Collection<HoppGQLRequest>[]
|
||||
) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "appendCollections",
|
||||
payload: {
|
||||
entries,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addGraphqlCollection(collection: Collection<HoppGQLRequest>) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "addCollection",
|
||||
payload: {
|
||||
collection,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeGraphqlCollection(collectionIndex: number) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "removeCollection",
|
||||
payload: {
|
||||
collectionIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function editGraphqlCollection(
|
||||
collectionIndex: number,
|
||||
collection: Collection<HoppGQLRequest>
|
||||
) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "editCollection",
|
||||
payload: {
|
||||
collectionIndex,
|
||||
collection,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addGraphqlFolder(name: string, path: string) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "addFolder",
|
||||
payload: {
|
||||
name,
|
||||
path,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function editGraphqlFolder(
|
||||
path: string,
|
||||
folder: Collection<HoppGQLRequest>
|
||||
) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "editFolder",
|
||||
payload: {
|
||||
path,
|
||||
folder,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeGraphqlFolder(path: string) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "removeFolder",
|
||||
payload: {
|
||||
path,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function editGraphqlRequest(
|
||||
path: string,
|
||||
requestIndex: number,
|
||||
requestNew: HoppGQLRequest
|
||||
) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "editRequest",
|
||||
payload: {
|
||||
path,
|
||||
requestIndex,
|
||||
requestNew,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function saveGraphqlRequestAs(path: string, request: HoppGQLRequest) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "saveRequestAs",
|
||||
payload: {
|
||||
path,
|
||||
request,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeGraphqlRequest(path: string, requestIndex: number) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "removeRequest",
|
||||
payload: {
|
||||
path,
|
||||
requestIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function moveGraphqlRequest(
|
||||
path: string,
|
||||
requestIndex: number,
|
||||
destinationPath: string
|
||||
) {
|
||||
graphqlCollectionStore.dispatch({
|
||||
dispatcher: "moveRequest",
|
||||
payload: {
|
||||
path,
|
||||
requestIndex,
|
||||
destinationPath,
|
||||
},
|
||||
})
|
||||
}
|
||||
489
packages/hoppscotch-app/newstore/environments.ts
Normal file
489
packages/hoppscotch-app/newstore/environments.ts
Normal file
@@ -0,0 +1,489 @@
|
||||
import isEqual from "lodash/isEqual"
|
||||
import { combineLatest } from "rxjs"
|
||||
import { distinctUntilChanged, map, pluck } from "rxjs/operators"
|
||||
import DispatchingStore, {
|
||||
defineDispatchers,
|
||||
} from "~/newstore/DispatchingStore"
|
||||
|
||||
export type Environment = {
|
||||
name: string
|
||||
variables: {
|
||||
key: string
|
||||
value: string
|
||||
}[]
|
||||
}
|
||||
|
||||
const defaultEnvironmentsState = {
|
||||
environments: [
|
||||
{
|
||||
name: "My Environment Variables",
|
||||
variables: [],
|
||||
},
|
||||
] as Environment[],
|
||||
|
||||
globals: [] as Environment["variables"],
|
||||
|
||||
// Current environment index specifies the index
|
||||
// -1 means no environments are selected
|
||||
currentEnvironmentIndex: -1,
|
||||
}
|
||||
|
||||
type EnvironmentStore = typeof defaultEnvironmentsState
|
||||
|
||||
const dispatchers = defineDispatchers({
|
||||
setCurrentEnviromentIndex(
|
||||
{ environments }: EnvironmentStore,
|
||||
{ newIndex }: { newIndex: number }
|
||||
) {
|
||||
if (newIndex >= environments.length || newIndex <= -2) {
|
||||
console.log(
|
||||
`Ignoring possibly invalid current environment index assignment (value: ${newIndex})`
|
||||
)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
currentEnvironmentIndex: newIndex,
|
||||
}
|
||||
},
|
||||
appendEnvironments(
|
||||
{ environments }: EnvironmentStore,
|
||||
{ envs }: { envs: Environment[] }
|
||||
) {
|
||||
return {
|
||||
environments: [...environments, ...envs],
|
||||
}
|
||||
},
|
||||
replaceEnvironments(
|
||||
_: EnvironmentStore,
|
||||
{ environments }: { environments: Environment[] }
|
||||
) {
|
||||
return {
|
||||
environments,
|
||||
}
|
||||
},
|
||||
createEnvironment(
|
||||
{ environments }: EnvironmentStore,
|
||||
{ name }: { name: string }
|
||||
) {
|
||||
return {
|
||||
environments: [
|
||||
...environments,
|
||||
{
|
||||
name,
|
||||
variables: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
deleteEnvironment(
|
||||
{ environments, currentEnvironmentIndex }: EnvironmentStore,
|
||||
{ envIndex }: { envIndex: number }
|
||||
) {
|
||||
return {
|
||||
environments: environments.filter((_, index) => index !== envIndex),
|
||||
currentEnvironmentIndex:
|
||||
envIndex === currentEnvironmentIndex ? -1 : currentEnvironmentIndex,
|
||||
}
|
||||
},
|
||||
renameEnvironment(
|
||||
{ environments }: EnvironmentStore,
|
||||
{ envIndex, newName }: { envIndex: number; newName: string }
|
||||
) {
|
||||
return {
|
||||
environments: environments.map((env, index) =>
|
||||
index === envIndex
|
||||
? {
|
||||
...env,
|
||||
name: newName,
|
||||
}
|
||||
: env
|
||||
),
|
||||
}
|
||||
},
|
||||
updateEnvironment(
|
||||
{ environments }: EnvironmentStore,
|
||||
{ envIndex, updatedEnv }: { envIndex: number; updatedEnv: Environment }
|
||||
) {
|
||||
return {
|
||||
environments: environments.map((env, index) =>
|
||||
index === envIndex ? updatedEnv : env
|
||||
),
|
||||
}
|
||||
},
|
||||
addEnvironmentVariable(
|
||||
{ environments }: EnvironmentStore,
|
||||
{ envIndex, key, value }: { envIndex: number; key: string; value: string }
|
||||
) {
|
||||
return {
|
||||
environments: environments.map((env, index) =>
|
||||
index === envIndex
|
||||
? {
|
||||
...env,
|
||||
variables: [...env.variables, { key, value }],
|
||||
}
|
||||
: env
|
||||
),
|
||||
}
|
||||
},
|
||||
removeEnvironmentVariable(
|
||||
{ environments }: EnvironmentStore,
|
||||
{ envIndex, variableIndex }: { envIndex: number; variableIndex: number }
|
||||
) {
|
||||
return {
|
||||
environments: environments.map((env, index) =>
|
||||
index === envIndex
|
||||
? {
|
||||
...env,
|
||||
variables: env.variables.filter(
|
||||
(_, vIndex) => vIndex !== variableIndex
|
||||
),
|
||||
}
|
||||
: env
|
||||
),
|
||||
}
|
||||
},
|
||||
setEnvironmentVariables(
|
||||
{ environments }: EnvironmentStore,
|
||||
{
|
||||
envIndex,
|
||||
vars,
|
||||
}: { envIndex: number; vars: { key: string; value: string }[] }
|
||||
) {
|
||||
return {
|
||||
environments: environments.map((env, index) =>
|
||||
index === envIndex
|
||||
? {
|
||||
...env,
|
||||
variables: vars,
|
||||
}
|
||||
: env
|
||||
),
|
||||
}
|
||||
},
|
||||
updateEnvironmentVariable(
|
||||
{ environments }: EnvironmentStore,
|
||||
{
|
||||
envIndex,
|
||||
variableIndex,
|
||||
updatedKey,
|
||||
updatedValue,
|
||||
}: {
|
||||
envIndex: number
|
||||
variableIndex: number
|
||||
updatedKey: string
|
||||
updatedValue: string
|
||||
}
|
||||
) {
|
||||
return {
|
||||
environments: environments.map((env, index) =>
|
||||
index === envIndex
|
||||
? {
|
||||
...env,
|
||||
variables: env.variables.map((v, vIndex) =>
|
||||
vIndex === variableIndex
|
||||
? { key: updatedKey, value: updatedValue }
|
||||
: v
|
||||
),
|
||||
}
|
||||
: env
|
||||
),
|
||||
}
|
||||
},
|
||||
setGlobalVariables(_, { entries }: { entries: Environment["variables"] }) {
|
||||
return {
|
||||
globals: entries,
|
||||
}
|
||||
},
|
||||
clearGlobalVariables() {
|
||||
return {
|
||||
globals: [],
|
||||
}
|
||||
},
|
||||
addGlobalVariable(
|
||||
{ globals },
|
||||
{ entry }: { entry: Environment["variables"][number] }
|
||||
) {
|
||||
return {
|
||||
globals: [...globals, entry],
|
||||
}
|
||||
},
|
||||
removeGlobalVariable({ globals }, { envIndex }: { envIndex: number }) {
|
||||
return {
|
||||
globals: globals.filter((_, i) => i !== envIndex),
|
||||
}
|
||||
},
|
||||
updateGlobalVariable(
|
||||
{ globals },
|
||||
{
|
||||
envIndex,
|
||||
updatedEntry,
|
||||
}: { envIndex: number; updatedEntry: Environment["variables"][number] }
|
||||
) {
|
||||
return {
|
||||
globals: globals.map((x, i) => (i !== envIndex ? x : updatedEntry)),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const environmentsStore = new DispatchingStore(
|
||||
defaultEnvironmentsState,
|
||||
dispatchers
|
||||
)
|
||||
|
||||
export const environments$ = environmentsStore.subject$.pipe(
|
||||
pluck("environments"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const globalEnv$ = environmentsStore.subject$.pipe(
|
||||
pluck("globals"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const selectedEnvIndex$ = environmentsStore.subject$.pipe(
|
||||
pluck("currentEnvironmentIndex"),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
export const currentEnvironment$ = combineLatest([
|
||||
environments$,
|
||||
selectedEnvIndex$,
|
||||
]).pipe(
|
||||
map(([envs, selectedIndex]) => {
|
||||
if (selectedIndex === -1) {
|
||||
const env: Environment = {
|
||||
name: "No environment",
|
||||
variables: [],
|
||||
}
|
||||
|
||||
return env
|
||||
} else {
|
||||
return envs[selectedIndex]
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Stream returning all the environment variables accessible in
|
||||
* the current state (Global + The Selected Environment).
|
||||
* NOTE: The source environment attribute will be "Global" for Global Env as source.
|
||||
*/
|
||||
export const aggregateEnvs$ = combineLatest([
|
||||
currentEnvironment$,
|
||||
globalEnv$,
|
||||
]).pipe(
|
||||
map(([selectedEnv, globalVars]) => {
|
||||
const results: { key: string; value: string; sourceEnv: string }[] = []
|
||||
|
||||
selectedEnv.variables.forEach(({ key, value }) =>
|
||||
results.push({ key, value, sourceEnv: selectedEnv.name })
|
||||
)
|
||||
globalVars.forEach(({ key, value }) =>
|
||||
results.push({ key, value, sourceEnv: "Global" })
|
||||
)
|
||||
|
||||
return results
|
||||
}),
|
||||
distinctUntilChanged(isEqual)
|
||||
)
|
||||
|
||||
export function getCurrentEnvironment(): Environment {
|
||||
if (environmentsStore.value.currentEnvironmentIndex === -1) {
|
||||
return {
|
||||
name: "No environment",
|
||||
variables: [],
|
||||
}
|
||||
}
|
||||
|
||||
return environmentsStore.value.environments[
|
||||
environmentsStore.value.currentEnvironmentIndex
|
||||
]
|
||||
}
|
||||
|
||||
export function setCurrentEnvironment(newEnvIndex: number) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "setCurrentEnviromentIndex",
|
||||
payload: {
|
||||
newIndex: newEnvIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getLegacyGlobalEnvironment(): Environment | null {
|
||||
const envs = environmentsStore.value.environments
|
||||
|
||||
const el = envs.find(
|
||||
(env) => env.name === "globals" || env.name === "Globals"
|
||||
)
|
||||
|
||||
return el ?? null
|
||||
}
|
||||
|
||||
export function getGlobalVariables(): Environment["variables"] {
|
||||
return environmentsStore.value.globals
|
||||
}
|
||||
|
||||
export function addGlobalEnvVariable(entry: Environment["variables"][number]) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "addGlobalVariable",
|
||||
payload: {
|
||||
entry,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setGlobalEnvVariables(entries: Environment["variables"]) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "setGlobalVariables",
|
||||
payload: {
|
||||
entries,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function clearGlobalEnvVariables() {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "clearGlobalVariables",
|
||||
payload: {},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeGlobalEnvVariable(envIndex: number) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "removeGlobalVariable",
|
||||
payload: {
|
||||
envIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateGlobalEnvVariable(
|
||||
envIndex: number,
|
||||
updatedEntry: Environment["variables"][number]
|
||||
) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "updateGlobalVariable",
|
||||
payload: {
|
||||
envIndex,
|
||||
updatedEntry,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function replaceEnvironments(newEnvironments: any[]) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "replaceEnvironments",
|
||||
payload: {
|
||||
environments: newEnvironments,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function appendEnvironments(envs: Environment[]) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "appendEnvironments",
|
||||
payload: {
|
||||
envs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function createEnvironment(envName: string) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "createEnvironment",
|
||||
payload: {
|
||||
name: envName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteEnvironment(envIndex: number) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "deleteEnvironment",
|
||||
payload: {
|
||||
envIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function renameEnvironment(envIndex: number, newName: string) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "renameEnvironment",
|
||||
payload: {
|
||||
envIndex,
|
||||
newName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateEnvironment(envIndex: number, updatedEnv: Environment) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "updateEnvironment",
|
||||
payload: {
|
||||
envIndex,
|
||||
updatedEnv,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function setEnvironmentVariables(
|
||||
envIndex: number,
|
||||
vars: { key: string; value: string }[]
|
||||
) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "setEnvironmentVariables",
|
||||
payload: {
|
||||
envIndex,
|
||||
vars,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function addEnvironmentVariable(
|
||||
envIndex: number,
|
||||
{ key, value }: { key: string; value: string }
|
||||
) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "addEnvironmentVariable",
|
||||
payload: {
|
||||
envIndex,
|
||||
key,
|
||||
value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function removeEnvironmentVariable(
|
||||
envIndex: number,
|
||||
variableIndex: number
|
||||
) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "removeEnvironmentVariable",
|
||||
payload: {
|
||||
envIndex,
|
||||
variableIndex,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function updateEnvironmentVariable(
|
||||
envIndex: number,
|
||||
variableIndex: number,
|
||||
{ key, value }: { key: string; value: string }
|
||||
) {
|
||||
environmentsStore.dispatch({
|
||||
dispatcher: "updateEnvironmentVariable",
|
||||
payload: {
|
||||
envIndex,
|
||||
variableIndex,
|
||||
updatedKey: key,
|
||||
updatedValue: value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getEnviroment(index: number) {
|
||||
return environmentsStore.value.environments[index]
|
||||
}
|
||||
305
packages/hoppscotch-app/newstore/history.ts
Normal file
305
packages/hoppscotch-app/newstore/history.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import eq from "lodash/eq"
|
||||
import { pluck } from "rxjs/operators"
|
||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||
import { completedRESTResponse$ } from "./RESTSession"
|
||||
import {
|
||||
HoppRESTRequest,
|
||||
translateToNewRequest,
|
||||
} from "~/helpers/types/HoppRESTRequest"
|
||||
import {
|
||||
HoppGQLRequest,
|
||||
translateToGQLRequest,
|
||||
} from "~/helpers/types/HoppGQLRequest"
|
||||
|
||||
export type RESTHistoryEntry = {
|
||||
v: number
|
||||
|
||||
request: HoppRESTRequest
|
||||
|
||||
responseMeta: {
|
||||
duration: number | null
|
||||
statusCode: number | null
|
||||
}
|
||||
|
||||
star: boolean
|
||||
|
||||
id?: string // For when Firebase Firestore is set
|
||||
}
|
||||
|
||||
export type GQLHistoryEntry = {
|
||||
v: number
|
||||
request: HoppGQLRequest
|
||||
|
||||
response: string
|
||||
|
||||
star: boolean
|
||||
|
||||
id?: string // For when Firestore ID is set
|
||||
}
|
||||
|
||||
export function makeRESTHistoryEntry(
|
||||
x: Omit<RESTHistoryEntry, "v">
|
||||
): RESTHistoryEntry {
|
||||
return {
|
||||
v: 1,
|
||||
...x,
|
||||
}
|
||||
}
|
||||
|
||||
export function makeGQLHistoryEntry(
|
||||
x: Omit<GQLHistoryEntry, "v">
|
||||
): GQLHistoryEntry {
|
||||
return {
|
||||
v: 1,
|
||||
...x,
|
||||
}
|
||||
}
|
||||
|
||||
export function translateToNewRESTHistory(x: any): RESTHistoryEntry {
|
||||
if (x.v === 1) return x
|
||||
|
||||
// Legacy
|
||||
const request = translateToNewRequest(x)
|
||||
const star = x.star ?? false
|
||||
const duration = x.duration ?? null
|
||||
const statusCode = x.status ?? null
|
||||
|
||||
const obj: RESTHistoryEntry = makeRESTHistoryEntry({
|
||||
request,
|
||||
star,
|
||||
responseMeta: {
|
||||
duration,
|
||||
statusCode,
|
||||
},
|
||||
})
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
export function translateToNewGQLHistory(x: any): GQLHistoryEntry {
|
||||
if (x.v === 1) return x
|
||||
|
||||
// Legacy
|
||||
const request = translateToGQLRequest(x)
|
||||
const star = x.star ?? false
|
||||
const response = x.response ?? ""
|
||||
|
||||
const obj: GQLHistoryEntry = makeGQLHistoryEntry({
|
||||
request,
|
||||
star,
|
||||
response,
|
||||
})
|
||||
|
||||
if (x.id) obj.id = x.id
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
export const defaultRESTHistoryState = {
|
||||
state: [] as RESTHistoryEntry[],
|
||||
}
|
||||
|
||||
export const defaultGraphqlHistoryState = {
|
||||
state: [] as GQLHistoryEntry[],
|
||||
}
|
||||
|
||||
export const HISTORY_LIMIT = 50
|
||||
|
||||
type RESTHistoryType = typeof defaultRESTHistoryState
|
||||
type GraphqlHistoryType = typeof defaultGraphqlHistoryState
|
||||
|
||||
const RESTHistoryDispatchers = defineDispatchers({
|
||||
setEntries(_: RESTHistoryType, { entries }: { entries: RESTHistoryEntry[] }) {
|
||||
return {
|
||||
state: entries,
|
||||
}
|
||||
},
|
||||
addEntry(
|
||||
currentVal: RESTHistoryType,
|
||||
{ entry }: { entry: RESTHistoryEntry }
|
||||
) {
|
||||
return {
|
||||
state: [entry, ...currentVal.state].slice(0, HISTORY_LIMIT),
|
||||
}
|
||||
},
|
||||
deleteEntry(
|
||||
currentVal: RESTHistoryType,
|
||||
{ entry }: { entry: RESTHistoryEntry }
|
||||
) {
|
||||
return {
|
||||
state: currentVal.state.filter((e) => !eq(e, entry)),
|
||||
}
|
||||
},
|
||||
clearHistory() {
|
||||
return {
|
||||
state: [],
|
||||
}
|
||||
},
|
||||
toggleStar(
|
||||
currentVal: RESTHistoryType,
|
||||
{ entry }: { entry: RESTHistoryEntry }
|
||||
) {
|
||||
return {
|
||||
state: currentVal.state.map((e) => {
|
||||
if (eq(e, entry) && e.star !== undefined) {
|
||||
return {
|
||||
...e,
|
||||
star: !e.star,
|
||||
}
|
||||
}
|
||||
return e
|
||||
}),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const GQLHistoryDispatchers = defineDispatchers({
|
||||
setEntries(
|
||||
_: GraphqlHistoryType,
|
||||
{ entries }: { entries: GQLHistoryEntry[] }
|
||||
) {
|
||||
return {
|
||||
state: entries,
|
||||
}
|
||||
},
|
||||
addEntry(
|
||||
currentVal: GraphqlHistoryType,
|
||||
{ entry }: { entry: GQLHistoryEntry }
|
||||
) {
|
||||
return {
|
||||
state: [entry, ...currentVal.state].slice(0, HISTORY_LIMIT),
|
||||
}
|
||||
},
|
||||
deleteEntry(
|
||||
currentVal: GraphqlHistoryType,
|
||||
{ entry }: { entry: GQLHistoryEntry }
|
||||
) {
|
||||
return {
|
||||
state: currentVal.state.filter((e) => !eq(e, entry)),
|
||||
}
|
||||
},
|
||||
clearHistory() {
|
||||
return {
|
||||
state: [],
|
||||
}
|
||||
},
|
||||
toggleStar(
|
||||
currentVal: GraphqlHistoryType,
|
||||
{ entry }: { entry: GQLHistoryEntry }
|
||||
) {
|
||||
return {
|
||||
state: currentVal.state.map((e) => {
|
||||
if (eq(e, entry) && e.star !== undefined) {
|
||||
return {
|
||||
...e,
|
||||
star: !e.star,
|
||||
}
|
||||
}
|
||||
return e
|
||||
}),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const restHistoryStore = new DispatchingStore(
|
||||
defaultRESTHistoryState,
|
||||
RESTHistoryDispatchers
|
||||
)
|
||||
|
||||
export const graphqlHistoryStore = new DispatchingStore(
|
||||
defaultGraphqlHistoryState,
|
||||
GQLHistoryDispatchers
|
||||
)
|
||||
|
||||
export const restHistory$ = restHistoryStore.subject$.pipe(pluck("state"))
|
||||
export const graphqlHistory$ = graphqlHistoryStore.subject$.pipe(pluck("state"))
|
||||
|
||||
export function setRESTHistoryEntries(entries: RESTHistoryEntry[]) {
|
||||
restHistoryStore.dispatch({
|
||||
dispatcher: "setEntries",
|
||||
payload: { entries },
|
||||
})
|
||||
}
|
||||
|
||||
export function addRESTHistoryEntry(entry: RESTHistoryEntry) {
|
||||
restHistoryStore.dispatch({
|
||||
dispatcher: "addEntry",
|
||||
payload: { entry },
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRESTHistoryEntry(entry: RESTHistoryEntry) {
|
||||
restHistoryStore.dispatch({
|
||||
dispatcher: "deleteEntry",
|
||||
payload: { entry },
|
||||
})
|
||||
}
|
||||
|
||||
export function clearRESTHistory() {
|
||||
restHistoryStore.dispatch({
|
||||
dispatcher: "clearHistory",
|
||||
payload: {},
|
||||
})
|
||||
}
|
||||
|
||||
export function toggleRESTHistoryEntryStar(entry: RESTHistoryEntry) {
|
||||
restHistoryStore.dispatch({
|
||||
dispatcher: "toggleStar",
|
||||
payload: { entry },
|
||||
})
|
||||
}
|
||||
|
||||
export function setGraphqlHistoryEntries(entries: GQLHistoryEntry[]) {
|
||||
graphqlHistoryStore.dispatch({
|
||||
dispatcher: "setEntries",
|
||||
payload: { entries },
|
||||
})
|
||||
}
|
||||
|
||||
export function addGraphqlHistoryEntry(entry: GQLHistoryEntry) {
|
||||
graphqlHistoryStore.dispatch({
|
||||
dispatcher: "addEntry",
|
||||
payload: { entry },
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteGraphqlHistoryEntry(entry: GQLHistoryEntry) {
|
||||
graphqlHistoryStore.dispatch({
|
||||
dispatcher: "deleteEntry",
|
||||
payload: { entry },
|
||||
})
|
||||
}
|
||||
|
||||
export function clearGraphqlHistory() {
|
||||
graphqlHistoryStore.dispatch({
|
||||
dispatcher: "clearHistory",
|
||||
payload: {},
|
||||
})
|
||||
}
|
||||
|
||||
export function toggleGraphqlHistoryEntryStar(entry: GQLHistoryEntry) {
|
||||
graphqlHistoryStore.dispatch({
|
||||
dispatcher: "toggleStar",
|
||||
payload: { entry },
|
||||
})
|
||||
}
|
||||
|
||||
// Listen to completed responses to add to history
|
||||
completedRESTResponse$.subscribe((res) => {
|
||||
if (res !== null) {
|
||||
if (res.type === "loading" || res.type === "network_fail") return
|
||||
|
||||
addRESTHistoryEntry(
|
||||
makeRESTHistoryEntry({
|
||||
request: res.req,
|
||||
responseMeta: {
|
||||
duration: res.meta.responseDuration,
|
||||
statusCode: res.statusCode,
|
||||
},
|
||||
star: false,
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
252
packages/hoppscotch-app/newstore/localpersistence.ts
Normal file
252
packages/hoppscotch-app/newstore/localpersistence.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
/* eslint-disable no-restricted-globals, no-restricted-syntax */
|
||||
|
||||
import clone from "lodash/clone"
|
||||
import assign from "lodash/assign"
|
||||
import isEmpty from "lodash/isEmpty"
|
||||
import {
|
||||
settingsStore,
|
||||
bulkApplySettings,
|
||||
defaultSettings,
|
||||
applySetting,
|
||||
HoppAccentColor,
|
||||
HoppBgColor,
|
||||
} from "./settings"
|
||||
import {
|
||||
restHistoryStore,
|
||||
graphqlHistoryStore,
|
||||
setRESTHistoryEntries,
|
||||
setGraphqlHistoryEntries,
|
||||
translateToNewRESTHistory,
|
||||
translateToNewGQLHistory,
|
||||
} from "./history"
|
||||
import {
|
||||
restCollectionStore,
|
||||
graphqlCollectionStore,
|
||||
setGraphqlCollections,
|
||||
setRESTCollections,
|
||||
translateToNewRESTCollection,
|
||||
translateToNewGQLCollection,
|
||||
} from "./collections"
|
||||
import {
|
||||
replaceEnvironments,
|
||||
environments$,
|
||||
Environment,
|
||||
addGlobalEnvVariable,
|
||||
setGlobalEnvVariables,
|
||||
globalEnv$,
|
||||
} from "./environments"
|
||||
import { restRequest$, setRESTRequest } from "./RESTSession"
|
||||
import { translateToNewRequest } from "~/helpers/types/HoppRESTRequest"
|
||||
|
||||
function checkAndMigrateOldSettings() {
|
||||
const vuexData = JSON.parse(window.localStorage.getItem("vuex") || "{}")
|
||||
if (isEmpty(vuexData)) return
|
||||
|
||||
const { postwoman } = vuexData
|
||||
|
||||
if (!isEmpty(postwoman?.settings)) {
|
||||
const settingsData = assign(clone(defaultSettings), postwoman.settings)
|
||||
|
||||
window.localStorage.setItem("settings", JSON.stringify(settingsData))
|
||||
|
||||
delete postwoman.settings
|
||||
window.localStorage.setItem("vuex", JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
if (postwoman?.collections) {
|
||||
window.localStorage.setItem(
|
||||
"collections",
|
||||
JSON.stringify(postwoman.collections)
|
||||
)
|
||||
|
||||
delete postwoman.collections
|
||||
window.localStorage.setItem("vuex", JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
if (postwoman?.collectionsGraphql) {
|
||||
window.localStorage.setItem(
|
||||
"collectionsGraphql",
|
||||
JSON.stringify(postwoman.collectionsGraphql)
|
||||
)
|
||||
|
||||
delete postwoman.collectionsGraphql
|
||||
window.localStorage.setItem("vuex", JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
if (postwoman?.environments) {
|
||||
window.localStorage.setItem(
|
||||
"environments",
|
||||
JSON.stringify(postwoman.environments)
|
||||
)
|
||||
|
||||
delete postwoman.environments
|
||||
window.localStorage.setItem("vuex", JSON.stringify(vuexData))
|
||||
}
|
||||
|
||||
if (window.localStorage.getItem("THEME_COLOR")) {
|
||||
const themeColor = window.localStorage.getItem("THEME_COLOR")
|
||||
applySetting("THEME_COLOR", themeColor as HoppAccentColor)
|
||||
|
||||
window.localStorage.removeItem("THEME_COLOR")
|
||||
}
|
||||
|
||||
if (window.localStorage.getItem("nuxt-color-mode")) {
|
||||
const color = window.localStorage.getItem("nuxt-color-mode") as HoppBgColor
|
||||
applySetting("BG_COLOR", color)
|
||||
|
||||
window.localStorage.removeItem("nuxt-color-mode")
|
||||
}
|
||||
}
|
||||
|
||||
function setupSettingsPersistence() {
|
||||
const settingsData = JSON.parse(
|
||||
window.localStorage.getItem("settings") || "{}"
|
||||
)
|
||||
|
||||
if (settingsData) {
|
||||
bulkApplySettings(settingsData)
|
||||
}
|
||||
|
||||
settingsStore.subject$.subscribe((settings) => {
|
||||
window.localStorage.setItem("settings", JSON.stringify(settings))
|
||||
})
|
||||
}
|
||||
|
||||
function setupHistoryPersistence() {
|
||||
const restHistoryData = JSON.parse(
|
||||
window.localStorage.getItem("history") || "[]"
|
||||
).map(translateToNewRESTHistory)
|
||||
|
||||
const graphqlHistoryData = JSON.parse(
|
||||
window.localStorage.getItem("graphqlHistory") || "[]"
|
||||
).map(translateToNewGQLHistory)
|
||||
|
||||
setRESTHistoryEntries(restHistoryData)
|
||||
setGraphqlHistoryEntries(graphqlHistoryData)
|
||||
|
||||
restHistoryStore.subject$.subscribe(({ state }) => {
|
||||
window.localStorage.setItem("history", JSON.stringify(state))
|
||||
})
|
||||
|
||||
graphqlHistoryStore.subject$.subscribe(({ state }) => {
|
||||
window.localStorage.setItem("graphqlHistory", JSON.stringify(state))
|
||||
})
|
||||
}
|
||||
|
||||
function setupCollectionsPersistence() {
|
||||
const restCollectionData = JSON.parse(
|
||||
window.localStorage.getItem("collections") || "[]"
|
||||
).map(translateToNewRESTCollection)
|
||||
|
||||
const graphqlCollectionData = JSON.parse(
|
||||
window.localStorage.getItem("collectionsGraphql") || "[]"
|
||||
).map(translateToNewGQLCollection)
|
||||
|
||||
setRESTCollections(restCollectionData)
|
||||
setGraphqlCollections(graphqlCollectionData)
|
||||
|
||||
restCollectionStore.subject$.subscribe(({ state }) => {
|
||||
window.localStorage.setItem("collections", JSON.stringify(state))
|
||||
})
|
||||
|
||||
graphqlCollectionStore.subject$.subscribe(({ state }) => {
|
||||
window.localStorage.setItem("collectionsGraphql", JSON.stringify(state))
|
||||
})
|
||||
}
|
||||
|
||||
function setupEnvironmentsPersistence() {
|
||||
const environmentsData: Environment[] = JSON.parse(
|
||||
window.localStorage.getItem("environments") || "[]"
|
||||
)
|
||||
|
||||
// Check if a global env is defined and if so move that to globals
|
||||
const globalIndex = environmentsData.findIndex(
|
||||
(x) => x.name.toLowerCase() === "globals"
|
||||
)
|
||||
|
||||
if (globalIndex !== -1) {
|
||||
const globalEnv = environmentsData[globalIndex]
|
||||
globalEnv.variables.forEach((variable) => addGlobalEnvVariable(variable))
|
||||
|
||||
// Remove global from environments
|
||||
environmentsData.splice(globalIndex, 1)
|
||||
|
||||
// Just sync the changes manually
|
||||
window.localStorage.setItem(
|
||||
"environments",
|
||||
JSON.stringify(environmentsData)
|
||||
)
|
||||
}
|
||||
|
||||
replaceEnvironments(environmentsData)
|
||||
|
||||
environments$.subscribe((envs) => {
|
||||
window.localStorage.setItem("environments", JSON.stringify(envs))
|
||||
})
|
||||
}
|
||||
|
||||
function setupGlobalEnvsPersistence() {
|
||||
const globals: Environment["variables"] = JSON.parse(
|
||||
window.localStorage.getItem("globalEnv") || "[]"
|
||||
)
|
||||
|
||||
setGlobalEnvVariables(globals)
|
||||
|
||||
globalEnv$.subscribe((vars) => {
|
||||
window.localStorage.setItem("globalEnv", JSON.stringify(vars))
|
||||
})
|
||||
}
|
||||
|
||||
function setupRequestPersistence() {
|
||||
const localRequest = JSON.parse(
|
||||
window.localStorage.getItem("restRequest") || "null"
|
||||
)
|
||||
|
||||
if (localRequest) {
|
||||
const parsedLocal = translateToNewRequest(localRequest)
|
||||
setRESTRequest(parsedLocal)
|
||||
}
|
||||
|
||||
restRequest$.subscribe((req) => {
|
||||
window.localStorage.setItem("restRequest", JSON.stringify(req))
|
||||
})
|
||||
}
|
||||
|
||||
export function setupLocalPersistence() {
|
||||
checkAndMigrateOldSettings()
|
||||
|
||||
setupSettingsPersistence()
|
||||
setupRequestPersistence()
|
||||
setupHistoryPersistence()
|
||||
setupCollectionsPersistence()
|
||||
setupGlobalEnvsPersistence()
|
||||
setupEnvironmentsPersistence()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value in LocalStorage.
|
||||
*
|
||||
* NOTE: Use LocalStorage to only store non-reactive simple data
|
||||
* For more complex data, use stores and connect it to localpersistence
|
||||
*/
|
||||
export function getLocalConfig(name: string) {
|
||||
return window.localStorage.getItem(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value in LocalStorage.
|
||||
*
|
||||
* NOTE: Use LocalStorage to only store non-reactive simple data
|
||||
* For more complex data, use stores and connect it to localpersistence
|
||||
*/
|
||||
export function setLocalConfig(key: string, value: string) {
|
||||
window.localStorage.setItem(key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear config value in LocalStorage.
|
||||
* @param key Key to be cleared
|
||||
*/
|
||||
export function removeLocalConfig(key: string) {
|
||||
window.localStorage.removeItem(key)
|
||||
}
|
||||
184
packages/hoppscotch-app/newstore/settings.ts
Normal file
184
packages/hoppscotch-app/newstore/settings.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { pluck, distinctUntilChanged } from "rxjs/operators"
|
||||
import has from "lodash/has"
|
||||
import { Observable } from "rxjs"
|
||||
import { Ref } from "@nuxtjs/composition-api"
|
||||
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||
import type { KeysMatching } from "~/types/ts-utils"
|
||||
import { useStream } from "~/helpers/utils/composables"
|
||||
|
||||
export const HoppBgColors = ["system", "light", "dark", "black"] as const
|
||||
|
||||
export type HoppBgColor = typeof HoppBgColors[number]
|
||||
|
||||
export const HoppAccentColors = [
|
||||
"green",
|
||||
"teal",
|
||||
"blue",
|
||||
"indigo",
|
||||
"purple",
|
||||
"yellow",
|
||||
"orange",
|
||||
"red",
|
||||
"pink",
|
||||
] as const
|
||||
|
||||
export type HoppAccentColor = typeof HoppAccentColors[number]
|
||||
|
||||
export const HoppFontSizes = ["small", "medium", "large"] as const
|
||||
|
||||
export type HoppFontSize = typeof HoppFontSizes[number]
|
||||
|
||||
export type SettingsType = {
|
||||
syncCollections: boolean
|
||||
syncHistory: boolean
|
||||
syncEnvironments: boolean
|
||||
|
||||
PROXY_ENABLED: boolean
|
||||
PROXY_URL: string
|
||||
PROXY_KEY: string
|
||||
EXTENSIONS_ENABLED: boolean
|
||||
EXPERIMENTAL_URL_BAR_ENABLED: boolean
|
||||
URL_EXCLUDES: {
|
||||
auth: boolean
|
||||
httpUser: boolean
|
||||
httpPassword: boolean
|
||||
bearerToken: boolean
|
||||
oauth2Token: boolean
|
||||
}
|
||||
THEME_COLOR: HoppAccentColor
|
||||
BG_COLOR: HoppBgColor
|
||||
TELEMETRY_ENABLED: boolean
|
||||
LEFT_SIDEBAR: boolean
|
||||
RIGHT_SIDEBAR: boolean
|
||||
ZEN_MODE: boolean
|
||||
FONT_SIZE: HoppFontSize
|
||||
}
|
||||
|
||||
export const defaultSettings: SettingsType = {
|
||||
syncCollections: true,
|
||||
syncHistory: true,
|
||||
syncEnvironments: true,
|
||||
|
||||
PROXY_ENABLED: false,
|
||||
PROXY_URL: "https://proxy.hoppscotch.io/",
|
||||
PROXY_KEY: "",
|
||||
EXTENSIONS_ENABLED: true,
|
||||
EXPERIMENTAL_URL_BAR_ENABLED: true,
|
||||
URL_EXCLUDES: {
|
||||
auth: true,
|
||||
httpUser: true,
|
||||
httpPassword: true,
|
||||
bearerToken: true,
|
||||
oauth2Token: true,
|
||||
},
|
||||
THEME_COLOR: "blue",
|
||||
BG_COLOR: "system",
|
||||
TELEMETRY_ENABLED: true,
|
||||
LEFT_SIDEBAR: true,
|
||||
RIGHT_SIDEBAR: true,
|
||||
ZEN_MODE: false,
|
||||
FONT_SIZE: "small",
|
||||
}
|
||||
|
||||
const validKeys = Object.keys(defaultSettings)
|
||||
|
||||
const dispatchers = defineDispatchers({
|
||||
bulkApplySettings(
|
||||
_currentState: SettingsType,
|
||||
payload: Partial<SettingsType>
|
||||
) {
|
||||
return payload
|
||||
},
|
||||
toggleSetting(
|
||||
currentState: SettingsType,
|
||||
{ settingKey }: { settingKey: KeysMatching<SettingsType, boolean> }
|
||||
) {
|
||||
if (!has(currentState, settingKey)) {
|
||||
console.log(
|
||||
`Toggling of a non-existent setting key '${settingKey}' ignored.`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
const result: Partial<SettingsType> = {}
|
||||
result[settingKey] = !currentState[settingKey]
|
||||
|
||||
return result
|
||||
},
|
||||
applySetting<K extends keyof SettingsType>(
|
||||
_currentState: SettingsType,
|
||||
{ settingKey, value }: { settingKey: K; value: SettingsType[K] }
|
||||
) {
|
||||
if (!validKeys.includes(settingKey)) {
|
||||
console.log(
|
||||
`Ignoring non-existent setting key '${settingKey}' assignment`
|
||||
)
|
||||
return {}
|
||||
}
|
||||
|
||||
const result: Partial<SettingsType> = {}
|
||||
result[settingKey] = value
|
||||
|
||||
return result
|
||||
},
|
||||
})
|
||||
|
||||
export const settingsStore = new DispatchingStore(defaultSettings, dispatchers)
|
||||
|
||||
/**
|
||||
* An observable value to make avail all the state information at once
|
||||
*/
|
||||
export const settings$ = settingsStore.subject$.asObservable()
|
||||
|
||||
export function getSettingSubject<K extends keyof SettingsType>(
|
||||
settingKey: K
|
||||
): Observable<SettingsType[K]> {
|
||||
return settingsStore.subject$.pipe(pluck(settingKey), distinctUntilChanged())
|
||||
}
|
||||
|
||||
export function bulkApplySettings(settingsObj: Partial<SettingsType>) {
|
||||
settingsStore.dispatch({
|
||||
dispatcher: "bulkApplySettings",
|
||||
payload: settingsObj,
|
||||
})
|
||||
}
|
||||
|
||||
export function toggleSetting(settingKey: KeysMatching<SettingsType, boolean>) {
|
||||
settingsStore.dispatch({
|
||||
dispatcher: "toggleSetting",
|
||||
payload: {
|
||||
settingKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function applySetting<K extends keyof SettingsType>(
|
||||
settingKey: K,
|
||||
value: SettingsType[K]
|
||||
) {
|
||||
settingsStore.dispatch({
|
||||
dispatcher: "applySetting",
|
||||
payload: {
|
||||
settingKey,
|
||||
value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useSetting<K extends keyof SettingsType>(
|
||||
settingKey: K
|
||||
): Ref<SettingsType[K]> {
|
||||
return useStream(
|
||||
settingsStore.subject$.pipe(pluck(settingKey), distinctUntilChanged()),
|
||||
settingsStore.value[settingKey],
|
||||
(value: SettingsType[K]) => {
|
||||
settingsStore.dispatch({
|
||||
dispatcher: "applySetting",
|
||||
payload: {
|
||||
settingKey,
|
||||
value,
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user