Header.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. <template>
  2. <div>
  3. <header
  4. class="flex items-center justify-between flex-1 px-2 py-2 space-x-2 overflow-x-auto"
  5. >
  6. <div class="inline-flex items-center space-x-2">
  7. <ButtonSecondary
  8. class="tracking-wide !font-bold !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark uppercase"
  9. :label="t('app.name')"
  10. to="/"
  11. />
  12. <AppGitHubStarButton class="mt-1.5 transition <sm:hidden" />
  13. </div>
  14. <div class="inline-flex items-center space-x-2">
  15. <ButtonSecondary
  16. id="installPWA"
  17. v-tippy="{ theme: 'tooltip' }"
  18. :title="t('header.install_pwa')"
  19. svg="download"
  20. class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
  21. @click.native="showInstallPrompt()"
  22. />
  23. <ButtonSecondary
  24. v-tippy="{ theme: 'tooltip', allowHTML: true }"
  25. :title="`${t('app.search')} <xmp>/</xmp>`"
  26. svg="search"
  27. class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
  28. @click.native="invokeAction('modals.search.toggle')"
  29. />
  30. <ButtonSecondary
  31. v-tippy="{ theme: 'tooltip', allowHTML: true }"
  32. :title="`${
  33. mdAndLarger ? t('support.title') : t('app.options')
  34. } <xmp>?</xmp>`"
  35. svg="life-buoy"
  36. class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
  37. @click.native="invokeAction('modals.support.toggle')"
  38. />
  39. <ButtonSecondary
  40. v-if="currentUser === null"
  41. svg="upload-cloud"
  42. :label="t('header.save_workspace')"
  43. filled
  44. class="hidden md:flex"
  45. @click.native="showLogin = true"
  46. />
  47. <ButtonPrimary
  48. v-if="currentUser === null"
  49. :label="t('header.login')"
  50. @click.native="showLogin = true"
  51. />
  52. <div v-else class="inline-flex items-center space-x-2">
  53. <ButtonPrimary
  54. v-tippy="{ theme: 'tooltip' }"
  55. :title="t('team.invite_tooltip')"
  56. :label="t('team.invite')"
  57. svg="user-plus"
  58. class="!bg-green-500 !bg-opacity-15 !text-green-500 !hover:bg-opacity-10 !hover:bg-green-400 !hover:text-green-600"
  59. @click.native="showTeamsModal = true"
  60. />
  61. <span class="px-2">
  62. <tippy
  63. ref="options"
  64. interactive
  65. trigger="click"
  66. theme="popover"
  67. arrow
  68. :on-shown="() => tippyActions.focus()"
  69. >
  70. <template #trigger>
  71. <ProfilePicture
  72. v-if="currentUser.photoURL"
  73. v-tippy="{
  74. theme: 'tooltip',
  75. }"
  76. :url="currentUser.photoURL"
  77. :alt="currentUser.displayName"
  78. :title="currentUser.displayName"
  79. indicator
  80. :indicator-styles="
  81. network.isOnline ? 'bg-green-500' : 'bg-red-500'
  82. "
  83. />
  84. <ProfilePicture
  85. v-else
  86. v-tippy="{ theme: 'tooltip' }"
  87. :title="currentUser.displayName"
  88. :initial="currentUser.displayName"
  89. indicator
  90. :indicator-styles="
  91. network.isOnline ? 'bg-green-500' : 'bg-red-500'
  92. "
  93. />
  94. </template>
  95. <div class="flex flex-col px-2 text-tiny" role="menu">
  96. <span class="inline-flex font-semibold truncate">
  97. {{ currentUser.displayName }}
  98. </span>
  99. <span class="inline-flex truncate text-secondaryLight">
  100. {{ currentUser.email }}
  101. </span>
  102. </div>
  103. <hr />
  104. <div
  105. ref="tippyActions"
  106. class="flex flex-col focus:outline-none"
  107. tabindex="0"
  108. @keyup.enter="profile.$el.click()"
  109. @keyup.s="settings.$el.click()"
  110. @keyup.l="logout.$el.click()"
  111. @keyup.escape="options.tippy().hide()"
  112. >
  113. <SmartItem
  114. ref="profile"
  115. to="/profile"
  116. svg="user"
  117. :label="t('navigation.profile')"
  118. :shortcut="['↩']"
  119. @click.native="options.tippy().hide()"
  120. />
  121. <SmartItem
  122. ref="settings"
  123. to="/settings"
  124. svg="settings"
  125. :label="t('navigation.settings')"
  126. :shortcut="['S']"
  127. @click.native="options.tippy().hide()"
  128. />
  129. <FirebaseLogout
  130. ref="logout"
  131. :shortcut="['L']"
  132. @confirm-logout="options.tippy().hide()"
  133. />
  134. </div>
  135. </tippy>
  136. </span>
  137. </div>
  138. </div>
  139. </header>
  140. <AppAnnouncement v-if="!network.isOnline" />
  141. <FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
  142. <TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
  143. </div>
  144. </template>
  145. <script setup lang="ts">
  146. import { onMounted, reactive, ref } from "@nuxtjs/composition-api"
  147. import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
  148. import initializePwa from "~/helpers/pwa"
  149. import { probableUser$ } from "~/helpers/fb/auth"
  150. import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
  151. import {
  152. useReadonlyStream,
  153. useI18n,
  154. useToast,
  155. } from "~/helpers/utils/composables"
  156. import { invokeAction } from "~/helpers/actions"
  157. const t = useI18n()
  158. const toast = useToast()
  159. /**
  160. * Once the PWA code is initialized, this holds a method
  161. * that can be called to show the user the installation
  162. * prompt.
  163. */
  164. const showInstallPrompt = ref(() => Promise.resolve()) // Async no-op till it is initialized
  165. const showLogin = ref(false)
  166. const showTeamsModal = ref(false)
  167. const breakpoints = useBreakpoints(breakpointsTailwind)
  168. const mdAndLarger = breakpoints.greater("md")
  169. const network = reactive(useNetwork())
  170. const currentUser = useReadonlyStream(probableUser$, null)
  171. onMounted(() => {
  172. // Initializes the PWA code - checks if the app is installed,
  173. // etc.
  174. showInstallPrompt.value = initializePwa()
  175. const cookiesAllowed = getLocalConfig("cookiesAllowed") === "yes"
  176. if (!cookiesAllowed) {
  177. toast.show(`${t("app.we_use_cookies")}`, {
  178. duration: 0,
  179. action: [
  180. {
  181. text: `${t("action.learn_more")}`,
  182. onClick: (_, toastObject) => {
  183. setLocalConfig("cookiesAllowed", "yes")
  184. toastObject.goAway(0)
  185. window.open("https://docs.hoppscotch.io/privacy", "_blank")?.focus()
  186. },
  187. },
  188. {
  189. text: `${t("action.dismiss")}`,
  190. onClick: (_, toastObject) => {
  191. setLocalConfig("cookiesAllowed", "yes")
  192. toastObject.goAway(0)
  193. },
  194. },
  195. ],
  196. })
  197. }
  198. })
  199. // Template refs
  200. const tippyActions = ref<any | null>(null)
  201. const profile = ref<any | null>(null)
  202. const settings = ref<any | null>(null)
  203. const logout = ref<any | null>(null)
  204. const options = ref<any | null>(null)
  205. </script>