Home.vue 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  1. <template>
  2. <div>
  3. <metadata title="Home" />
  4. <div class="app">
  5. <main-header
  6. :hide-logo="true"
  7. :transparent="true"
  8. :hide-logged-out="true"
  9. />
  10. <div class="header" :class="{ loggedIn }">
  11. <img class="background" src="/assets/homebg.jpeg" />
  12. <div class="overlay"></div>
  13. <div class="content-container">
  14. <div class="content">
  15. <img
  16. class="logo"
  17. src="/assets/white_wordmark.png"
  18. :alt="`${this.siteName}` || `Musare`"
  19. />
  20. <div v-if="!loggedIn" class="buttons">
  21. <button
  22. class="button login"
  23. @click="
  24. openModal({
  25. sector: 'header',
  26. modal: 'login'
  27. })
  28. "
  29. >
  30. Login
  31. </button>
  32. <button
  33. class="button register"
  34. @click="
  35. openModal({
  36. sector: 'header',
  37. modal: 'register'
  38. })
  39. "
  40. >
  41. Register
  42. </button>
  43. </div>
  44. </div>
  45. </div>
  46. </div>
  47. <div v-if="favoriteStations.length > 0" class="group">
  48. <div class="group-title">
  49. <div>
  50. <h2>My Favorites</h2>
  51. </div>
  52. </div>
  53. <router-link
  54. v-for="(station, index) in favoriteStations"
  55. :key="index"
  56. :to="{
  57. name: 'station',
  58. params: { id: station.name }
  59. }"
  60. class="card station-card"
  61. :class="{
  62. isPrivate: station.privacy === 'private',
  63. isMine: isOwner(station)
  64. }"
  65. :style="'--primary-color: var(--' + station.theme + ')'"
  66. >
  67. <div class="card-image">
  68. <figure class="image is-square">
  69. <div
  70. v-if="
  71. station.currentSong.songId &&
  72. (!station.currentSong.thumbnail ||
  73. (station.currentSong.thumbnail &&
  74. (station.currentSong.thumbnail.lastIndexOf(
  75. 'notes-transparent'
  76. ) !== -1 ||
  77. station.currentSong.thumbnail.lastIndexOf(
  78. '/assets/notes.png'
  79. ) !== -1 ||
  80. station.currentSong.thumbnail.lastIndexOf(
  81. 'i.ytimg.com'
  82. ) !== -1)) ||
  83. station.currentSong.thumbnail ==
  84. ('empty' || null))
  85. "
  86. class="ytThumbnailBg"
  87. :style="{
  88. 'background-image':
  89. 'url(' +
  90. `https://img.youtube.com/vi/${station.currentSong.songId}/mqdefault.jpg` +
  91. ')'
  92. }"
  93. ></div>
  94. <img
  95. v-if="
  96. station.currentSong.songId &&
  97. (!station.currentSong.thumbnail ||
  98. (station.currentSong.thumbnail &&
  99. (station.currentSong.thumbnail.lastIndexOf(
  100. 'notes-transparent'
  101. ) !== -1 ||
  102. station.currentSong.thumbnail.lastIndexOf(
  103. '/assets/notes.png'
  104. ) !== -1 ||
  105. station.currentSong.thumbnail.lastIndexOf(
  106. 'i.ytimg.com'
  107. ) !== -1)) ||
  108. station.currentSong.thumbnail ===
  109. 'empty' ||
  110. station.currentSong.thumbnail ==
  111. null)
  112. "
  113. :src="
  114. `https://img.youtube.com/vi/${station.currentSong.songId}/mqdefault.jpg`
  115. "
  116. onerror="this.src='/assets/notes-transparent.png'"
  117. />
  118. <img
  119. v-else
  120. :src="station.currentSong.thumbnail"
  121. onerror="this.src='/assets/notes-transparent.png'"
  122. />
  123. </figure>
  124. </div>
  125. <div class="card-content">
  126. <div class="media">
  127. <div class="media-left displayName">
  128. <i
  129. v-if="loggedIn && !station.isFavorited"
  130. @click.prevent="favoriteStation(station)"
  131. class="favorite material-icons"
  132. >star_border</i
  133. >
  134. <i
  135. v-if="loggedIn && station.isFavorited"
  136. @click.prevent="unfavoriteStation(station)"
  137. class="favorite material-icons"
  138. >star</i
  139. >
  140. <h5>{{ station.displayName }}</h5>
  141. <i
  142. v-if="station.type === 'official'"
  143. class="material-icons verified-station"
  144. title="Verified station"
  145. >
  146. check_circle
  147. </i>
  148. </div>
  149. </div>
  150. <div class="content">
  151. {{ station.description }}
  152. </div>
  153. <div class="under-content">
  154. <p class="hostedBy">
  155. Hosted by
  156. <span class="host">
  157. <span
  158. v-if="station.type === 'official'"
  159. title="Musare"
  160. >Musare</span
  161. >
  162. <user-id-to-username
  163. v-else
  164. :user-id="station.owner"
  165. :link="true"
  166. />
  167. </span>
  168. </p>
  169. <div class="icons">
  170. <i
  171. v-if="
  172. station.type === 'community' &&
  173. isOwner(station)
  174. "
  175. class="homeIcon material-icons"
  176. title="This is your station."
  177. >home</i
  178. >
  179. <i
  180. v-if="station.privacy === 'private'"
  181. class="privateIcon material-icons"
  182. title="This station is not visible to other users."
  183. >lock</i
  184. >
  185. <i
  186. v-if="station.privacy === 'unlisted'"
  187. class="unlistedIcon material-icons"
  188. title="Unlisted Station"
  189. >link</i
  190. >
  191. </div>
  192. </div>
  193. </div>
  194. <div class="bottomBar">
  195. <i
  196. v-if="station.paused && station.currentSong.title"
  197. class="material-icons"
  198. title="Station Paused"
  199. >pause</i
  200. >
  201. <i
  202. v-else-if="station.currentSong.title"
  203. class="material-icons"
  204. >music_note</i
  205. >
  206. <i v-else class="material-icons">music_off</i>
  207. <span
  208. v-if="station.currentSong.title"
  209. class="songTitle"
  210. :title="
  211. station.currentSong.artists.length > 0
  212. ? 'Now Playing: ' +
  213. station.currentSong.title +
  214. ' by ' +
  215. station.currentSong.artists.join(',')
  216. : 'Now Playing: ' +
  217. station.currentSong.title
  218. "
  219. >{{ station.currentSong.title }}
  220. {{
  221. station.currentSong.artists.length > 0
  222. ? " by " +
  223. station.currentSong.artists.join(",")
  224. : ""
  225. }}</span
  226. >
  227. <span v-else class="songTitle">No Songs Playing</span>
  228. <i
  229. class="material-icons stationMode"
  230. :title="
  231. station.partyMode
  232. ? 'Station in Party mode'
  233. : 'Station in Playlist mode'
  234. "
  235. >{{
  236. station.partyMode
  237. ? "emoji_people"
  238. : "playlist_play"
  239. }}</i
  240. >
  241. </div>
  242. </router-link>
  243. </div>
  244. <div class="group bottom">
  245. <div class="group-title">
  246. <div>
  247. <h1>Stations</h1>
  248. </div>
  249. </div>
  250. <a
  251. v-if="loggedIn"
  252. @click="
  253. openModal({
  254. sector: 'home',
  255. modal: 'createCommunityStation'
  256. })
  257. "
  258. class="card station-card createStation"
  259. >
  260. <div class="card-image">
  261. <figure class="image is-square">
  262. <i class="material-icons">radio</i>
  263. </figure>
  264. </div>
  265. <div class="card-content">
  266. <div class="media">
  267. <div class="media-left displayName">
  268. <h5>Create Station</h5>
  269. </div>
  270. </div>
  271. <div class="content">
  272. Click here to create your own station!
  273. </div>
  274. </div>
  275. <div class="bottomBar"></div>
  276. </a>
  277. <a
  278. v-else
  279. @click="
  280. openModal({
  281. sector: 'header',
  282. modal: 'login'
  283. })
  284. "
  285. class="card station-card createStation"
  286. >
  287. <div class="card-image">
  288. <figure class="image is-square">
  289. <i class="material-icons">radio</i>
  290. </figure>
  291. </div>
  292. <div class="card-content">
  293. <div class="media">
  294. <div class="media-left displayName">
  295. <h5>Create Station</h5>
  296. </div>
  297. </div>
  298. <div class="content">Login to create a station!</div>
  299. </div>
  300. <div class="bottomBar"></div>
  301. </a>
  302. <router-link
  303. v-for="(station, index) in filteredStations"
  304. :key="index"
  305. :to="{
  306. name: 'station',
  307. params: { id: station.name }
  308. }"
  309. class="card station-card"
  310. :class="{
  311. isPrivate: station.privacy === 'private',
  312. isMine: isOwner(station)
  313. }"
  314. :style="'--primary-color: var(--' + station.theme + ')'"
  315. >
  316. <div class="card-image">
  317. <figure class="image is-square">
  318. <div
  319. v-if="
  320. station.currentSong.songId &&
  321. (!station.currentSong.thumbnail ||
  322. (station.currentSong.thumbnail &&
  323. (station.currentSong.thumbnail.lastIndexOf(
  324. 'notes-transparent'
  325. ) !== -1 ||
  326. station.currentSong.thumbnail.lastIndexOf(
  327. '/assets/notes.png'
  328. ) !== -1)) ||
  329. station.currentSong.thumbnail ===
  330. 'empty' ||
  331. station.currentSong.thumbnail ==
  332. null)
  333. "
  334. class="ytThumbnailBg"
  335. :style="{
  336. 'background-image':
  337. 'url(' +
  338. `https://img.youtube.com/vi/${station.currentSong.songId}/mqdefault.jpg` +
  339. ')'
  340. }"
  341. ></div>
  342. <img
  343. v-if="
  344. station.currentSong.songId &&
  345. (!station.currentSong.thumbnail ||
  346. (station.currentSong.thumbnail &&
  347. (station.currentSong.thumbnail.lastIndexOf(
  348. 'notes-transparent'
  349. ) !== -1 ||
  350. station.currentSong.thumbnail.lastIndexOf(
  351. '/assets/notes.png'
  352. ) !== -1)) ||
  353. station.currentSong.thumbnail ==
  354. ('empty' || null))
  355. "
  356. :src="
  357. `https://img.youtube.com/vi/${station.currentSong.songId}/mqdefault.jpg`
  358. "
  359. onerror="this.src='/assets/notes-transparent.png'"
  360. />
  361. <img
  362. v-else
  363. :src="station.currentSong.thumbnail"
  364. onerror="this.src='/assets/notes-transparent.png'"
  365. />
  366. </figure>
  367. </div>
  368. <div class="card-content">
  369. <div class="media">
  370. <div class="media-left displayName">
  371. <i
  372. v-if="loggedIn && !station.isFavorited"
  373. @click.prevent="favoriteStation(station)"
  374. class="favorite material-icons"
  375. >star_border</i
  376. >
  377. <i
  378. v-if="loggedIn && station.isFavorited"
  379. @click.prevent="unfavoriteStation(station)"
  380. class="favorite material-icons"
  381. >star</i
  382. >
  383. <h5>{{ station.displayName }}</h5>
  384. <i
  385. v-if="station.type === 'official'"
  386. class="material-icons verified-station"
  387. title="Verified station"
  388. >
  389. check_circle
  390. </i>
  391. </div>
  392. </div>
  393. <div class="content">
  394. {{ station.description }}
  395. </div>
  396. <div class="under-content">
  397. <p class="hostedBy">
  398. Hosted by
  399. <span class="host">
  400. <span
  401. v-if="station.type === 'official'"
  402. title="Musare"
  403. >Musare</span
  404. >
  405. <user-id-to-username
  406. v-else
  407. :user-id="station.owner"
  408. :link="true"
  409. />
  410. </span>
  411. </p>
  412. <div class="icons">
  413. <i
  414. v-if="
  415. station.type === 'community' &&
  416. isOwner(station)
  417. "
  418. class="homeIcon material-icons"
  419. title="This is your station."
  420. >home</i
  421. >
  422. <i
  423. v-if="station.privacy === 'private'"
  424. class="privateIcon material-icons"
  425. title="This station is not visible to other users."
  426. >lock</i
  427. >
  428. <i
  429. v-if="station.privacy === 'unlisted'"
  430. class="unlistedIcon material-icons"
  431. title="Unlisted Station"
  432. >link</i
  433. >
  434. </div>
  435. </div>
  436. </div>
  437. <div class="bottomBar">
  438. <i
  439. v-if="station.paused && station.currentSong.title"
  440. class="material-icons"
  441. title="Station Paused"
  442. >pause</i
  443. >
  444. <i
  445. v-else-if="station.currentSong.title"
  446. class="material-icons"
  447. >music_note</i
  448. >
  449. <i v-else class="material-icons">music_off</i>
  450. <span
  451. v-if="station.currentSong.title"
  452. class="songTitle"
  453. :title="
  454. station.currentSong.artists.length > 0
  455. ? 'Now Playing: ' +
  456. station.currentSong.title +
  457. ' by ' +
  458. station.currentSong.artists.join(',')
  459. : 'Now Playing: ' +
  460. station.currentSong.title
  461. "
  462. >{{ station.currentSong.title }}
  463. {{
  464. station.currentSong.artists.length > 0
  465. ? " by " +
  466. station.currentSong.artists.join(",")
  467. : ""
  468. }}</span
  469. >
  470. <span v-else class="songTitle">No Songs Playing</span>
  471. <i
  472. class="material-icons stationMode"
  473. :title="
  474. station.partyMode
  475. ? 'Station in Party mode'
  476. : 'Station in Playlist mode'
  477. "
  478. >{{
  479. station.partyMode
  480. ? "emoji_people"
  481. : "playlist_play"
  482. }}</i
  483. >
  484. </div>
  485. </router-link>
  486. <h4 v-if="stations.length === 0">
  487. There are no stations to display
  488. </h4>
  489. </div>
  490. <main-footer />
  491. </div>
  492. <create-community-station v-if="modals.createCommunityStation" />
  493. </div>
  494. </template>
  495. <script>
  496. import { mapState, mapGetters, mapActions } from "vuex";
  497. import Toast from "toasters";
  498. import MainHeader from "../components/layout/MainHeader.vue";
  499. import MainFooter from "../components/layout/MainFooter.vue";
  500. import UserIdToUsername from "../components/common/UserIdToUsername.vue";
  501. import ws from "../ws";
  502. export default {
  503. components: {
  504. MainHeader,
  505. MainFooter,
  506. CreateCommunityStation: () =>
  507. import("../components/modals/CreateCommunityStation.vue"),
  508. UserIdToUsername
  509. },
  510. data() {
  511. return {
  512. recaptcha: { key: "" },
  513. stations: [],
  514. searchQuery: "",
  515. siteName: "Musare"
  516. };
  517. },
  518. computed: {
  519. ...mapState({
  520. loggedIn: state => state.user.auth.loggedIn,
  521. userId: state => state.user.auth.userId,
  522. modals: state => state.modalVisibility.modals.home
  523. }),
  524. ...mapGetters({
  525. socket: "websockets/getSocket"
  526. }),
  527. filteredStations() {
  528. const privacyOrder = ["public", "unlisted", "private"];
  529. return this.stations
  530. .filter(
  531. station =>
  532. JSON.stringify(Object.values(station)).indexOf(
  533. this.searchQuery
  534. ) !== -1
  535. )
  536. .sort(
  537. (a, b) =>
  538. this.isOwner(b) - this.isOwner(a) ||
  539. this.isPlaying(b) - this.isPlaying(a) ||
  540. a.paused - b.paused ||
  541. privacyOrder.indexOf(a.privacy) -
  542. privacyOrder.indexOf(b.privacy) ||
  543. b.userCount - a.userCount
  544. );
  545. },
  546. favoriteStations() {
  547. return this.filteredStations.filter(
  548. station => station.isFavorited === true
  549. );
  550. }
  551. },
  552. async mounted() {
  553. this.siteName = await lofig.get("siteSettings.siteName");
  554. if (this.socket.readyState === 1) this.init();
  555. ws.onConnect(() => this.init());
  556. this.socket.on("event:stations.created", res => {
  557. const station = res;
  558. if (this.stations.find(_station => _station._id === station._id)) {
  559. this.stations.forEach(s => {
  560. const _station = s;
  561. if (_station._id === station._id) {
  562. _station.privacy = station.privacy;
  563. }
  564. });
  565. } else {
  566. if (!station.currentSong)
  567. station.currentSong = {
  568. thumbnail: "/assets/notes-transparent.png"
  569. };
  570. if (station.currentSong && !station.currentSong.thumbnail)
  571. station.currentSong.ytThumbnail = `https://img.youtube.com/vi/${station.currentSong.songId}/mqdefault.jpg`;
  572. this.stations.push(station);
  573. }
  574. });
  575. this.socket.on("event:station.removed", response => {
  576. const { stationId } = response;
  577. const station = this.stations.find(
  578. station => station._id === stationId
  579. );
  580. if (station) {
  581. const stationIndex = this.stations.indexOf(station);
  582. this.stations.splice(stationIndex, 1);
  583. }
  584. });
  585. this.socket.on("event:userCount.updated", (stationId, userCount) => {
  586. this.stations.forEach(s => {
  587. const station = s;
  588. if (station._id === stationId) {
  589. station.userCount = userCount;
  590. }
  591. });
  592. });
  593. this.socket.on("event:station.updatePrivacy", response => {
  594. const { stationId, privacy } = response;
  595. this.stations.forEach(s => {
  596. const station = s;
  597. if (station._id === stationId) {
  598. station.privacy = privacy;
  599. }
  600. });
  601. });
  602. this.socket.on("event:station.updateName", response => {
  603. const { stationId, name } = response;
  604. this.stations.forEach(s => {
  605. const station = s;
  606. if (station._id === stationId) {
  607. station.name = name;
  608. }
  609. });
  610. });
  611. this.socket.on("event:station.updateDisplayName", response => {
  612. const { stationId, displayName } = response;
  613. this.stations.forEach(s => {
  614. const station = s;
  615. if (station._id === stationId) {
  616. station.displayName = displayName;
  617. }
  618. });
  619. });
  620. this.socket.on("event:station.updateDescription", response => {
  621. const { stationId, description } = response;
  622. this.stations.forEach(s => {
  623. const station = s;
  624. if (station._id === stationId) {
  625. station.description = description;
  626. }
  627. });
  628. });
  629. this.socket.on("event:station.updateTheme", response => {
  630. const { stationId, theme } = response;
  631. this.stations.forEach(s => {
  632. const station = s;
  633. if (station._id === stationId) {
  634. station.theme = theme;
  635. }
  636. });
  637. });
  638. this.socket.on("event:station.nextSong", (stationId, song) => {
  639. let newSong = song;
  640. this.stations.forEach(s => {
  641. const station = s;
  642. if (station._id === stationId) {
  643. if (!newSong)
  644. newSong = {
  645. thumbnail: "/assets/notes-transparent.png"
  646. };
  647. station.currentSong = newSong;
  648. }
  649. });
  650. });
  651. this.socket.on("event:station.pause", response => {
  652. const { stationId } = response;
  653. this.stations.forEach(s => {
  654. const station = s;
  655. if (station._id === stationId) {
  656. station.paused = true;
  657. }
  658. });
  659. });
  660. this.socket.on("event:station.resume", response => {
  661. const { stationId } = response;
  662. this.stations.forEach(s => {
  663. const station = s;
  664. if (station._id === stationId) {
  665. station.paused = false;
  666. }
  667. });
  668. });
  669. this.socket.on("event:user.favoritedStation", stationId => {
  670. this.stations.forEach(s => {
  671. const station = s;
  672. if (station._id === stationId) {
  673. station.isFavorited = true;
  674. }
  675. });
  676. });
  677. this.socket.on("event:user.unfavoritedStation", stationId => {
  678. this.stations.forEach(s => {
  679. const station = s;
  680. if (station._id === stationId) {
  681. station.isFavorited = false;
  682. }
  683. });
  684. });
  685. },
  686. methods: {
  687. init() {
  688. this.socket.dispatch("stations.index", data => {
  689. this.stations = [];
  690. if (data.status === "success")
  691. data.stations.forEach(station => {
  692. const modifiableStation = station;
  693. if (!modifiableStation.currentSong)
  694. modifiableStation.currentSong = {
  695. thumbnail: "/assets/notes-transparent.png"
  696. };
  697. if (
  698. modifiableStation.currentSong &&
  699. !modifiableStation.currentSong.thumbnail
  700. )
  701. modifiableStation.currentSong.ytThumbnail = `https://img.youtube.com/vi/${station.currentSong.songId}/mqdefault.jpg`;
  702. this.stations.push(modifiableStation);
  703. });
  704. });
  705. this.socket.dispatch("apis.joinRoom", "home", () => {});
  706. },
  707. isOwner(station) {
  708. return station.owner === this.userId;
  709. },
  710. isPlaying(station) {
  711. return typeof station.currentSong.title !== "undefined";
  712. },
  713. favoriteStation(station) {
  714. this.socket.dispatch(
  715. "stations.favoriteStation",
  716. station._id,
  717. res => {
  718. if (res.status === "success") {
  719. new Toast({
  720. content: "Successfully favorited station.",
  721. timeout: 4000
  722. });
  723. } else new Toast({ content: res.message, timeout: 8000 });
  724. }
  725. );
  726. },
  727. unfavoriteStation(station) {
  728. this.socket.dispatch(
  729. "stations.unfavoriteStation",
  730. station._id,
  731. res => {
  732. if (res.status === "success") {
  733. new Toast({
  734. content: "Successfully unfavorited station.",
  735. timeout: 4000
  736. });
  737. } else new Toast({ content: res.message, timeout: 8000 });
  738. }
  739. );
  740. },
  741. ...mapActions("modalVisibility", ["openModal"]),
  742. ...mapActions("station", ["updateIfStationIsFavorited"])
  743. }
  744. };
  745. </script>
  746. <style lang="scss">
  747. * {
  748. box-sizing: border-box;
  749. }
  750. html {
  751. width: 100%;
  752. height: 100%;
  753. color: rgba(0, 0, 0, 0.87);
  754. body {
  755. width: 100%;
  756. height: 100%;
  757. margin: 0;
  758. padding: 0;
  759. }
  760. }
  761. .night-mode {
  762. .header .overlay {
  763. background: linear-gradient(
  764. 180deg,
  765. rgba(34, 34, 34, 0.8) 0%,
  766. rgba(34, 34, 34, 0.95) 31.25%,
  767. rgba(34, 34, 34, 0.9) 54.17%,
  768. rgba(34, 34, 34, 0.8) 100%
  769. );
  770. }
  771. .card,
  772. .card-content,
  773. .card-content div {
  774. background-color: var(--dark-grey-3);
  775. }
  776. .card-content .icons i,
  777. .group-title i {
  778. color: var(--light-grey-2);
  779. }
  780. .card-image .image {
  781. background-color: var(--dark-grey-2);
  782. }
  783. .card-content .under-content .hostedBy {
  784. color: var(--light-grey-2);
  785. }
  786. }
  787. @media only screen and (min-width: 1200px) {
  788. html {
  789. font-size: 15px;
  790. }
  791. }
  792. @media only screen and (min-width: 992px) {
  793. html {
  794. font-size: 14.5px;
  795. }
  796. }
  797. @media only screen and (min-width: 0) {
  798. html {
  799. font-size: 14px;
  800. }
  801. }
  802. .header {
  803. display: flex;
  804. height: 35vh;
  805. margin-top: -64px;
  806. border-radius: 0% 0% 33% 33% / 0% 0% 7% 7%;
  807. img.background {
  808. height: 35vh;
  809. width: 100%;
  810. object-fit: cover;
  811. object-position: center;
  812. filter: blur(1px);
  813. border-radius: 0% 0% 33% 33% / 0% 0% 7% 7%;
  814. overflow: hidden;
  815. }
  816. .overlay {
  817. background: linear-gradient(
  818. 180deg,
  819. rgba(3, 169, 244, 0.8) 0%,
  820. rgba(3, 169, 244, 0.95) 31.25%,
  821. rgba(3, 169, 244, 0.9) 54.17%,
  822. rgba(3, 169, 244, 0.8) 100%
  823. );
  824. position: absolute;
  825. height: 35vh;
  826. width: 100%;
  827. border-radius: 0% 0% 33% 33% / 0% 0% 7% 7%;
  828. overflow: hidden;
  829. }
  830. .content-container {
  831. position: absolute;
  832. left: 0;
  833. right: 0;
  834. margin-left: auto;
  835. margin-right: auto;
  836. text-align: center;
  837. height: 100%;
  838. height: 35vh;
  839. .content {
  840. position: absolute;
  841. top: 50%;
  842. left: 0;
  843. right: 0;
  844. transform: translateY(-50%);
  845. background-color: transparent !important;
  846. img.logo {
  847. max-height: 90px;
  848. font-size: 40px;
  849. color: var(--white);
  850. font-family: Pacifico, cursive;
  851. }
  852. .buttons {
  853. display: flex;
  854. justify-content: center;
  855. margin-top: 20px;
  856. flex-wrap: wrap;
  857. .login,
  858. .register {
  859. margin: 5px 10px;
  860. padding: 10px 15px;
  861. border-radius: 5px;
  862. font-size: 18px;
  863. width: 100%;
  864. max-width: 250px;
  865. font-weight: 600;
  866. border: 0;
  867. height: inherit;
  868. }
  869. .login {
  870. background: var(--white);
  871. color: var(--primary-color);
  872. }
  873. .register {
  874. background: var(--purple);
  875. color: var(--white);
  876. }
  877. }
  878. }
  879. }
  880. &.loggedIn {
  881. height: 20vh;
  882. .overlay,
  883. .content-container,
  884. img.background {
  885. height: 20vh;
  886. }
  887. }
  888. }
  889. @media only screen and (max-width: 550px) {
  890. .header {
  891. height: 45vh;
  892. .overlay,
  893. .content-container,
  894. img.background {
  895. height: 45vh;
  896. }
  897. }
  898. }
  899. .under-content {
  900. height: 20px;
  901. position: relative;
  902. line-height: 1;
  903. font-size: 24px;
  904. display: flex;
  905. align-items: center;
  906. text-align: left;
  907. margin-top: 10px;
  908. p {
  909. font-size: 15px;
  910. line-height: 15px;
  911. display: inline;
  912. }
  913. i {
  914. font-size: 20px;
  915. }
  916. * {
  917. z-index: 10;
  918. position: relative;
  919. }
  920. .icons {
  921. position: absolute;
  922. right: 0;
  923. .material-icons {
  924. font-size: 22px;
  925. }
  926. .material-icons:first-child {
  927. margin-left: 5px;
  928. }
  929. .unlistedIcon {
  930. color: var(--orange);
  931. }
  932. .privateIcon {
  933. color: var(--dark-pink);
  934. }
  935. .homeIcon {
  936. color: var(--light-purple);
  937. }
  938. }
  939. .hostedBy {
  940. font-weight: 400;
  941. font-size: 12px;
  942. color: var(--black);
  943. .host,
  944. .host a {
  945. font-weight: 400;
  946. color: var(--primary-color);
  947. &:hover,
  948. &:focus {
  949. filter: brightness(90%);
  950. }
  951. }
  952. }
  953. }
  954. .app {
  955. display: flex;
  956. flex-direction: column;
  957. }
  958. .users-count {
  959. font-size: 20px;
  960. position: relative;
  961. top: -4px;
  962. }
  963. .group {
  964. min-height: 64px;
  965. flex: 1 0 auto;
  966. }
  967. .station-card {
  968. display: inline-flex;
  969. flex-direction: row;
  970. overflow: hidden;
  971. margin: 10px;
  972. cursor: pointer;
  973. height: 150px;
  974. width: calc(100% - 30px);
  975. max-width: 400px;
  976. flex-wrap: wrap;
  977. border-radius: 5px;
  978. box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
  979. transition: all ease-in-out 0.2s;
  980. .card-content {
  981. padding: 10px 10px 10px 15px;
  982. display: flex;
  983. flex-direction: column;
  984. flex-grow: 1;
  985. -webkit-line-clamp: 2;
  986. .media {
  987. display: flex;
  988. align-items: center;
  989. margin-bottom: 0;
  990. .displayName {
  991. display: flex;
  992. align-items: center;
  993. width: 100%;
  994. overflow: hidden;
  995. text-overflow: ellipsis;
  996. display: flex;
  997. line-height: 30px;
  998. max-height: 30px;
  999. .favorite {
  1000. position: absolute;
  1001. color: var(--yellow);
  1002. right: 10px;
  1003. top: 10px;
  1004. font-size: 28px;
  1005. }
  1006. h5 {
  1007. font-size: 20px;
  1008. font-weight: 400;
  1009. margin: 0;
  1010. display: inline;
  1011. margin-right: 6px;
  1012. line-height: 30px;
  1013. text-overflow: ellipsis;
  1014. overflow: hidden;
  1015. white-space: nowrap;
  1016. max-width: 200px;
  1017. }
  1018. i {
  1019. font-size: 22px;
  1020. }
  1021. .verified-station {
  1022. color: var(--primary-color);
  1023. }
  1024. }
  1025. }
  1026. .content {
  1027. word-wrap: break-word;
  1028. overflow: hidden;
  1029. text-overflow: ellipsis;
  1030. display: -webkit-box;
  1031. -webkit-box-orient: vertical;
  1032. -webkit-line-clamp: 3;
  1033. line-height: 20px;
  1034. flex-grow: 1;
  1035. text-align: left;
  1036. word-wrap: break-word;
  1037. margin-bottom: 0;
  1038. }
  1039. }
  1040. .card-image {
  1041. width: 120px;
  1042. .image {
  1043. box-shadow: 1px 0 3px rgba(100, 100, 100, 0.3);
  1044. .ytThumbnailBg {
  1045. background: url("/assets/notes-transparent.png") no-repeat
  1046. center center;
  1047. background-size: cover;
  1048. height: 100%;
  1049. width: 100%;
  1050. position: absolute;
  1051. top: 0;
  1052. filter: blur(1px);
  1053. }
  1054. img {
  1055. height: auto;
  1056. width: 120px;
  1057. top: 0;
  1058. margin-top: auto;
  1059. margin-bottom: auto;
  1060. z-index: 1;
  1061. }
  1062. }
  1063. }
  1064. .bottomBar {
  1065. position: relative;
  1066. display: flex;
  1067. align-items: center;
  1068. background: var(--primary-color);
  1069. // box-shadow: inset 0px 2px 4px rgba(100, 100, 100, 0.3);
  1070. width: 100%;
  1071. height: 30px;
  1072. line-height: 30px;
  1073. color: var(--white);
  1074. font-weight: 400;
  1075. font-size: 12px;
  1076. padding: 0 5px;
  1077. flex-basis: 100%;
  1078. i.material-icons {
  1079. vertical-align: middle;
  1080. margin-left: 5px;
  1081. font-size: 22px;
  1082. }
  1083. .songTitle {
  1084. text-align: left;
  1085. vertical-align: middle;
  1086. margin-left: 5px;
  1087. line-height: 30px;
  1088. flex: 2 1 0;
  1089. overflow: hidden;
  1090. text-overflow: ellipsis;
  1091. white-space: nowrap;
  1092. }
  1093. }
  1094. &.createStation {
  1095. .card-image .image.is-square .material-icons {
  1096. position: absolute;
  1097. top: 25px;
  1098. bottom: 25px;
  1099. left: 0;
  1100. right: 0;
  1101. text-align: center;
  1102. font-size: 70px;
  1103. color: var(--primary-color);
  1104. }
  1105. .card-content {
  1106. .media {
  1107. margin-top: auto;
  1108. .displayName h5 {
  1109. font-weight: 600;
  1110. }
  1111. }
  1112. .content {
  1113. flex-grow: unset;
  1114. margin-bottom: auto;
  1115. }
  1116. }
  1117. }
  1118. }
  1119. .station-card:hover {
  1120. box-shadow: 0 2px 3px rgba(10, 10, 10, 0.3), 0 0 10px rgba(10, 10, 10, 0.3);
  1121. transition: all ease-in-out 0.2s;
  1122. }
  1123. .community-button {
  1124. cursor: pointer;
  1125. transition: 0.25s ease color;
  1126. font-size: 30px;
  1127. color: var(--dark-grey);
  1128. }
  1129. .community-button:hover {
  1130. color: var(--primary-color);
  1131. }
  1132. .station-privacy {
  1133. text-transform: capitalize;
  1134. }
  1135. .label {
  1136. display: flex;
  1137. }
  1138. .g-recaptcha {
  1139. display: flex;
  1140. justify-content: center;
  1141. margin-top: 20px;
  1142. }
  1143. .group {
  1144. text-align: center;
  1145. width: 100%;
  1146. margin: 10px 0;
  1147. .group-title {
  1148. display: flex;
  1149. align-items: center;
  1150. justify-content: center;
  1151. margin: 25px 0;
  1152. h1 {
  1153. display: inline-block;
  1154. font-size: 45px;
  1155. margin: 0;
  1156. }
  1157. h2 {
  1158. font-size: 35px;
  1159. margin: 0;
  1160. }
  1161. a {
  1162. display: flex;
  1163. margin-left: 8px;
  1164. }
  1165. }
  1166. &.bottom {
  1167. margin-bottom: 40px;
  1168. }
  1169. }
  1170. </style>