Revamp of the Settings State System along with TypeScript support (#1560)

* Add vue-rx, rxjs and lodash as dependencies

* Added vue-rx plugin integration to nuxt config

* Initial settings store implementation

* Add babel plugin for private class properties to for Jest

* Add DispatchingStore test spec

* Initial settings code

* Reactive Streams for fb current user and id token

* Fix typo

* Migrate index and graphql pages to the new store

* Migrate network strategy to the new store

* Fixed Section.vue errors

* Fix getSettingSubject issue

* Migrate fb settings reference in components to the new state system

* Add typings for lodash as dev dependency

* Load setting

* Load initial sync setting values

* Update proxy url

* Add typescript support

* Rewrite Settings store to TypeScript

* Port Settings page to TypeScript as reference

* Move all store migrations to a separate file

* Delete test file for fb.js

* Add ts-jest as dev dependency

* Remove firebase-mock as dependency

* Remove FRAME_COLORS_ENABLED settings value
This commit is contained in:
Andrew Bastin
2021-03-23 11:18:14 -04:00
committed by GitHub
parent 64f64b9e31
commit 5fce1118f6
47 changed files with 32426 additions and 9143 deletions

View File

@@ -448,7 +448,7 @@ import * as gql from "graphql"
import { commonHeaders } from "~/helpers/headers"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { sendNetworkRequest } from "~/helpers/network"
import { fb } from "~/helpers/fb"
import { getSettingSubject } from "~/newstore/settings"
export default {
data() {
@@ -469,13 +469,11 @@ export default {
activeSidebar: true,
editRequest: {},
showSaveRequestModal: false,
settings: {
SCROLL_INTO_ENABLED:
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !== "undefined"
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
: true,
},
}
},
subscriptions() {
return {
SCROLL_INTO_ENABLED: getSettingSubject("SCROLL_INTO_ENABLED"),
}
},
watch: {
@@ -681,7 +679,7 @@ export default {
const rootTypeName = this.resolveRootType(type).name
const target = document.getElementById(`type_${rootTypeName}`)
if (target && this.settings.SCROLL_INTO_ENABLED) {
if (target && this.SCROLL_INTO_ENABLED) {
this.$refs.gqlTabs.$el
.querySelector(".gqlTabs")
.scrollTo({ top: target.offsetTop, behavior: "smooth" })
@@ -739,7 +737,7 @@ export default {
this.$nuxt.$loading.start()
this.response = this.$t("loading")
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("response")
if (this.SCROLL_INTO_ENABLED) this.scrollInto("response")
try {
let headers = {}
@@ -769,7 +767,7 @@ export default {
star: false,
headers: this.headers,
}
const res = await sendNetworkRequest(reqOptions, this.$store)
const res = await sendNetworkRequest(reqOptions)
// HACK: Temporary trailing null character issue from the extension fix
const responseText = new TextDecoder("utf-8").decode(res.data).replace(/\0+$/, "")
@@ -946,7 +944,7 @@ export default {
this.$nuxt.$loading.start()
this.schema = this.$t("loading")
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("schema")
if (this.SCROLL_INTO_ENABLED) this.scrollInto("schema")
try {
const query = JSON.stringify({

View File

@@ -38,7 +38,7 @@
<li>
<label for="url">{{ $t("url") }}</label>
<input
v-if="!this.$store.state.postwoman.settings.EXPERIMENTAL_URL_BAR_ENABLED"
v-if="!EXPERIMENTAL_URL_BAR_ENABLED"
:class="{ error: !isValidURL }"
class="border-dashed md:border-l border-brdColor"
@keyup.enter="isValidURL ? sendRequest() : null"
@@ -280,7 +280,7 @@
</li>
</ul>
<div class="row-wrapper">
<SmartToggle :on="!urlExcludes.auth" @change="setExclude('auth', !$event)">
<SmartToggle :on="!URL_EXCLUDES.auth" @change="setExclude('auth', !$event)">
{{ $t("include_in_url") }}
</SmartToggle>
</div>
@@ -665,6 +665,8 @@ import { parseUrlAndPath } from "~/helpers/utils/uri"
import { httpValid } from "~/helpers/utils/valid"
import { knownContentTypes, isJSONContentType } from "~/helpers/utils/contenttypes"
import { generateCodeWithGenerator } from "~/helpers/codegen/codegen"
import { getSettingSubject, applySetting } from "~/newstore/settings"
import clone from "lodash/clone"
export default {
data() {
@@ -693,7 +695,6 @@ export default {
showTokenRequestList: false,
showSaveRequestModal: false,
editRequest: {},
urlExcludes: {},
activeSidebar: true,
fb,
customMethod: false,
@@ -701,12 +702,6 @@ export default {
filenames: "",
navigatorShare: navigator.share,
runningRequest: false,
settings: {
SCROLL_INTO_ENABLED:
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !== "undefined"
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
: true,
},
currentMethodIndex: 0,
methodMenuItems: [
"GET",
@@ -722,16 +717,18 @@ export default {
],
}
},
subscriptions() {
return {
SCROLL_INTO_ENABLED: getSettingSubject("SCROLL_INTO_ENABLED"),
PROXY_ENABLED: getSettingSubject("PROXY_ENABLED"),
URL_EXCLUDES: getSettingSubject("URL_EXCLUDES"),
EXPERIMENTAL_URL_BAR_ENABLED: getSettingSubject("EXPERIMENTAL_URL_BAR_ENABLED"),
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
SYNC_HISTORY: getSettingSubject("syncHistory"),
}
},
watch: {
urlExcludes: {
deep: true,
handler() {
this.$store.commit("postwoman/applySetting", [
"URL_EXCLUDES",
Object.assign({}, this.urlExcludes),
])
},
},
canListParameters: {
immediate: true,
handler(canListParameters) {
@@ -1230,7 +1227,7 @@ export default {
this.requestType = entry.requestType
this.testScript = entry.testScript
this.testsEnabled = entry.usesPostScripts
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("request")
if (this.SCROLL_INTO_ENABLED) this.scrollInto("request")
},
async makeRequest(auth, headers, requestBody, preRequestScript) {
const requestOptions = {
@@ -1260,14 +1257,14 @@ export default {
if (typeof requestOptions.data === "string") {
requestOptions.data = parseTemplateString(requestOptions.data)
}
return await sendNetworkRequest(requestOptions, this.$store)
return await sendNetworkRequest(requestOptions)
},
cancelRequest() {
cancelRunningRequest(this.$store)
cancelRunningRequest()
},
async sendRequest() {
this.$toast.clear()
if (this.settings.SCROLL_INTO_ENABLED) this.scrollInto("response")
if (this.SCROLL_INTO_ENABLED) this.scrollInto("response")
if (!this.isValidURL) {
this.$toast.error(this.$t("url_invalid_format"), {
icon: "error",
@@ -1396,10 +1393,8 @@ export default {
}
this.$refs.historyComponent.addEntry(entry)
if (fb.currentUser !== null && fb.currentSettings[2]) {
if (fb.currentSettings[2].value) {
fb.writeHistory(entry)
}
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
fb.writeHistory(entry)
}
})()
} catch (error) {
@@ -1456,10 +1451,8 @@ export default {
}
this.$refs.historyComponent.addEntry(entry)
if (fb.currentUser !== null && fb.currentSettings[2]) {
if (fb.currentSettings[2].value) {
fb.writeHistory(entry)
}
if (fb.currentUser !== null && this.SYNC_HISTORY) {
fb.writeHistory(entry)
}
return
} else {
@@ -1468,7 +1461,7 @@ export default {
this.$toast.error(`${error} ${this.$t("f12_details")}`, {
icon: "error",
})
if (!this.$store.state.postwoman.settings.PROXY_ENABLED) {
if (!this.PROXY_ENABLED) {
this.$toast.info(this.$t("enable_proxy"), {
icon: "help",
duration: 8000,
@@ -1629,10 +1622,10 @@ export default {
"method",
"url",
"path",
!this.urlExcludes.auth ? "auth" : null,
!this.urlExcludes.httpUser ? "httpUser" : null,
!this.urlExcludes.httpPassword ? "httpPassword" : null,
!this.urlExcludes.bearerToken ? "bearerToken" : null,
!this.URL_EXCLUDES.auth ? "auth" : null,
!this.URL_EXCLUDES.httpUser ? "httpUser" : null,
!this.URL_EXCLUDES.httpPassword ? "httpPassword" : null,
!this.URL_EXCLUDES.bearerToken ? "bearerToken" : null,
"contentType",
]
.filter((item) => item !== null)
@@ -1818,14 +1811,19 @@ export default {
this.editRequest = {}
},
setExclude(excludedField, excluded) {
const update = clone(this.URL_EXCLUDES)
if (excludedField === "auth") {
this.urlExcludes.auth = excluded
this.urlExcludes.httpUser = excluded
this.urlExcludes.httpPassword = excluded
this.urlExcludes.bearerToken = excluded
update.auth = excluded
update.httpUser = excluded
update.httpPassword = excluded
update.bearerToken = excluded
} else {
this.urlExcludes[excludedField] = excluded
update[excludedField] = excluded
}
applySetting("URL_EXCLUDES", update)
this.setRouteQueryState()
},
updateRawBody(rawParams) {
@@ -1987,13 +1985,6 @@ export default {
await this.oauthRedirectReq()
},
created() {
this.urlExcludes = this.$store.state.postwoman.settings.URL_EXCLUDES || {
// Exclude authentication by default for security reasons.
auth: true,
httpUser: true,
httpPassword: true,
bearerToken: true,
}
if (Object.keys(this.$route.query).length) this.setRouteQueries(this.$route.query)
this.$watch(
(vm) => [

View File

@@ -24,16 +24,33 @@
</button>
<br />
<FirebaseLogout />
<p v-for="setting in fb.currentSettings" :key="setting.id">
<p>
<SmartToggle
:key="setting.name"
:on="setting.value"
@change="toggleSettings(setting.name, setting.value)"
:on="SYNC_COLLECTIONS"
@change="toggleSettings('syncCollections', !SYNC_COLLECTIONS)"
>
{{ $t(setting.name) + " " + $t("sync") }}
{{ setting.value ? $t("enabled") : $t("disabled") }}
{{ $t("syncCollections") + " " + $t("sync") }}
{{ SYNC_COLLECTIONS ? $t("enabled") : $t("disabled") }}
</SmartToggle>
</p>
<p>
<SmartToggle
:on="SYNC_ENVIRONMENTS"
@change="toggleSettings('syncEnvironments', !SYNC_ENVIRONMENTS)"
>
{{ $t("syncEnvironments") + " " + $t("sync") }}
{{ SYNC_ENVIRONMENTS ? $t("enabled") : $t("disabled") }}
</SmartToggle>
</p>
<p>
<SmartToggle :on="SYNC_HISTORY" @change="toggleSettings('syncHistory', !SYNC_HISTORY)">
{{ $t("syncHistory") + " " + $t("sync") }}
{{ SYNC_HISTORY ? $t("enabled") : $t("disabled") }}
</SmartToggle>
</p>
<p v-if="fb.currentSettings.length !== 3">
<button @click="initSettings">
<i class="material-icons">sync</i>
@@ -56,12 +73,9 @@
<SmartColorModePicker />
<SmartAccentModePicker />
<span>
<SmartToggle
:on="settings.SCROLL_INTO_ENABLED"
@change="toggleSetting('SCROLL_INTO_ENABLED')"
>
<SmartToggle :on="SCROLL_INTO_ENABLED" @change="toggleSetting('SCROLL_INTO_ENABLED')">
{{ $t("scrollInto_use_toggle") }}
{{ settings.SCROLL_INTO_ENABLED ? $t("enabled") : $t("disabled") }}
{{ SCROLL_INTO_ENABLED ? $t("enabled") : $t("disabled") }}
</SmartToggle>
</span>
</div>
@@ -71,10 +85,7 @@
<div class="flex flex-col">
<label>{{ $t("extensions") }}</label>
<div class="row-wrapper">
<SmartToggle
:on="settings.EXTENSIONS_ENABLED"
@change="toggleSetting('EXTENSIONS_ENABLED')"
>
<SmartToggle :on="EXTENSIONS_ENABLED" @change="toggleSetting('EXTENSIONS_ENABLED')">
{{ $t("extensions_use_toggle") }}
</SmartToggle>
</div>
@@ -92,9 +103,9 @@
<label>{{ $t("proxy") }}</label>
<div class="row-wrapper">
<span>
<SmartToggle :on="settings.PROXY_ENABLED" @change="toggleSetting('PROXY_ENABLED')">
<SmartToggle :on="PROXY_ENABLED" @change="toggleSetting('PROXY_ENABLED')">
{{ $t("proxy") }}
{{ settings.PROXY_ENABLED ? $t("enabled") : $t("disabled") }}
{{ PROXY_ENABLED ? $t("enabled") : $t("disabled") }}
</SmartToggle>
</span>
<a
@@ -116,8 +127,8 @@
<input
id="url"
type="url"
v-model="settings.PROXY_URL"
:disabled="!settings.PROXY_ENABLED"
v-model="PROXY_URL"
:disabled="!PROXY_ENABLED"
:placeholder="$t('url')"
/>
<p class="info">
@@ -166,7 +177,7 @@
</p>
<div class="row-wrapper">
<SmartToggle
:on="settings.EXPERIMENTAL_URL_BAR_ENABLED"
:on="EXPERIMENTAL_URL_BAR_ENABLED"
@change="toggleSetting('EXPERIMENTAL_URL_BAR_ENABLED')"
>
{{ $t("use_experimental_url_bar") }}
@@ -177,40 +188,54 @@
</div>
</template>
<script>
<script lang="ts">
import { fb } from "~/helpers/fb"
import { hasExtensionInstalled } from "../helpers/strategies/ExtensionStrategy"
import {
getSettingSubject,
applySetting,
toggleSetting,
defaultSettings,
} from "~/newstore/settings"
import type { KeysMatching } from "~/types/ts-utils"
export default {
import Vue from "vue"
type SettingsType = typeof defaultSettings
export default Vue.extend({
data() {
return {
extensionVersion: hasExtensionInstalled()
? window.__POSTWOMAN_EXTENSION_HOOK__.getVersion()
: null,
settings: {
SCROLL_INTO_ENABLED:
typeof this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED !== "undefined"
? this.$store.state.postwoman.settings.SCROLL_INTO_ENABLED
: true,
PROXY_ENABLED: this.$store.state.postwoman.settings.PROXY_ENABLED || false,
PROXY_URL: this.$store.state.postwoman.settings.PROXY_URL || "https://proxy.hoppscotch.io",
PROXY_KEY: this.$store.state.postwoman.settings.PROXY_KEY || "",
EXTENSIONS_ENABLED:
typeof this.$store.state.postwoman.settings.EXTENSIONS_ENABLED !== "undefined"
? this.$store.state.postwoman.settings.EXTENSIONS_ENABLED
: true,
EXPERIMENTAL_URL_BAR_ENABLED:
typeof this.$store.state.postwoman.settings.EXPERIMENTAL_URL_BAR_ENABLED !== "undefined"
? this.$store.state.postwoman.settings.EXPERIMENTAL_URL_BAR_ENABLED
: false,
},
doneButton: '<i class="material-icons">done</i>',
fb,
SYNC_COLLECTIONS: true,
SYNC_ENVIRONMENTS: true,
SYNC_HISTORY: true,
PROXY_URL: "",
PROXY_KEY: "",
}
},
subscriptions() {
return {
SCROLL_INTO_ENABLED: getSettingSubject("SCROLL_INTO_ENABLED"),
PROXY_ENABLED: getSettingSubject("PROXY_ENABLED"),
PROXY_URL: getSettingSubject("PROXY_URL"),
PROXY_KEY: getSettingSubject("PROXY_KEY"),
EXTENSIONS_ENABLED: getSettingSubject("EXTENSIONS_ENABLED"),
EXPERIMENTAL_URL_BAR_ENABLED: getSettingSubject("EXPERIMENTAL_URL_BAR_ENABLED"),
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
SYNC_HISTORY: getSettingSubject("syncHistory"),
}
},
watch: {
@@ -223,16 +248,15 @@ export default {
},
},
methods: {
applySetting(key, value) {
this.settings[key] = value
this.$store.commit("postwoman/applySetting", [key, value])
applySetting<K extends keyof SettingsType>(key: K, value: SettingsType[K]) {
applySetting(key, value)
},
toggleSetting(key) {
this.settings[key] = !this.settings[key]
this.$store.commit("postwoman/applySetting", [key, this.settings[key]])
toggleSetting<K extends KeysMatching<SettingsType, boolean>>(key: K) {
toggleSetting(key)
},
toggleSettings(name, value) {
fb.writeSettings(name, !value)
toggleSettings<K extends KeysMatching<SettingsType, boolean>>(name: K, value: SettingsType[K]) {
this.applySetting(name, value)
if (name === "syncCollections" && value) {
this.syncCollections()
}
@@ -241,45 +265,42 @@ export default {
}
},
initSettings() {
fb.writeSettings("syncHistory", true)
fb.writeSettings("syncCollections", true)
fb.writeSettings("syncEnvironments", true)
applySetting("syncHistory", true)
applySetting("syncCollections", true)
applySetting("syncEnvironments", true)
},
resetProxy({ target }) {
this.settings.PROXY_URL = `https://proxy.hoppscotch.io`
resetProxy({ target }: { target: HTMLElement }) {
applySetting("PROXY_URL", `https://proxy.hoppscotch.io/`)
target.innerHTML = this.doneButton
this.$toast.info(this.$t("cleared"), {
icon: "clear_all",
})
setTimeout(() => (target.innerHTML = '<i class="material-icons">clear_all</i>'), 1000)
},
syncCollections() {
if (fb.currentUser !== null && fb.currentSettings[0]) {
if (fb.currentSettings[0].value) {
fb.writeCollections(
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
"collections"
)
fb.writeCollections(
JSON.parse(JSON.stringify(this.$store.state.postwoman.collectionsGraphql)),
"collectionsGraphql"
)
}
syncCollections(): void {
if (fb.currentUser !== null && this.SYNC_COLLECTIONS) {
fb.writeCollections(
JSON.parse(JSON.stringify(this.$store.state.postwoman.collections)),
"collections"
)
fb.writeCollections(
JSON.parse(JSON.stringify(this.$store.state.postwoman.collectionsGraphql)),
"collectionsGraphql"
)
}
},
syncEnvironments() {
if (fb.currentUser !== null && fb.currentSettings[1]) {
if (fb.currentSettings[1].value) {
fb.writeEnvironments(JSON.parse(JSON.stringify(this.$store.state.postwoman.environments)))
}
syncEnvironments(): void {
if (fb.currentUser !== null && this.SYNC_ENVIRONMENTS) {
fb.writeEnvironments(JSON.parse(JSON.stringify(this.$store.state.postwoman.environments)))
}
},
},
computed: {
proxySettings() {
proxySettings(): { url: string; key: string } {
return {
url: this.settings.PROXY_URL,
key: this.settings.PROXY_KEY,
url: this.PROXY_URL,
key: this.PROXY_KEY,
}
},
},
@@ -288,5 +309,5 @@ export default {
title: `Settings • Hoppscotch`,
}
},
}
})
</script>