Header.vue (hoppscotch-2.2.1) | : | Header.vue (hoppscotch-3.0.0) | ||
---|---|---|---|---|
<template> | <template> | |||
<div> | <div> | |||
<header | <header | |||
class="flex items-center justify-between flex-1 px-2 py-2 space-x-2" | class="flex items-center justify-between flex-1 px-2 py-2 overflow-x-auto overflow-y-hidden space-x-2" | |||
> | > | |||
<div class="inline-flex items-center space-x-2"> | <div class="inline-flex items-center space-x-2"> | |||
<ButtonSecondary | <ButtonSecondary | |||
class="tracking-wide !font-bold !text-secondaryDark hover:bg-primaryDa | class="tracking-wide !font-bold !text-secondaryDark hover:bg-primaryDa | |||
rk focus-visible:bg-primaryDark" | rk focus-visible:bg-primaryDark uppercase" | |||
label="HOPPSCOTCH" | :label="t('app.name')" | |||
to="/" | to="/" | |||
/> | /> | |||
<AppGitHubStarButton class="mt-1.5 transition <sm:hidden" /> | <AppGitHubStarButton class="mt-1.5 transition <sm:hidden" /> | |||
</div> | </div> | |||
<div class="inline-flex items-center space-x-2"> | <div class="inline-flex items-center space-x-2"> | |||
<ButtonSecondary | <ButtonSecondary | |||
id="installPWA" | v-if="showInstallButton" | |||
v-tippy="{ theme: 'tooltip' }" | v-tippy="{ theme: 'tooltip' }" | |||
:title="t('header.install_pwa')" | :title="t('header.install_pwa')" | |||
svg="download" | :icon="IconDownload" | |||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark" | class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark" | |||
@click.native="showInstallPrompt()" | @click="installPWA()" | |||
/> | /> | |||
<ButtonSecondary | <ButtonSecondary | |||
v-tippy="{ theme: 'tooltip' }" | v-tippy="{ theme: 'tooltip', allowHTML: true }" | |||
:title="`${t('app.search')} <kbd>/</kbd>`" | :title="`${t('app.search')} <xmp>/</xmp>`" | |||
svg="search" | :icon="IconSearch" | |||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark" | class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark" | |||
@click.native="invokeAction('modals.search.toggle')" | @click="invokeAction('modals.search.toggle')" | |||
/> | /> | |||
<ButtonSecondary | <ButtonSecondary | |||
v-tippy="{ theme: 'tooltip' }" | v-tippy="{ theme: 'tooltip', allowHTML: true }" | |||
:title="`${t('support.title')} <kbd>?</kbd>`" | :title="`${ | |||
svg="life-buoy" | mdAndLarger ? t('support.title') : t('app.options') | |||
} <xmp>?</xmp>`" | ||||
:icon="IconLifeBuoy" | ||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark" | class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark" | |||
@click.native="showSupport = true" | @click="invokeAction('modals.support.toggle')" | |||
/> | /> | |||
<ButtonSecondary | <ButtonSecondary | |||
v-if="currentUser === null" | v-if="currentUser === null" | |||
svg="upload-cloud" | :icon="IconUploadCloud" | |||
:label="t('header.save_workspace')" | :label="t('header.save_workspace')" | |||
filled | filled | |||
class="hidden md:flex" | class="hidden md:flex" | |||
@click.native="showLogin = true" | @click="showLogin = true" | |||
/> | /> | |||
<ButtonPrimary | <ButtonPrimary | |||
v-if="currentUser === null" | v-if="currentUser === null" | |||
:label="t('header.login')" | :label="t('header.login')" | |||
@click.native="showLogin = true" | @click="showLogin = true" | |||
/> | /> | |||
<div v-else class="inline-flex items-center space-x-2"> | <div v-else class="inline-flex items-center space-x-2"> | |||
<ButtonPrimary | <ButtonPrimary | |||
v-tippy="{ theme: 'tooltip' }" | v-tippy="{ theme: 'tooltip' }" | |||
:title="t('team.invite_tooltip')" | :title="t('team.invite_tooltip')" | |||
:label="t('team.invite')" | :label="t('team.invite')" | |||
svg="user-plus" | :icon="IconUserPlus" | |||
class="!bg-green-500 !bg-opacity-15 !text-green-500 !hover:bg-opacit y-10 !hover:bg-green-400 !hover:text-green-600" | class="!bg-green-500 !bg-opacity-15 !text-green-500 !hover:bg-opacit y-10 !hover:bg-green-400 !hover:text-green-600" | |||
@click.native="showTeamsModal = true" | @click="showTeamsModal = true" | |||
/> | /> | |||
<span class="px-2"> | <span class="px-2"> | |||
<tippy | <tippy | |||
ref="options" | ||||
interactive | interactive | |||
trigger="click" | trigger="click" | |||
theme="popover" | theme="popover" | |||
arrow | arrow | |||
:on-shown="() => tippyActions.focus()" | :on-shown="() => tippyActions.focus()" | |||
> | > | |||
<template #trigger> | <ProfilePicture | |||
<ProfilePicture | v-if="currentUser.photoURL" | |||
v-if="currentUser.photoURL" | v-tippy="{ | |||
v-tippy="{ | theme: 'tooltip', | |||
theme: 'tooltip', | }" | |||
}" | :url="currentUser.photoURL" | |||
:url="currentUser.photoURL" | :alt="currentUser.displayName" | |||
:alt="currentUser.displayName" | :title="currentUser.displayName" | |||
:title="currentUser.displayName" | indicator | |||
indicator | :indicator-styles=" | |||
:indicator-styles="isOnLine ? 'bg-green-500' : 'bg-red-500'" | network.isOnline ? 'bg-green-500' : 'bg-red-500' | |||
/> | " | |||
<ButtonSecondary | /> | |||
v-else | <ProfilePicture | |||
v-tippy="{ theme: 'tooltip' }" | v-else | |||
:title="t('header.account')" | v-tippy="{ theme: 'tooltip' }" | |||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDa | :title="currentUser.displayName" | |||
rk" | :initial="currentUser.displayName" | |||
svg="user" | indicator | |||
/> | :indicator-styles=" | |||
network.isOnline ? 'bg-green-500' : 'bg-red-500' | ||||
" | ||||
/> | ||||
<template #content="{ hide }"> | ||||
<div class="flex flex-col px-2 text-tiny"> | ||||
<span class="inline-flex font-semibold truncate"> | ||||
{{ currentUser.displayName }} | ||||
</span> | ||||
<span class="inline-flex truncate text-secondaryLight"> | ||||
{{ currentUser.email }} | ||||
</span> | ||||
</div> | ||||
<hr /> | ||||
<div | ||||
ref="tippyActions" | ||||
class="flex flex-col focus:outline-none" | ||||
tabindex="0" | ||||
role="menu" | ||||
@keyup.enter="profile.$el.click()" | ||||
@keyup.s="settings.$el.click()" | ||||
@keyup.l="logout.$el.click()" | ||||
@keyup.escape="hide()" | ||||
> | ||||
<SmartItem | ||||
ref="profile" | ||||
to="/profile" | ||||
:icon="IconUser" | ||||
:label="t('navigation.profile')" | ||||
:shortcut="['↩']" | ||||
@click="hide()" | ||||
/> | ||||
<SmartItem | ||||
ref="settings" | ||||
to="/settings" | ||||
:icon="IconSettings" | ||||
:label="t('navigation.settings')" | ||||
:shortcut="['S']" | ||||
@click="hide()" | ||||
/> | ||||
<FirebaseLogout | ||||
ref="logout" | ||||
:shortcut="['L']" | ||||
@confirm-logout="hide()" | ||||
/> | ||||
</div> | ||||
</template> | </template> | |||
<div class="flex flex-col px-2 text-tiny"> | ||||
<span class="inline-flex font-semibold truncate"> | ||||
{{ currentUser.displayName }} | ||||
</span> | ||||
<span class="inline-flex truncate text-secondaryLight"> | ||||
{{ currentUser.email }} | ||||
</span> | ||||
</div> | ||||
<hr /> | ||||
<div | ||||
ref="tippyActions" | ||||
class="flex flex-col focus:outline-none" | ||||
tabindex="0" | ||||
@keyup.enter="profile.$el.click()" | ||||
@keyup.s="settings.$el.click()" | ||||
@keyup.l="logout.$el.click()" | ||||
@keyup.escape="options.tippy().hide()" | ||||
> | ||||
<SmartItem | ||||
ref="profile" | ||||
to="/profile" | ||||
svg="user" | ||||
:label="t('navigation.profile')" | ||||
:shortcut="['↩']" | ||||
@click.native="options.tippy().hide()" | ||||
/> | ||||
<SmartItem | ||||
ref="settings" | ||||
to="/settings" | ||||
svg="settings" | ||||
:label="t('navigation.settings')" | ||||
:shortcut="['S']" | ||||
@click.native="options.tippy().hide()" | ||||
/> | ||||
<FirebaseLogout | ||||
ref="logout" | ||||
:shortcut="['L']" | ||||
@confirm-logout="options.tippy().hide()" | ||||
/> | ||||
</div> | ||||
</tippy> | </tippy> | |||
</span> | </span> | |||
</div> | </div> | |||
</div> | </div> | |||
</header> | </header> | |||
<AppAnnouncement v-if="!isOnLine" /> | <AppAnnouncement v-if="!network.isOnline" /> | |||
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" /> | <FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" /> | |||
<AppSupport :show="showSupport" @hide-modal="showSupport = false" /> | ||||
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" /> | <TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" /> | |||
</div> | </div> | |||
</template> | </template> | |||
<script setup lang="ts"> | <script setup lang="ts"> | |||
import { onMounted, ref } from "@nuxtjs/composition-api" | import { computed, onMounted, reactive, ref } from "vue" | |||
import intializePwa from "~/helpers/pwa" | import IconUser from "~icons/lucide/user" | |||
import { probableUser$ } from "~/helpers/fb/auth" | import IconSettings from "~icons/lucide/settings" | |||
import IconDownload from "~icons/lucide/download" | ||||
import IconSearch from "~icons/lucide/search" | ||||
import IconLifeBuoy from "~icons/lucide/life-buoy" | ||||
import IconUploadCloud from "~icons/lucide/upload-cloud" | ||||
import IconUserPlus from "~icons/lucide/user-plus" | ||||
import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core" | ||||
import { pwaDefferedPrompt, installPWA } from "@modules/pwa" | ||||
import { probableUser$ } from "@helpers/fb/auth" | ||||
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence" | import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence" | |||
import { | import { useI18n } from "@composables/i18n" | |||
useReadonlyStream, | import { useToast } from "@composables/toast" | |||
useI18n, | import { useReadonlyStream } from "@composables/stream" | |||
useToast, | import { invokeAction } from "@helpers/actions" | |||
} from "~/helpers/utils/composables" | ||||
import { defineActionHandler, invokeAction } from "~/helpers/actions" | ||||
const t = useI18n() | const t = useI18n() | |||
const toast = useToast() | const toast = useToast() | |||
/** | /** | |||
* Once the PWA code is initialized, this holds a method | * Once the PWA code is initialized, this holds a method | |||
* that can be called to show the user the installation | * that can be called to show the user the installation | |||
* prompt. | * prompt. | |||
*/ | */ | |||
const showInstallPrompt = ref(() => Promise.resolve()) // Async no-op till it is initialized | ||||
const showSupport = ref(false) | const showInstallButton = computed(() => !!pwaDefferedPrompt.value) | |||
const showLogin = ref(false) | const showLogin = ref(false) | |||
const showTeamsModal = ref(false) | const showTeamsModal = ref(false) | |||
const isOnLine = ref(navigator.onLine) | const breakpoints = useBreakpoints(breakpointsTailwind) | |||
const mdAndLarger = breakpoints.greater("md") | ||||
const currentUser = useReadonlyStream(probableUser$, null) | const network = reactive(useNetwork()) | |||
defineActionHandler("modals.support.toggle", () => { | const currentUser = useReadonlyStream(probableUser$, null) | |||
showSupport.value = !showSupport.value | ||||
}) | ||||
onMounted(() => { | onMounted(() => { | |||
window.addEventListener("online", () => { | ||||
isOnLine.value = true | ||||
}) | ||||
window.addEventListener("offline", () => { | ||||
isOnLine.value = false | ||||
}) | ||||
// Initializes the PWA code - checks if the app is installed, | ||||
// etc. | ||||
showInstallPrompt.value = intializePwa() | ||||
const cookiesAllowed = getLocalConfig("cookiesAllowed") === "yes" | const cookiesAllowed = getLocalConfig("cookiesAllowed") === "yes" | |||
if (!cookiesAllowed) { | if (!cookiesAllowed) { | |||
toast.show(`${t("app.we_use_cookies")}`, { | toast.show(`${t("app.we_use_cookies")}`, { | |||
duration: 0, | duration: 0, | |||
action: [ | action: [ | |||
{ | { | |||
text: `${t("action.learn_more")}`, | text: `${t("action.learn_more")}`, | |||
onClick: (_, toastObject) => { | onClick: (_, toastObject) => { | |||
setLocalConfig("cookiesAllowed", "yes") | setLocalConfig("cookiesAllowed", "yes") | |||
toastObject.goAway(0) | toastObject.goAway(0) | |||
skipping to change at line 217 | skipping to change at line 217 | |||
], | ], | |||
}) | }) | |||
} | } | |||
}) | }) | |||
// Template refs | // Template refs | |||
const tippyActions = ref<any | null>(null) | const tippyActions = ref<any | null>(null) | |||
const profile = ref<any | null>(null) | const profile = ref<any | null>(null) | |||
const settings = ref<any | null>(null) | const settings = ref<any | null>(null) | |||
const logout = ref<any | null>(null) | const logout = ref<any | null>(null) | |||
const options = ref<any | null>(null) | ||||
</script> | </script> | |||
End of changes. 28 change blocks. | ||||
111 lines changed or deleted | 109 lines changed or added |