MainHeader.vue 7.2 KB

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