2
0

MainHeader.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. <script setup lang="ts">
  2. import { defineAsyncComponent, ref, onMounted, watch, nextTick } from "vue";
  3. import Toast from "toasters";
  4. import { storeToRefs } from "pinia";
  5. import { useRoute } from "vue-router";
  6. import { useWebsocketsStore } from "@/stores/websockets";
  7. import { useConfigStore } from "@/stores/config";
  8. import { useUserAuthStore } from "@/stores/userAuth";
  9. import { useUserPreferencesStore } from "@/stores/userPreferences";
  10. import { useModalsStore } from "@/stores/modals";
  11. const ChristmasLights = defineAsyncComponent(
  12. () => import("@/components/ChristmasLights.vue")
  13. );
  14. defineProps({
  15. hideLogo: { type: Boolean, default: false },
  16. transparent: { type: Boolean, default: false },
  17. hideLoggedOut: { type: Boolean, default: false }
  18. });
  19. const route = useRoute();
  20. const userAuthStore = useUserAuthStore();
  21. const localNightmode = ref(false);
  22. const isMobile = ref(false);
  23. const windowWidth = ref(0);
  24. const broadcastChannel = ref();
  25. const { socket } = useWebsocketsStore();
  26. const configStore = useConfigStore();
  27. const {
  28. cookie,
  29. sitename,
  30. registrationDisabled,
  31. christmas,
  32. oidcAuthentication
  33. } = storeToRefs(configStore);
  34. const { loggedIn, username } = storeToRefs(userAuthStore);
  35. const { logout, hasPermission } = userAuthStore;
  36. const userPreferencesStore = useUserPreferencesStore();
  37. const { nightmode } = storeToRefs(userPreferencesStore);
  38. const { openModal } = useModalsStore();
  39. const toggleNightmode = toggle => {
  40. localNightmode.value =
  41. toggle === undefined ? !localNightmode.value : toggle;
  42. if (loggedIn.value) {
  43. socket.dispatch(
  44. "users.updatePreferences",
  45. { nightmode: localNightmode.value },
  46. res => {
  47. if (res.status !== "success") new Toast(res.message);
  48. }
  49. );
  50. } else {
  51. broadcastChannel.value.postMessage(localNightmode.value);
  52. }
  53. };
  54. const onResize = () => {
  55. windowWidth.value = window.innerWidth;
  56. };
  57. const oidcRedirect = () => {
  58. localStorage.setItem("oidc_redirect", route.path);
  59. };
  60. watch(nightmode, value => {
  61. localNightmode.value = value;
  62. });
  63. onMounted(async () => {
  64. localNightmode.value = nightmode.value;
  65. await nextTick();
  66. onResize();
  67. window.addEventListener("resize", onResize);
  68. broadcastChannel.value = new BroadcastChannel(`${cookie.value}.nightmode`);
  69. });
  70. </script>
  71. <template>
  72. <nav
  73. class="nav is-info"
  74. :class="{ transparent, 'hide-logged-out': !loggedIn && hideLoggedOut }"
  75. >
  76. <div class="nav-left">
  77. <router-link v-if="!hideLogo" class="nav-item is-brand" to="/">
  78. <img
  79. v-if="sitename === 'Musare'"
  80. src="/assets/white_wordmark.png"
  81. :alt="sitename"
  82. />
  83. <span v-else>{{ sitename }}</span>
  84. </router-link>
  85. </div>
  86. <span
  87. v-if="loggedIn || !hideLoggedOut"
  88. class="nav-toggle"
  89. :class="{ 'is-active': isMobile }"
  90. tabindex="0"
  91. @click="isMobile = !isMobile"
  92. @keyup.enter="isMobile = !isMobile"
  93. >
  94. <span />
  95. <span />
  96. <span />
  97. </span>
  98. <div class="nav-right nav-menu" :class="{ 'is-active': isMobile }">
  99. <div
  100. class="nav-item"
  101. id="nightmode-toggle"
  102. @click="toggleNightmode(!localNightmode)"
  103. >
  104. <span
  105. :class="{
  106. 'material-icons': true,
  107. 'night-mode-toggle': true,
  108. 'night-mode-on': localNightmode
  109. }"
  110. :content="`${
  111. localNightmode ? 'Disable' : 'Enable'
  112. } Nightmode`"
  113. v-tippy
  114. >
  115. {{ localNightmode ? "dark_mode" : "light_mode" }}
  116. </span>
  117. <span class="night-mode-label">Toggle Nightmode</span>
  118. </div>
  119. <span v-if="loggedIn" class="grouped">
  120. <router-link
  121. v-if="hasPermission('admin.view')"
  122. class="nav-item admin"
  123. to="/admin"
  124. >
  125. <strong>Admin</strong>
  126. </router-link>
  127. <router-link
  128. class="nav-item"
  129. :to="{
  130. name: 'profile',
  131. params: { username },
  132. query: { tab: 'playlists' }
  133. }"
  134. >
  135. Playlists
  136. </router-link>
  137. <router-link
  138. class="nav-item"
  139. :to="{
  140. name: 'profile',
  141. params: { username }
  142. }"
  143. >
  144. Profile
  145. </router-link>
  146. <router-link class="nav-item" to="/settings"
  147. >Settings</router-link
  148. >
  149. <a class="nav-item" @click="logout()">Logout</a>
  150. </span>
  151. <span v-if="!loggedIn && !hideLoggedOut" class="grouped">
  152. <a
  153. v-if="oidcAuthentication"
  154. class="nav-item"
  155. :href="configStore.urls.api + '/auth/oidc/authorize'"
  156. @click="oidcRedirect()"
  157. >
  158. Login
  159. </a>
  160. <a v-else class="nav-item" @click="openModal('login')">
  161. Login
  162. </a>
  163. <a
  164. v-if="!registrationDisabled"
  165. class="nav-item"
  166. @click="openModal('register')"
  167. >Register</a
  168. >
  169. </span>
  170. </div>
  171. <christmas-lights
  172. v-if="christmas"
  173. :lights="Math.min(Math.max(Math.floor(windowWidth / 175), 5), 15)"
  174. />
  175. </nav>
  176. </template>
  177. <style lang="less" scoped>
  178. .night-mode {
  179. .nav {
  180. background-color: var(--dark-grey-3) !important;
  181. }
  182. @media screen and (max-width: 768px) {
  183. .nav:not(.hide-logged-out) .nav-menu {
  184. background-color: var(--dark-grey-3) !important;
  185. }
  186. }
  187. .nav-item {
  188. color: var(--light-grey-2) !important;
  189. }
  190. }
  191. .nav {
  192. flex-shrink: 0;
  193. display: flex;
  194. position: relative;
  195. background-color: var(--primary-color);
  196. height: 64px;
  197. z-index: 100;
  198. &.transparent {
  199. background-color: transparent !important;
  200. }
  201. .nav-left,
  202. .nav-right {
  203. flex: 1;
  204. display: flex;
  205. }
  206. .nav-right {
  207. justify-content: flex-end;
  208. }
  209. a.nav-item.is-tab:hover {
  210. border-bottom: none;
  211. border-top: solid 1px var(--white);
  212. padding-top: 9px;
  213. }
  214. .nav-toggle {
  215. height: 64px;
  216. width: 50px;
  217. position: relative;
  218. background-color: transparent;
  219. display: none;
  220. position: relative;
  221. cursor: pointer;
  222. &.is-active {
  223. span:nth-child(1) {
  224. margin-left: -5px;
  225. transform: rotate(45deg);
  226. transform-origin: left top;
  227. }
  228. span:nth-child(2) {
  229. opacity: 0;
  230. }
  231. span:nth-child(3) {
  232. margin-left: -5px;
  233. transform: rotate(-45deg);
  234. transform-origin: left bottom;
  235. }
  236. }
  237. span {
  238. background-color: var(--white) !important;
  239. display: block;
  240. height: 1px;
  241. left: 50%;
  242. margin-left: -7px;
  243. position: absolute;
  244. top: 50%;
  245. width: 15px;
  246. transition: none 86ms ease-out;
  247. transition-property: opacity, transform;
  248. &:nth-child(1) {
  249. margin-top: -6px;
  250. }
  251. &:nth-child(2) {
  252. margin-top: -1px;
  253. }
  254. &:nth-child(3) {
  255. margin-top: 4px;
  256. }
  257. }
  258. }
  259. .nav-item {
  260. font-size: 17px;
  261. color: var(--white);
  262. border-top: 0;
  263. display: flex;
  264. align-items: center;
  265. padding: 10px;
  266. cursor: pointer;
  267. &:hover,
  268. &:focus {
  269. color: var(--white);
  270. }
  271. &.is-brand {
  272. font-size: 2.1rem !important;
  273. line-height: 38px !important;
  274. padding: 0 20px;
  275. font-family: Pacifico, cursive;
  276. display: flex;
  277. align-items: center;
  278. img {
  279. max-height: 38px;
  280. color: var(--primary-color);
  281. user-select: none;
  282. -webkit-user-drag: none;
  283. }
  284. }
  285. .night-mode-label {
  286. display: none;
  287. }
  288. }
  289. }
  290. .grouped {
  291. margin: 0;
  292. display: flex;
  293. text-decoration: none;
  294. .nav-item {
  295. &:hover,
  296. &:focus {
  297. border-top: 1px solid var(--white);
  298. height: calc(100% - 1px);
  299. }
  300. }
  301. }
  302. @media screen and (max-width: 768px) {
  303. .nav:not(.hide-logged-out) {
  304. .nav-toggle {
  305. display: block !important;
  306. }
  307. .nav-menu {
  308. display: none !important;
  309. box-shadow: @box-shadow-dropdown;
  310. left: 0;
  311. right: 0;
  312. top: 100%;
  313. position: absolute;
  314. background: var(--white);
  315. z-index: 100;
  316. }
  317. .nav-menu.is-active {
  318. display: flex !important;
  319. flex-direction: column-reverse;
  320. .nav-item {
  321. color: var(--dark-grey-2);
  322. &:hover {
  323. color: var(--dark-grey-2);
  324. }
  325. .night-mode-label {
  326. display: inline;
  327. margin-left: 5px;
  328. }
  329. }
  330. }
  331. .nav-menu {
  332. .grouped {
  333. flex-direction: column;
  334. }
  335. .nav-item {
  336. padding: 10px 20px;
  337. &:hover,
  338. &:focus {
  339. border-top: 0;
  340. height: unset;
  341. }
  342. }
  343. }
  344. }
  345. }
  346. </style>