MainHeader.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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. }"
  130. >
  131. Profile
  132. </router-link>
  133. <router-link class="nav-item" to="/settings"
  134. >Settings</router-link
  135. >
  136. <a class="nav-item" @click="logout()">Logout</a>
  137. </span>
  138. <span v-if="!loggedIn && !hideLoggedOut" class="grouped">
  139. <a class="nav-item" @click="openModal('login')">Login</a>
  140. <a
  141. v-if="!siteSettings.registrationDisabled"
  142. class="nav-item"
  143. @click="openModal('register')"
  144. >Register</a
  145. >
  146. </span>
  147. </div>
  148. <christmas-lights
  149. v-if="siteSettings.christmas"
  150. :lights="Math.min(Math.max(Math.floor(windowWidth / 175), 5), 15)"
  151. />
  152. </nav>
  153. </template>
  154. <style lang="less" scoped>
  155. .night-mode {
  156. .nav {
  157. background-color: var(--dark-grey-3) !important;
  158. }
  159. @media screen and (max-width: 768px) {
  160. .nav:not(.hide-logged-out) .nav-menu {
  161. background-color: var(--dark-grey-3) !important;
  162. }
  163. }
  164. .nav-item {
  165. color: var(--light-grey-2) !important;
  166. }
  167. }
  168. .nav {
  169. flex-shrink: 0;
  170. display: flex;
  171. position: relative;
  172. background-color: var(--primary-color);
  173. height: 64px;
  174. z-index: 100;
  175. &.transparent {
  176. background-color: transparent !important;
  177. }
  178. .nav-left,
  179. .nav-right {
  180. flex: 1;
  181. display: flex;
  182. }
  183. .nav-right {
  184. justify-content: flex-end;
  185. }
  186. a.nav-item.is-tab:hover {
  187. border-bottom: none;
  188. border-top: solid 1px var(--white);
  189. padding-top: 9px;
  190. }
  191. .nav-toggle {
  192. height: 64px;
  193. width: 50px;
  194. position: relative;
  195. background-color: transparent;
  196. display: none;
  197. position: relative;
  198. cursor: pointer;
  199. &.is-active {
  200. span:nth-child(1) {
  201. margin-left: -5px;
  202. transform: rotate(45deg);
  203. transform-origin: left top;
  204. }
  205. span:nth-child(2) {
  206. opacity: 0;
  207. }
  208. span:nth-child(3) {
  209. margin-left: -5px;
  210. transform: rotate(-45deg);
  211. transform-origin: left bottom;
  212. }
  213. }
  214. span {
  215. background-color: var(--white) !important;
  216. display: block;
  217. height: 1px;
  218. left: 50%;
  219. margin-left: -7px;
  220. position: absolute;
  221. top: 50%;
  222. width: 15px;
  223. transition: none 86ms ease-out;
  224. transition-property: opacity, transform;
  225. &:nth-child(1) {
  226. margin-top: -6px;
  227. }
  228. &:nth-child(2) {
  229. margin-top: -1px;
  230. }
  231. &:nth-child(3) {
  232. margin-top: 4px;
  233. }
  234. }
  235. }
  236. .nav-item {
  237. font-size: 17px;
  238. color: var(--white);
  239. border-top: 0;
  240. display: flex;
  241. align-items: center;
  242. padding: 10px;
  243. cursor: pointer;
  244. &:hover,
  245. &:focus {
  246. color: var(--white);
  247. }
  248. &.is-brand {
  249. font-size: 2.1rem !important;
  250. line-height: 38px !important;
  251. padding: 0 20px;
  252. font-family: Pacifico, cursive;
  253. display: flex;
  254. align-items: center;
  255. img {
  256. max-height: 38px;
  257. color: var(--primary-color);
  258. user-select: none;
  259. -webkit-user-drag: none;
  260. }
  261. }
  262. .night-mode-label {
  263. display: none;
  264. }
  265. }
  266. }
  267. .grouped {
  268. margin: 0;
  269. display: flex;
  270. text-decoration: none;
  271. .nav-item {
  272. &:hover,
  273. &:focus {
  274. border-top: 1px solid var(--white);
  275. height: calc(100% - 1px);
  276. }
  277. }
  278. }
  279. @media screen and (max-width: 768px) {
  280. .nav:not(.hide-logged-out) {
  281. .nav-toggle {
  282. display: block !important;
  283. }
  284. .nav-menu {
  285. display: none !important;
  286. box-shadow: @box-shadow-dropdown;
  287. left: 0;
  288. right: 0;
  289. top: 100%;
  290. position: absolute;
  291. background: var(--white);
  292. z-index: 100;
  293. }
  294. .nav-menu.is-active {
  295. display: flex !important;
  296. flex-direction: column-reverse;
  297. .nav-item {
  298. color: var(--dark-grey-2);
  299. &:hover {
  300. color: var(--dark-grey-2);
  301. }
  302. .night-mode-label {
  303. display: inline;
  304. margin-left: 5px;
  305. }
  306. }
  307. }
  308. .nav-menu {
  309. .grouped {
  310. flex-direction: column;
  311. }
  312. .nav-item {
  313. padding: 10px 20px;
  314. &:hover,
  315. &:focus {
  316. border-top: 0;
  317. height: unset;
  318. }
  319. }
  320. }
  321. }
  322. }
  323. </style>