refactor: monorepo+pnpm (removed husky)

This commit is contained in:
Andrew Bastin
2021-09-10 00:28:28 +05:30
parent 917550ff4d
commit b28f82a881
445 changed files with 81301 additions and 63752 deletions

View File

@@ -0,0 +1,6 @@
# PAGES
This directory contains your Application Views and Routes.
The framework reads all the `*.vue` files inside this directory and creates the router of your application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).

View File

@@ -0,0 +1,359 @@
<template>
<Splitpanes
class="smart-splitter"
:dbl-click-splitter="false"
:horizontal="!(windowInnerWidth.x.value >= 768)"
>
<Pane class="hide-scrollbar !overflow-auto">
<Splitpanes class="smart-splitter" :dbl-click-splitter="false" horizontal>
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="import">
<div class="flex p-4 items-start justify-between">
<label>
{{ $t("documentation.generate_message") }}
</label>
<span
class="
bg-accentDark
rounded
text-accentContrast
py-1
px-2
inline-flex
"
>
BETA
</span>
</div>
<div
class="
bg-primary
border-b border-dividerLight
flex
top-0
z-10
items-start
justify-between
sticky
"
>
<label for="collectionUpload">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
title="JSON"
svg="folder"
class="!rounded-none"
:label="$t('import.collections')"
@click.native="$refs.collectionUpload.click()"
/>
</label>
<input
ref="collectionUpload"
class="input"
name="collectionUpload"
type="file"
@change="uploadCollection"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('action.clear')"
svg="trash-2"
@click.native="collectionJSON = '[]'"
/>
</div>
<SmartAceEditor
v-model="collectionJSON"
:lang="'json'"
:lint="false"
:options="{
maxLines: Infinity,
minLines: 16,
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
/>
<div
class="
bg-primary
border-t border-b border-dividerLight
flex
p-4
bottom-0
z-10
justify-between
items-start
sticky
"
>
<ButtonPrimary
:label="$t('documentation.generate')"
@click.native="getDoc"
/>
</div>
</AppSection>
</Pane>
<Pane class="hide-scrollbar !overflow-auto">
<AppSection label="documentation">
<div class="flex flex-col">
<div
v-if="items.length === 0"
class="
flex flex-col
text-secondaryLight
p-4
items-center
justify-center
"
>
<i class="opacity-75 pb-2 material-icons">topic</i>
<span class="text-center">
{{ $t("helpers.generate_documentation_first") }}
</span>
</div>
<div
v-else
class="
bg-primary
border-b border-dividerLight
flex flex-1
p-4
top-0
z-10
sticky
"
>
<span
v-tippy="{ theme: 'tooltip' }"
:title="
!currentUser
? $t('export.require_github')
: currentUser.provider !== 'github.com'
? $t('export.require_github')
: 'Beta'
"
>
<ButtonPrimary
:disabled="
!currentUser
? true
: currentUser.provider !== 'github.com'
? true
: false
"
:label="$t('export.create_secret_gist')"
@click.native="createDocsGist"
/>
</span>
</div>
<div
v-for="(collection, index) in items"
:key="`collection-${index}`"
>
<DocsCollection :collection="collection" />
</div>
</div>
</AppSection>
</Pane>
</Splitpanes>
</Pane>
<Pane
v-if="RIGHT_SIDEBAR"
max-size="35"
size="25"
min-size="20"
class="hide-scrollbar !overflow-auto"
>
<aside>
<Collections
:selected="selected"
:doc="true"
@use-collection="useSelectedCollection($event)"
@remove-collection="removeSelectedCollection($event)"
/>
</aside>
</Pane>
</Splitpanes>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css"
import Mustache from "mustache"
import { currentUser$ } from "~/helpers/fb/auth"
import DocsTemplate from "~/assets/md/docs.md"
import folderContents from "~/assets/md/folderContents.md"
import folderBody from "~/assets/md/folderBody.md"
import { useSetting } from "~/newstore/settings"
import { useReadonlyStream } from "~/helpers/utils/composables"
import useWindowSize from "~/helpers/utils/useWindowSize"
export default defineComponent({
components: { Splitpanes, Pane },
setup() {
return {
windowInnerWidth: useWindowSize(),
RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
currentUser: useReadonlyStream(currentUser$, null),
}
},
data() {
return {
collectionJSON: "[]",
items: [],
docsMarkdown: "",
selected: [],
}
},
head() {
return {
title: `${this.$t("navigation.doc")} • Hoppscotch`,
}
},
methods: {
async createDocsGist() {
await this.$axios
.$post(
"https://api.github.com/gists",
{
files: {
"api-docs.md": {
content: this.docsMarkdown,
},
},
},
{
headers: {
Authorization: `token ${this.currentUser.accessToken}`,
Accept: "application/vnd.github.v3+json",
},
}
)
.then((res) => {
this.$toast.success(this.$t("export.gist_created"), {
icon: "done",
})
window.open(res.html_url)
})
.catch((e) => {
this.$toast.error(this.$t("error.something_went_wrong"), {
icon: "error_outline",
})
console.error(e)
})
},
uploadCollection() {
const file = this.$refs.collectionUpload.files[0]
if (file !== undefined && file !== null) {
const reader = new FileReader()
reader.onload = ({ target }) => {
this.collectionJSON = target.result
}
reader.readAsText(file)
this.$toast.success(this.$t("state.file_imported"), {
icon: "attach_file",
})
} else {
this.$toast.error(this.$t("action.choose_file"), {
icon: "attach_file",
})
}
this.$refs.collectionUpload.value = ""
},
assignIDs(items, pref, nestingLevel) {
for (let i = 0; i < items.length; ++i) {
items[i].id = `&emsp;${pref}${i + 1}.`
items[i].ref = `${items[i].name.split(" ").join("-")}`
items[i].nestingLevel = nestingLevel
items[i].folders = this.assignIDs(
items[i].folders,
items[i].id,
nestingLevel + "#"
)
for (let j = 0; j < items[i].requests.length; ++j) {
items[i].requests[j].id = `&emsp;${items[i].id}${i + 1}`
items[i].requests[j].ref = `${items[i].requests[j].name
.split(" ")
.join("-")}`
items[i].requests[j].nestingLevel = nestingLevel + "#"
}
}
return items
},
getDoc() {
try {
this.items = JSON.parse(this.collectionJSON)
this.assignIDs(this.items, "", "#")
this.$toast.clear()
this.$toast.success(this.$t("state.docs_generated"), {
icon: "book",
})
const docsMarkdown = Mustache.render(
DocsTemplate,
{
collections: this.items,
isHeaders() {
return this.headers.length
},
isParams() {
return this.params.length
},
isAuth() {
return this.auth !== "None"
},
isAuthBasic() {
return this.httpUser && this.httpPassword
},
isRawParams() {
return this.rawParams && this.rawParams !== ""
},
isPreRequestScript() {
return this.preRequestScript && this.preRequestScript !== ""
},
isTestScript() {
return this.testScript && this.testScript !== ""
},
},
{
folderContents,
folderBody,
}
)
this.docsMarkdown = docsMarkdown.replace(/^\s*[\r\n]/gm, "\n\n")
} catch (e) {
console.error(e)
this.$toast.error(this.$t("error.something_went_wrong"), {
icon: "error_outline",
})
}
},
useSelectedCollection(collection) {
if (this.selected.find((coll) => coll === collection)) {
return
}
this.selected.push(collection)
const importCollection = JSON.stringify(this.selected, null, 2)
this.collectionJSON = JSON.stringify(
JSON.parse(importCollection),
null,
2
)
},
removeSelectedCollection(collection) {
this.selected = this.selected.filter((coll) => coll !== collection)
const importCollection = JSON.stringify(this.selected, null, 2)
this.collectionJSON = JSON.stringify(
JSON.parse(importCollection),
null,
2
)
},
},
})
</script>

View File

@@ -0,0 +1,53 @@
<template>
<div class="flex flex-col min-h-screen items-center justify-center">
<SmartSpinner v-if="signingInWithEmail" />
<SmartLoadingIndicator v-else />
<pre v-if="error">{{ error }}</pre>
</div>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { initializeFirebase } from "~/helpers/fb"
import { isSignInWithEmailLink, signInWithEmailLink } from "~/helpers/fb/auth"
import { getLocalConfig, removeLocalConfig } from "~/newstore/localpersistence"
export default defineComponent({
layout: "empty",
data() {
return {
signingInWithEmail: false,
error: null,
}
},
beforeMount() {
initializeFirebase()
},
async mounted() {
if (isSignInWithEmailLink(window.location.href)) {
this.signingInWithEmail = true
let email = getLocalConfig("emailForSignIn")
if (!email) {
email = window.prompt(
"Please provide your email for confirmation"
) as string
}
await signInWithEmailLink(email, window.location.href)
.then(() => {
removeLocalConfig("emailForSignIn")
this.$router.push({ path: "/" })
})
.catch((e) => {
this.signingInWithEmail = false
this.error = e.message
})
.finally(() => {
this.signingInWithEmail = false
})
}
},
})
</script>

View File

@@ -0,0 +1,77 @@
<template>
<div>
<Splitpanes
class="smart-splitter"
:dbl-click-splitter="false"
:horizontal="!(windowInnerWidth.x.value >= 768)"
>
<Pane class="hide-scrollbar !overflow-auto">
<Splitpanes
class="smart-splitter"
:dbl-click-splitter="false"
horizontal
>
<Pane class="hide-scrollbar !overflow-auto">
<GraphqlRequest :conn="gqlConn" />
<GraphqlRequestOptions :conn="gqlConn" />
</Pane>
<Pane class="hide-scrollbar !overflow-auto">
<GraphqlResponse :conn="gqlConn" />
</Pane>
</Splitpanes>
</Pane>
<Pane
v-if="RIGHT_SIDEBAR"
max-size="35"
size="25"
min-size="20"
class="hide-scrollbar !overflow-auto"
>
<GraphqlSidebar :conn="gqlConn" />
</Pane>
</Splitpanes>
</div>
</template>
<script lang="ts">
import { defineComponent, watch } from "@nuxtjs/composition-api"
import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css"
import { useSetting } from "~/newstore/settings"
import { GQLConnection } from "~/helpers/GQLConnection"
import { useNuxt, useReadonlyStream } from "~/helpers/utils/composables"
import useWindowSize from "~/helpers/utils/useWindowSize"
export default defineComponent({
components: { Splitpanes, Pane },
beforeRouteLeave(_to, _from, next) {
if (this.gqlConn.connected$.value) {
this.gqlConn.disconnect()
}
next()
},
setup() {
const nuxt = useNuxt()
const gqlConn = new GQLConnection()
const isLoading = useReadonlyStream(gqlConn.isLoading$, false)
watch(isLoading, () => {
if (isLoading) nuxt.value.$loading.start()
else nuxt.value.$loading.finish()
})
return {
windowInnerWidth: useWindowSize(),
RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
gqlConn,
}
},
head() {
return {
title: `${this.$t("navigation.graphql")} • Hoppscotch`,
}
},
})
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div class="flex flex-col space-y-16">
<LandingHero />
<LandingUsers />
<LandingFeatures />
<LandingCTA />
<LandingFooter />
</div>
</template>

View File

@@ -0,0 +1,275 @@
<template>
<Splitpanes
class="smart-splitter"
:dbl-click-splitter="false"
:horizontal="!(windowInnerWidth.x.value >= 768)"
>
<Pane class="hide-scrollbar !overflow-auto">
<Splitpanes class="smart-splitter" :dbl-click-splitter="false" horizontal>
<Pane class="hide-scrollbar !overflow-auto">
<HttpRequest />
<SmartTabs styles="sticky bg-primary top-upperPrimaryStickyFold z-10">
<SmartTab
:id="'params'"
:label="$t('tab.parameters')"
:selected="true"
:info="newActiveParamsCount$"
>
<HttpParameters />
</SmartTab>
<SmartTab :id="'bodyParams'" :label="$t('tab.body')">
<HttpBody />
</SmartTab>
<SmartTab
:id="'headers'"
:label="$t('tab.headers')"
:info="newActiveHeadersCount$"
>
<HttpHeaders />
</SmartTab>
<SmartTab :id="'authorization'" :label="$t('tab.authorization')">
<HttpAuthorization />
</SmartTab>
<SmartTab
:id="'preRequestScript'"
:label="$t('tab.pre_request_script')"
>
<HttpPreRequestScript />
</SmartTab>
<SmartTab :id="'tests'" :label="$t('tab.tests')">
<HttpTests />
</SmartTab>
</SmartTabs>
</Pane>
<Pane class="hide-scrollbar !overflow-auto">
<HttpResponse ref="response" />
</Pane>
</Splitpanes>
</Pane>
<Pane
v-if="RIGHT_SIDEBAR"
max-size="35"
size="25"
min-size="20"
class="hide-scrollbar !overflow-auto"
>
<aside>
<SmartTabs styles="sticky bg-primary z-10 top-0">
<SmartTab :id="'history'" :label="$t('tab.history')" :selected="true">
<History ref="historyComponent" :page="'rest'" />
</SmartTab>
<SmartTab :id="'collections'" :label="$t('tab.collections')">
<Collections />
</SmartTab>
<SmartTab :id="'env'" :label="$t('environment.title')">
<Environments />
</SmartTab>
</SmartTabs>
</aside>
</Pane>
<SmartConfirmModal
:show="confirmSync"
:title="$t('confirm.sync')"
@hide-modal="confirmSync = false"
@resolve="syncRequest"
/>
</Splitpanes>
</template>
<script lang="ts">
import {
computed,
defineComponent,
onBeforeMount,
onBeforeUnmount,
onMounted,
Ref,
ref,
useContext,
watch,
} from "@nuxtjs/composition-api"
import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css"
import { map } from "rxjs/operators"
import { Subscription } from "rxjs"
import isEqual from "lodash/isEqual"
import { useSetting } from "~/newstore/settings"
import {
restRequest$,
restActiveParamsCount$,
restActiveHeadersCount$,
getRESTRequest,
setRESTRequest,
setRESTAuth,
restAuth$,
} from "~/newstore/RESTSession"
import { translateExtURLParams } from "~/helpers/RESTExtURLParams"
import {
pluckRef,
useReadonlyStream,
useStream,
} from "~/helpers/utils/composables"
import { loadRequestFromSync, startRequestSync } from "~/helpers/fb/request"
import { onLoggedIn } from "~/helpers/fb/auth"
import { HoppRESTRequest } from "~/helpers/types/HoppRESTRequest"
import { oauthRedirect } from "~/helpers/oauth"
import { HoppRESTAuthOAuth2 } from "~/helpers/types/HoppRESTAuth"
import useWindowSize from "~/helpers/utils/useWindowSize"
function bindRequestToURLParams() {
const {
route,
app: { router },
} = useContext()
const request = useStream(restRequest$, getRESTRequest(), setRESTRequest)
// Process headers and params to proper values
const headers = computed(() => {
const filtered = request.value.headers.filter((x) => x.key !== "")
return filtered.length > 0 ? JSON.stringify(filtered) : null
})
const params = computed(() => {
const filtered = request.value.params.filter((x) => x.key !== "")
return filtered.length > 0 ? JSON.stringify(filtered) : null
})
// Combine them together to a cleaner value
const urlParams = computed(() => ({
v: request.value.v,
method: request.value.method,
endpoint: request.value.endpoint,
headers: headers.value,
params: params.value,
}))
// Watch and update accordingly
watch(urlParams, () => {
history.replaceState(
window.location.href,
"",
`${router!.options.base}?${encodeURI(
Object.entries(urlParams.value)
.filter((x) => x[1] !== null)
.map((x) => `${x[0]}=${x[1]!}`)
.join("&")
)}`
)
})
// Now, we have to see the initial URL param and set that as the request
onMounted(() => {
const query = route.value.query
// If query params are empty, or contains code or error param (these are from Oauth Redirect)
// We skip URL params parsing
if (Object.keys(query).length === 0 || query.code || query.error) return
setRESTRequest(translateExtURLParams(query))
})
}
function oAuthURL() {
const auth = useStream(
restAuth$,
{ authType: "none", authActive: true },
setRESTAuth
)
const oauth2Token = pluckRef(auth as Ref<HoppRESTAuthOAuth2>, "token")
onBeforeMount(async () => {
const tokenInfo = await oauthRedirect()
if (Object.prototype.hasOwnProperty.call(tokenInfo, "access_token")) {
if (typeof tokenInfo === "object") {
oauth2Token.value = tokenInfo.access_token
}
}
})
}
function setupRequestSync(
confirmSync: Ref<boolean>,
requestForSync: Ref<HoppRESTRequest | null>
) {
const { route } = useContext()
// Subscription to request sync
let sub: Subscription | null = null
// Load request on login resolve and start sync
onLoggedIn(async () => {
if (
Object.keys(route.value.query).length === 0 &&
!(route.value.query.code || route.value.query.error)
) {
const request = await loadRequestFromSync()
if (request) {
// setRESTRequest(request)
if (!isEqual(request, getRESTRequest())) {
requestForSync.value = request
confirmSync.value = true
}
}
}
sub = startRequestSync()
})
// Stop subscripton to stop syncing
onBeforeUnmount(() => {
sub?.unsubscribe()
})
}
export default defineComponent({
components: { Splitpanes, Pane },
setup() {
const requestForSync = ref<HoppRESTRequest | null>(null)
const confirmSync = ref(false)
const syncRequest = () => {
setRESTRequest(requestForSync.value!)
}
setupRequestSync(confirmSync, requestForSync)
bindRequestToURLParams()
return {
windowInnerWidth: useWindowSize(),
newActiveParamsCount$: useReadonlyStream(
restActiveParamsCount$.pipe(
map((e) => {
if (e === 0) return null
return e.toString()
})
),
null
),
newActiveHeadersCount$: useReadonlyStream(
restActiveHeadersCount$.pipe(
map((e) => {
if (e === 0) return null
return e.toString()
})
),
null
),
RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
confirmSync,
syncRequest,
oAuthURL,
requestForSync,
}
},
})
</script>

View File

@@ -0,0 +1,48 @@
<template>
<SmartTabs
class="h-full overflow-hidden"
styles="sticky bg-primary top-0 z-10 border-b border-dividerLight !overflow-visible"
>
<SmartTab
id="websocket"
:label="$t('tab.websocket')"
:selected="true"
style="height: calc(100% - var(--sidebar-primary-sticky-fold))"
>
<RealtimeWebsocket />
</SmartTab>
<SmartTab
id="sse"
:label="$t('tab.sse')"
style="height: calc(100% - var(--sidebar-primary-sticky-fold))"
>
<RealtimeSse />
</SmartTab>
<SmartTab
id="socketio"
:label="$t('tab.socketio')"
style="height: calc(100% - var(--sidebar-primary-sticky-fold))"
>
<RealtimeSocketio />
</SmartTab>
<SmartTab
id="mqtt"
:label="$t('tab.mqtt')"
style="height: calc(100% - var(--sidebar-primary-sticky-fold))"
>
<RealtimeMqtt />
</SmartTab>
</SmartTabs>
</template>
<script>
import { defineComponent } from "@nuxtjs/composition-api"
export default defineComponent({
head() {
return {
title: `${this.$t("navigation.realtime")} • Hoppscotch`,
}
},
})
</script>

View File

@@ -0,0 +1,475 @@
<template>
<div>
<div class="divide-y divide-dividerLight space-y-8">
<div class="md:grid md:gap-4 md:grid-cols-3">
<div class="p-8 md:col-span-1">
<h3 class="heading">
{{ $t("settings.account") }}
</h3>
<p class="mt-1 text-secondaryLight">
{{ $t("settings.account_description") }}
</p>
</div>
<div class="p-8 md:col-span-2">
<div v-if="currentUser === null">
<ButtonPrimary
:label="$t('auth.login')"
@click.native="showLogin = true"
/>
</div>
<div v-else class="space-y-8">
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.user") }}
</h4>
<div class="space-y-4 py-4">
<div class="flex items-start">
<div class="flex items-center">
<img
v-if="currentUser.photoURL"
:src="currentUser.photoURL"
class="rounded-full h-5 w-5"
/>
<SmartIcon v-else name="user" class="svg-icons" />
</div>
<div class="ml-4">
<label>
{{ currentUser.displayName || $t("state.nothing_found") }}
</label>
<p class="mt-1 text-secondaryLight">
{{ $t("settings.account_name_description") }}
</p>
</div>
</div>
<div class="flex items-start">
<div class="flex items-center">
<SmartIcon name="at-sign" class="svg-icons" />
</div>
<div class="ml-4">
<label>
{{ currentUser.email || $t("state.nothing_found") }}
</label>
<p class="mt-1 text-secondaryLight">
{{ $t("settings.account_email_description") }}
</p>
</div>
</div>
</div>
</section>
<Teams v-if="currentBackendUser && currentBackendUser.eaInvited" />
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.sync") }}
</h4>
<div class="mt-1 text-secondaryLight">
{{ $t("settings.sync_description") }}
</div>
<div class="space-y-4 py-4">
<div class="flex items-center">
<SmartToggle
:on="SYNC_COLLECTIONS"
@change="
toggleSettings('syncCollections', !SYNC_COLLECTIONS)
"
>
{{ $t("settings.sync_collections") }}
</SmartToggle>
</div>
<div class="flex items-center">
<SmartToggle
:on="SYNC_ENVIRONMENTS"
@change="
toggleSettings('syncEnvironments', !SYNC_ENVIRONMENTS)
"
>
{{ $t("settings.sync_environments") }}
</SmartToggle>
</div>
<div class="flex items-center">
<SmartToggle
:on="SYNC_HISTORY"
@change="toggleSettings('syncHistory', !SYNC_HISTORY)"
>
{{ $t("settings.sync_history") }}
</SmartToggle>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="md:grid md:gap-4 md:grid-cols-3">
<div class="p-8 md:col-span-1">
<h3 class="heading">
{{ $t("settings.theme") }}
</h3>
<p class="mt-1 text-secondaryLight">
{{ $t("settings.theme_description") }}
</p>
</div>
<div class="space-y-8 p-8 md:col-span-2">
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.background") }}
</h4>
<div class="mt-1 text-secondaryLight">
<ColorScheme placeholder="..." tag="span">
{{ $t(getColorModeName($colorMode.preference)) }}
<span v-if="$colorMode.preference === 'system'">
({{ $t(getColorModeName($colorMode.value)) }})
</span>
</ColorScheme>
</div>
<div class="mt-4">
<SmartColorModePicker />
</div>
</section>
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.accent_color") }}
</h4>
<div class="mt-1 text-secondaryLight">
{{ active.charAt(0).toUpperCase() + active.slice(1) }}
</div>
<div class="mt-4">
<SmartAccentModePicker />
</div>
</section>
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.font_size") }}
</h4>
<div class="mt-4">
<SmartFontSizePicker />
</div>
</section>
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.language") }}
</h4>
<div class="mt-4">
<SmartChangeLanguage />
</div>
</section>
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.experiments") }}
</h4>
<div class="mt-1 text-secondaryLight">
{{ $t("settings.experiments_notice") }}
<SmartLink
class="link"
to="https://github.com/hoppscotch/hoppscotch/issues/new/choose"
blank
>
{{ $t("app.contact_us") }} </SmartLink
>.
</div>
<div class="space-y-4 py-4">
<div class="flex items-center">
<SmartToggle
:on="EXPERIMENTAL_URL_BAR_ENABLED"
@change="toggleSetting('EXPERIMENTAL_URL_BAR_ENABLED')"
>
{{ $t("settings.use_experimental_url_bar") }}
</SmartToggle>
</div>
<div class="flex items-center">
<SmartToggle :on="TELEMETRY_ENABLED" @change="showConfirmModal">
{{ $t("settings.telemetry") }}
{{
TELEMETRY_ENABLED
? $t("state.enabled")
: $t("state.disabled")
}}
</SmartToggle>
</div>
<!-- <div class="flex items-center">
<SmartToggle
:on="LEFT_SIDEBAR"
@change="toggleSetting('LEFT_SIDEBAR')"
>
{{ $t("settings.navigation_sidebar") }}
{{
LEFT_SIDEBAR ? $t("state.enabled") : $t("state.disabled")
}}
</SmartToggle>
</div> -->
<div class="flex items-center">
<SmartToggle :on="ZEN_MODE" @change="toggleSetting('ZEN_MODE')">
{{ $t("layout.zen_mode") }}
{{ ZEN_MODE ? $t("state.enabled") : $t("state.disabled") }}
</SmartToggle>
</div>
</div>
</section>
</div>
</div>
<div class="md:grid md:gap-4 md:grid-cols-3">
<div class="p-8 md:col-span-1">
<h3 class="heading">
{{ $t("settings.interceptor") }}
</h3>
<p class="mt-1 text-secondaryLight">
{{ $t("settings.interceptor_description") }}
</p>
</div>
<div class="space-y-8 p-8 md:col-span-2">
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.extensions") }}
</h4>
<div class="mt-1 text-secondaryLight">
<span v-if="extensionVersion != null">
{{
`${$t("settings.extension_version")}: v${
extensionVersion.major
}.${extensionVersion.minor}`
}}
</span>
<span v-else>
{{ $t("settings.extension_version") }}:
{{ $t("settings.extension_ver_not_reported") }}
</span>
</div>
<div class="flex flex-col space-y-2 py-4">
<span>
<SmartItem
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
blank
svg="brands/firefox"
label="Firefox"
:info-icon="hasFirefoxExtInstalled ? 'check_circle' : ''"
:active-info-icon="hasFirefoxExtInstalled"
outline
/>
</span>
<span>
<SmartItem
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
blank
svg="brands/chrome"
label="Chrome"
:info-icon="hasChromeExtInstalled ? 'check_circle' : ''"
:active-info-icon="hasChromeExtInstalled"
outline
/>
</span>
</div>
<div class="space-y-4 py-4">
<div class="flex items-center">
<SmartToggle
:on="EXTENSIONS_ENABLED"
@change="toggleSetting('EXTENSIONS_ENABLED')"
>
{{ $t("settings.extensions_use_toggle") }}
</SmartToggle>
</div>
</div>
</section>
<section>
<h4 class="font-semibold text-secondaryDark">
{{ $t("settings.proxy") }}
</h4>
<div class="mt-1 text-secondaryLight">
{{
`${$t("settings.official_proxy_hosting")} ${$t(
"settings.read_the"
)}`
}}
<SmartLink
class="link"
to="https://docs.hoppscotch.io/privacy"
blank
>
{{ $t("app.proxy_privacy_policy") }} </SmartLink
>.
</div>
<div class="space-y-4 py-4">
<div class="flex items-center">
<SmartToggle
:on="PROXY_ENABLED"
@change="toggleSetting('PROXY_ENABLED')"
>
{{ $t("settings.proxy_use_toggle") }}
</SmartToggle>
</div>
</div>
<div class="flex space-x-2 py-4 items-center">
<div class="flex flex-1 items-center relative">
<input
id="url"
v-model="PROXY_URL"
class="input floating-input"
placeholder=" "
type="url"
autocomplete="off"
:disabled="!PROXY_ENABLED"
/>
<label for="url">
{{ $t("settings.proxy_url") }}
</label>
</div>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('settings.reset_default')"
:svg="clearIcon"
outline
class="rounded"
@click.native="resetProxy"
/>
</div>
</section>
</div>
</div>
</div>
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
<SmartConfirmModal
:show="confirmRemove"
:title="`${$t('confirm.remove_telemetry')} ${$t(
'settings.telemetry_helps_us'
)}`"
@hide-modal="confirmRemove = false"
@resolve="
toggleSetting('TELEMETRY_ENABLED')
confirmRemove = false
"
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { currentUserInfo$ } from "~/helpers/teams/BackendUserInfo"
import {
hasExtensionInstalled,
hasChromeExtensionInstalled,
hasFirefoxExtensionInstalled,
} from "~/helpers/strategies/ExtensionStrategy"
import {
applySetting,
toggleSetting,
defaultSettings,
useSetting,
} from "~/newstore/settings"
import type { KeysMatching } from "~/types/ts-utils"
import { currentUser$ } from "~/helpers/fb/auth"
import { getLocalConfig } from "~/newstore/localpersistence"
import { useReadonlyStream } from "~/helpers/utils/composables"
type SettingsType = typeof defaultSettings
export default defineComponent({
setup() {
return {
PROXY_ENABLED: useSetting("PROXY_ENABLED"),
PROXY_URL: useSetting("PROXY_URL"),
PROXY_KEY: useSetting("PROXY_KEY"),
EXTENSIONS_ENABLED: useSetting("EXTENSIONS_ENABLED"),
EXPERIMENTAL_URL_BAR_ENABLED: useSetting("EXPERIMENTAL_URL_BAR_ENABLED"),
SYNC_COLLECTIONS: useSetting("syncCollections"),
SYNC_ENVIRONMENTS: useSetting("syncEnvironments"),
SYNC_HISTORY: useSetting("syncHistory"),
TELEMETRY_ENABLED: useSetting("TELEMETRY_ENABLED"),
LEFT_SIDEBAR: useSetting("LEFT_SIDEBAR"),
ZEN_MODE: useSetting("ZEN_MODE"),
currentUser: useReadonlyStream(currentUser$, currentUser$.value),
currentBackendUser: useReadonlyStream(
currentUserInfo$,
currentUserInfo$.value
),
}
},
data() {
return {
extensionVersion: hasExtensionInstalled()
? window.__POSTWOMAN_EXTENSION_HOOK__.getVersion()
: null,
hasChromeExtInstalled: hasChromeExtensionInstalled(),
hasFirefoxExtInstalled: hasFirefoxExtensionInstalled(),
clearIcon: "rotate-ccw",
showLogin: false,
active: getLocalConfig("THEME_COLOR") || "blue",
confirmRemove: false,
}
},
head() {
return {
title: `${this.$t("navigation.settings")} • Hoppscotch`,
}
},
computed: {
proxySettings(): { url: string; key: string } {
return {
url: this.PROXY_URL,
key: this.PROXY_KEY,
}
},
},
watch: {
ZEN_MODE(ZEN_MODE) {
this.applySetting("LEFT_SIDEBAR", !ZEN_MODE)
// this.applySetting("RIGHT_SIDEBAR", !ZEN_MODE)
},
proxySettings: {
deep: true,
handler({ url, key }) {
this.applySetting("PROXY_URL", url)
this.applySetting("PROXY_KEY", key)
},
},
},
methods: {
showConfirmModal() {
if (this.TELEMETRY_ENABLED) this.confirmRemove = true
else toggleSetting("TELEMETRY_ENABLED")
},
applySetting<K extends keyof SettingsType>(key: K, value: SettingsType[K]) {
applySetting(key, value)
},
toggleSetting<K extends KeysMatching<SettingsType, boolean>>(key: K) {
if (key === "EXTENSIONS_ENABLED" && this.PROXY_ENABLED) {
toggleSetting("PROXY_ENABLED")
}
if (key === "PROXY_ENABLED" && this.EXTENSIONS_ENABLED) {
toggleSetting("EXTENSIONS_ENABLED")
}
toggleSetting(key)
},
toggleSettings<K extends KeysMatching<SettingsType, boolean>>(
name: K,
value: SettingsType[K]
) {
this.applySetting(name, value)
},
resetProxy() {
applySetting("PROXY_URL", `https://proxy.hoppscotch.io/`)
this.clearIcon = "check"
this.$toast.success(this.$t("state.cleared").toString(), {
icon: "clear_all",
})
setTimeout(() => (this.clearIcon = "rotate-ccw"), 1000)
},
getColorModeName(colorMode: string) {
switch (colorMode) {
case "system":
return "settings.system_mode"
case "light":
return "settings.light_mode"
case "dark":
return "settings.dark_mode"
case "black":
return "settings.black_mode"
default:
return "settings.system_mode"
}
},
},
})
</script>