default.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <template>
  2. <div class="flex w-screen h-screen">
  3. <Splitpanes class="no-splitter" :dbl-click-splitter="false" horizontal>
  4. <Pane v-if="!ZEN_MODE" style="height: auto">
  5. <AppHeader />
  6. </Pane>
  7. <Pane
  8. :class="spacerClass"
  9. class="flex flex-1 hide-scrollbar !overflow-auto md:mb-0"
  10. >
  11. <Splitpanes
  12. class="no-splitter"
  13. :dbl-click-splitter="false"
  14. :horizontal="!mdAndLarger"
  15. >
  16. <Pane
  17. style="width: auto; height: auto"
  18. class="hide-scrollbar !overflow-auto hidden md:flex md:flex-col"
  19. >
  20. <AppSidenav />
  21. </Pane>
  22. <Pane class="flex flex-1 hide-scrollbar !overflow-auto">
  23. <Splitpanes
  24. class="no-splitter"
  25. :dbl-click-splitter="false"
  26. horizontal
  27. >
  28. <Pane class="flex flex-1 hide-scrollbar !overflow-auto">
  29. <main class="flex flex-1 w-full" role="main">
  30. <nuxt class="flex flex-1" />
  31. </main>
  32. </Pane>
  33. </Splitpanes>
  34. </Pane>
  35. </Splitpanes>
  36. </Pane>
  37. <Pane v-if="mdAndLarger" style="height: auto">
  38. <AppFooter />
  39. </Pane>
  40. <Pane
  41. v-else
  42. style="height: auto"
  43. class="hide-scrollbar !overflow-auto flex flex-col fixed inset-x-0 bottom-0 z-10"
  44. >
  45. <AppSidenav />
  46. </Pane>
  47. </Splitpanes>
  48. <AppPowerSearch :show="showSearch" @hide-modal="showSearch = false" />
  49. <AppSupport
  50. v-if="mdAndLarger"
  51. :show="showSupport"
  52. @hide-modal="showSupport = false"
  53. />
  54. <AppOptions v-else :show="showSupport" @hide-modal="showSupport = false" />
  55. </div>
  56. </template>
  57. <script lang="ts">
  58. import {
  59. defineComponent,
  60. computed,
  61. onBeforeMount,
  62. useContext,
  63. useRouter,
  64. watch,
  65. ref,
  66. onMounted,
  67. onBeforeUnmount,
  68. } from "@nuxtjs/composition-api"
  69. import { Splitpanes, Pane } from "splitpanes"
  70. import "splitpanes/dist/splitpanes.css"
  71. import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
  72. import { setupLocalPersistence } from "~/newstore/localpersistence"
  73. import { performMigrations } from "~/helpers/migrations"
  74. import { initUserInfo } from "~/helpers/teams/BackendUserInfo"
  75. import { applySetting, useSetting } from "~/newstore/settings"
  76. import { logPageView } from "~/helpers/fb/analytics"
  77. import { hookKeybindingsListener } from "~/helpers/keybindings"
  78. import { defineActionHandler } from "~/helpers/actions"
  79. import { useSentry } from "~/helpers/sentry"
  80. import { useColorMode } from "~/helpers/utils/composables"
  81. import {
  82. changeExtensionStatus,
  83. ExtensionStatus,
  84. } from "~/newstore/HoppExtension"
  85. import { defineSubscribableObject } from "~/helpers/strategies/ExtensionStrategy"
  86. function appLayout() {
  87. const rightSidebar = useSetting("SIDEBAR")
  88. const columnLayout = useSetting("COLUMN_LAYOUT")
  89. const breakpoints = useBreakpoints(breakpointsTailwind)
  90. const mdAndLarger = breakpoints.greater("md")
  91. // Initially apply
  92. onBeforeMount(() => {
  93. if (!mdAndLarger.value) {
  94. rightSidebar.value = false
  95. columnLayout.value = true
  96. }
  97. })
  98. // Listen for updates
  99. watch(mdAndLarger, () => {
  100. if (mdAndLarger.value) rightSidebar.value = true
  101. else {
  102. rightSidebar.value = false
  103. columnLayout.value = true
  104. }
  105. })
  106. }
  107. function setupSentry() {
  108. const sentry = useSentry()
  109. const telemetryEnabled = useSetting("TELEMETRY_ENABLED")
  110. // Disable sentry error reporting if no telemetry allowed
  111. watch(
  112. telemetryEnabled,
  113. () => {
  114. const client = sentry.getCurrentHub()?.getClient()
  115. if (!client) return
  116. client.getOptions().enabled = telemetryEnabled.value
  117. },
  118. { immediate: true }
  119. )
  120. }
  121. function updateThemes() {
  122. const $colorMode = useColorMode()
  123. // Apply theme updates
  124. const themeColor = useSetting("THEME_COLOR")
  125. const bgColor = useSetting("BG_COLOR")
  126. const fontSize = useSetting("FONT_SIZE")
  127. const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
  128. const spacerClass = computed(() => {
  129. if (fontSize.value === "small" && EXPAND_NAVIGATION.value)
  130. return "spacer-small"
  131. if (fontSize.value === "medium" && EXPAND_NAVIGATION.value)
  132. return "spacer-medium"
  133. if (fontSize.value === "large" && EXPAND_NAVIGATION.value)
  134. return "spacer-large"
  135. if (
  136. (fontSize.value === "small" ||
  137. fontSize.value === "medium" ||
  138. fontSize.value === "large") &&
  139. !EXPAND_NAVIGATION.value
  140. )
  141. return "spacer-expand"
  142. })
  143. // Initially apply
  144. onBeforeMount(() => {
  145. document.documentElement.setAttribute("data-accent", themeColor.value)
  146. $colorMode.preference = bgColor.value
  147. document.documentElement.setAttribute("data-font-size", fontSize.value)
  148. })
  149. // Listen for updates
  150. watch(themeColor, () =>
  151. document.documentElement.setAttribute("data-accent", themeColor.value)
  152. )
  153. watch(bgColor, () => ($colorMode.preference = bgColor.value))
  154. watch(fontSize, () =>
  155. document.documentElement.setAttribute("data-font-size", fontSize.value)
  156. )
  157. return {
  158. spacerClass,
  159. }
  160. }
  161. function defineJumpActions() {
  162. const router = useRouter()
  163. const { localePath } = useContext() as any
  164. defineActionHandler("navigation.jump.rest", () => {
  165. router.push({ path: localePath("/") })
  166. })
  167. defineActionHandler("navigation.jump.graphql", () => {
  168. router.push({ path: localePath("/graphql") })
  169. })
  170. defineActionHandler("navigation.jump.realtime", () => {
  171. router.push({ path: localePath("/realtime") })
  172. })
  173. defineActionHandler("navigation.jump.documentation", () => {
  174. router.push({ path: localePath("/documentation") })
  175. })
  176. defineActionHandler("navigation.jump.settings", () => {
  177. router.push({ path: localePath("/settings") })
  178. })
  179. defineActionHandler("navigation.jump.profile", () => {
  180. router.push({ path: localePath("/profile") })
  181. })
  182. defineActionHandler("settings.theme.system", () => {
  183. applySetting("BG_COLOR", "system")
  184. })
  185. defineActionHandler("settings.theme.light", () => {
  186. applySetting("BG_COLOR", "light")
  187. })
  188. defineActionHandler("settings.theme.dark", () => {
  189. applySetting("BG_COLOR", "dark")
  190. })
  191. defineActionHandler("settings.theme.black", () => {
  192. applySetting("BG_COLOR", "black")
  193. })
  194. }
  195. function setupExtensionHooks() {
  196. const extensionPollIntervalId = ref<ReturnType<typeof setInterval>>()
  197. onMounted(() => {
  198. if (window.__HOPP_EXTENSION_STATUS_PROXY__) {
  199. changeExtensionStatus(window.__HOPP_EXTENSION_STATUS_PROXY__.status)
  200. window.__HOPP_EXTENSION_STATUS_PROXY__.subscribe(
  201. "status",
  202. (status: ExtensionStatus) => changeExtensionStatus(status)
  203. )
  204. } else {
  205. const statusProxy = defineSubscribableObject({
  206. status: "waiting" as ExtensionStatus,
  207. })
  208. window.__HOPP_EXTENSION_STATUS_PROXY__ = statusProxy
  209. statusProxy.subscribe("status", (status: ExtensionStatus) =>
  210. changeExtensionStatus(status)
  211. )
  212. /**
  213. * Keeping identifying extension backward compatible
  214. * We are assuming the default version is 0.24 or later. So if the extension exists, its identified immediately,
  215. * then we use a poll to find the version, this will get the version for 0.24 and any other version
  216. * of the extension, but will have a slight lag.
  217. * 0.24 users will get the benefits of 0.24, while the extension won't break for the old users
  218. */
  219. extensionPollIntervalId.value = setInterval(() => {
  220. if (typeof window.__POSTWOMAN_EXTENSION_HOOK__ !== "undefined") {
  221. if (extensionPollIntervalId.value)
  222. clearInterval(extensionPollIntervalId.value)
  223. const version = window.__POSTWOMAN_EXTENSION_HOOK__.getVersion()
  224. // When the version is not 0.24 or higher, the extension wont do this. so we have to do it manually
  225. if (
  226. version.major === 0 &&
  227. version.minor <= 23 &&
  228. window.__HOPP_EXTENSION_STATUS_PROXY__
  229. ) {
  230. window.__HOPP_EXTENSION_STATUS_PROXY__.status = "available"
  231. }
  232. }
  233. }, 2000)
  234. }
  235. })
  236. // Cleanup timer
  237. onBeforeUnmount(() => {
  238. if (extensionPollIntervalId.value) {
  239. clearInterval(extensionPollIntervalId.value)
  240. }
  241. })
  242. }
  243. export default defineComponent({
  244. components: { Splitpanes, Pane },
  245. setup() {
  246. appLayout()
  247. hookKeybindingsListener()
  248. defineJumpActions()
  249. const { spacerClass } = updateThemes()
  250. setupSentry()
  251. const breakpoints = useBreakpoints(breakpointsTailwind)
  252. const mdAndLarger = breakpoints.greater("md")
  253. const showSearch = ref(false)
  254. const showSupport = ref(false)
  255. defineActionHandler("modals.search.toggle", () => {
  256. showSearch.value = !showSearch.value
  257. })
  258. defineActionHandler("modals.support.toggle", () => {
  259. showSupport.value = !showSupport.value
  260. })
  261. setupExtensionHooks()
  262. return {
  263. mdAndLarger,
  264. spacerClass,
  265. ZEN_MODE: useSetting("ZEN_MODE"),
  266. showSearch,
  267. showSupport,
  268. }
  269. },
  270. head() {
  271. return this.$nuxtI18nHead({ addSeoAttributes: true })
  272. },
  273. watch: {
  274. $route(to) {
  275. logPageView(to.fullPath)
  276. },
  277. },
  278. beforeMount() {
  279. setupLocalPersistence()
  280. },
  281. async mounted() {
  282. performMigrations()
  283. console.info(
  284. "%cWe ❤︎ open source!",
  285. "background-color:white;padding:8px 16px;border-radius:8px;font-size:32px;color:red;"
  286. )
  287. console.info(
  288. "%cContribute: https://github.com/hoppscotch/hoppscotch",
  289. "background-color:black;padding:4px 8px;border-radius:8px;font-size:16px;color:white;"
  290. )
  291. const workbox = await (window as any).$workbox
  292. if (workbox) {
  293. workbox.addEventListener("installed", (event: any) => {
  294. if (event.isUpdate) {
  295. this.$toast.show(`${this.$t("app.new_version_found")}`, {
  296. duration: 0,
  297. action: [
  298. {
  299. text: `${this.$t("action.dismiss")}`,
  300. onClick: (_, toastObject) => {
  301. toastObject.goAway(0)
  302. },
  303. },
  304. {
  305. text: `${this.$t("app.reload")}`,
  306. onClick: (_, toastObject) => {
  307. toastObject.goAway(0)
  308. window.location.reload()
  309. },
  310. },
  311. ],
  312. })
  313. }
  314. })
  315. }
  316. initUserInfo()
  317. logPageView(this.$router.currentRoute.fullPath)
  318. },
  319. })
  320. </script>
  321. <style scoped>
  322. .spacer-small {
  323. margin-bottom: 4.2rem;
  324. }
  325. .spacer-medium {
  326. margin-bottom: 4.8rem;
  327. }
  328. .spacer-large {
  329. margin-bottom: 5.5rem;
  330. }
  331. .spacer-expand {
  332. margin-bottom: 2.9rem;
  333. }
  334. @media screen and (min-width: 768px) {
  335. .spacer-small {
  336. margin-bottom: 0;
  337. }
  338. .spacer-medium {
  339. margin-bottom: 0;
  340. }
  341. .spacer-large {
  342. margin-bottom: 0;
  343. }
  344. .spacer-expand {
  345. margin-bottom: 0;
  346. }
  347. }
  348. </style>