Files
hoppscotch/packages/hoppscotch-common/src/newstore/settings.ts
jamesgeorge007 40c8d7e4b7 feat: persist bulk mode line wrap setting for request variables
- Show line wrap toggle only if entries are present under http request headers & URL encoded params body.
- Organize imports.
2024-03-07 12:24:33 +05:30

296 lines
7.0 KiB
TypeScript

import { pluck, distinctUntilChanged } from "rxjs/operators"
import { cloneDeep, defaultsDeep, has } from "lodash-es"
import { Observable } from "rxjs"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
import type { KeysMatching } from "~/types/ts-utils"
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 type SettingsDef = {
syncCollections: boolean
syncHistory: boolean
syncEnvironments: boolean
PROXY_URL: string
WRAP_LINES: {
httpRequestBody: boolean
httpResponseBody: boolean
httpHeaders: boolean
httpParams: boolean
httpUrlEncoded: boolean
httpPreRequest: boolean
httpTest: boolean
httpRequestVariables: boolean
graphqlQuery: boolean
graphqlResponseBody: boolean
graphqlHeaders: boolean
graphqlVariables: boolean
graphqlSchema: boolean
importCurl: boolean
codeGen: boolean
cookie: boolean
}
CURRENT_INTERCEPTOR_ID: string
URL_EXCLUDES: {
auth: boolean
httpUser: boolean
httpPassword: boolean
bearerToken: boolean
oauth2Token: boolean
}
THEME_COLOR: HoppAccentColor
BG_COLOR: HoppBgColor
TELEMETRY_ENABLED: boolean
EXPAND_NAVIGATION: boolean
SIDEBAR: boolean
SIDEBAR_ON_LEFT: boolean
COLUMN_LAYOUT: boolean
}
export const getDefaultSettings = (): SettingsDef => ({
syncCollections: true,
syncHistory: true,
syncEnvironments: true,
WRAP_LINES: {
httpRequestBody: true,
httpResponseBody: true,
httpHeaders: true,
httpParams: true,
httpUrlEncoded: true,
httpPreRequest: true,
httpTest: true,
httpRequestVariables: true,
graphqlQuery: true,
graphqlResponseBody: true,
graphqlHeaders: false,
graphqlVariables: false,
graphqlSchema: true,
importCurl: true,
codeGen: true,
cookie: true,
},
CURRENT_INTERCEPTOR_ID: "browser", // TODO: Allow the platform definition to take this place
// TODO: Interceptor related settings should move under the interceptor systems
PROXY_URL: "https://proxy.hoppscotch.io/",
URL_EXCLUDES: {
auth: true,
httpUser: true,
httpPassword: true,
bearerToken: true,
oauth2Token: true,
},
THEME_COLOR: "indigo",
BG_COLOR: "system",
TELEMETRY_ENABLED: true,
EXPAND_NAVIGATION: false,
SIDEBAR: true,
SIDEBAR_ON_LEFT: false,
COLUMN_LAYOUT: true,
})
type ApplySettingPayload = {
[K in keyof SettingsDef]: {
settingKey: K
value: SettingsDef[K]
}
}[keyof SettingsDef]
type ApplyNestedSettingPayload = {
[K in KeysMatching<SettingsDef, Record<string, any>>]: {
[P in keyof SettingsDef[K]]: {
settingKey: K
property: P
value: SettingsDef[K][P]
}
}[keyof SettingsDef[K]]
}[KeysMatching<SettingsDef, Record<string, any>>]
const dispatchers = defineDispatchers({
bulkApplySettings(_currentState: SettingsDef, payload: Partial<SettingsDef>) {
return payload
},
toggleSetting(
currentState: SettingsDef,
{ settingKey }: { settingKey: KeysMatching<SettingsDef, boolean> }
) {
if (!has(currentState, settingKey)) {
// console.log(
// `Toggling of a non-existent setting key '${settingKey}' ignored`
// )
return {}
}
const result: Partial<SettingsDef> = {}
result[settingKey] = !currentState[settingKey]
return result
},
toggleNestedSetting(
currentState: SettingsDef,
{
settingKey,
property,
}: {
settingKey: KeysMatching<SettingsDef, Record<string, boolean>>
property: KeysMatching<SettingsDef[typeof settingKey], boolean>
}
) {
if (!has(currentState, [settingKey, property])) {
return {}
}
const result: Partial<SettingsDef> = {
[settingKey]: {
...currentState[settingKey],
[property]: !currentState[settingKey][property],
},
}
return result
},
applySetting(
_currentState: SettingsDef,
{ settingKey, value }: ApplySettingPayload
) {
const result: Partial<SettingsDef> = {
[settingKey]: value,
}
return result
},
applyNestedSetting(
_currentState: SettingsDef,
{ settingKey, property, value }: ApplyNestedSettingPayload
) {
const result: Partial<SettingsDef> = {
[settingKey]: {
[property]: value,
},
}
return result
},
})
export const settingsStore = new DispatchingStore(
getDefaultSettings(),
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 SettingsDef>(
settingKey: K
): Observable<SettingsDef[K]> {
return settingsStore.subject$.pipe(pluck(settingKey), distinctUntilChanged())
}
export function bulkApplySettings(settingsObj: Partial<SettingsDef>) {
settingsStore.dispatch({
dispatcher: "bulkApplySettings",
payload: settingsObj,
})
}
export function toggleSetting(settingKey: KeysMatching<SettingsDef, boolean>) {
settingsStore.dispatch({
dispatcher: "toggleSetting",
payload: {
settingKey,
},
})
}
export function toggleNestedSetting<
K extends KeysMatching<SettingsDef, Record<string, boolean>>,
P extends keyof SettingsDef[K],
>(settingKey: K, property: P) {
settingsStore.dispatch({
dispatcher: "toggleNestedSetting",
payload: {
settingKey,
// @ts-expect-error TS is not able to understand the type semantics here
property,
},
})
}
export function applySetting<K extends keyof SettingsDef>(
settingKey: K,
value: SettingsDef[K]
) {
settingsStore.dispatch({
dispatcher: "applySetting",
payload: {
// @ts-expect-error TS is not able to understand the type semantics here
settingKey,
// @ts-expect-error TS is not able to understand the type semantics here
value,
},
})
}
export function applyNestedSetting<
K extends KeysMatching<SettingsDef, Record<string, any>>,
P extends keyof SettingsDef[K],
R extends SettingsDef[K][P],
>(settingKey: K, property: P, value: R) {
settingsStore.dispatch({
dispatcher: "applyNestedSetting",
payload: {
settingKey,
// @ts-expect-error TS is not able to understand the type semantics here
property,
value,
},
})
}
export function performSettingsDataMigrations(data: any): SettingsDef {
const source = cloneDeep(data)
if (source["EXTENSIONS_ENABLED"]) {
const result = JSON.parse(source["EXTENSIONS_ENABLED"])
if (result) source["CURRENT_INTERCEPTOR_ID"] = "extension"
delete source["EXTENSIONS_ENABLED"]
}
if (source["PROXY_ENABLED"]) {
const result = JSON.parse(source["PROXY_ENABLED"])
if (result) source["CURRENT_INTERCEPTOR_ID"] = "proxy"
delete source["PROXY_ENABLED"]
}
const final = defaultsDeep(source, getDefaultSettings())
return final
}