MainHeader.vue 7.5 KB

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