Playlists.vue 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214
  1. <template>
  2. <div class="station-playlists">
  3. <div class="tabs-container">
  4. <div class="tab-selection">
  5. <button
  6. class="button is-default"
  7. ref="search-tab"
  8. :class="{ selected: tab === 'search' }"
  9. @click="showTab('search')"
  10. >
  11. Search
  12. </button>
  13. <button
  14. v-if="station.type === 'community'"
  15. class="button is-default"
  16. ref="my-playlists-tab"
  17. :class="{ selected: tab === 'my-playlists' }"
  18. @click="showTab('my-playlists')"
  19. >
  20. My Playlists
  21. </button>
  22. <button
  23. class="button is-default"
  24. ref="party-tab"
  25. :class="{ selected: tab === 'party' }"
  26. v-if="isPartyMode()"
  27. @click="showTab('party')"
  28. >
  29. Party
  30. </button>
  31. <button
  32. class="button is-default"
  33. ref="included-tab"
  34. :class="{ selected: tab === 'included' }"
  35. v-if="isPlaylistMode()"
  36. @click="showTab('included')"
  37. >
  38. Included
  39. </button>
  40. <button
  41. class="button is-default"
  42. ref="excluded-tab"
  43. :class="{ selected: tab === 'excluded' }"
  44. v-if="isOwnerOrAdmin()"
  45. @click="showTab('excluded')"
  46. >
  47. Excluded
  48. </button>
  49. </div>
  50. <div class="tab" v-show="tab === 'search'">
  51. <div v-if="featuredPlaylists.length > 0">
  52. <label class="label"> Featured playlists </label>
  53. <playlist-item
  54. v-for="featuredPlaylist in featuredPlaylists"
  55. :key="`searchKey-${featuredPlaylist._id}`"
  56. :playlist="featuredPlaylist"
  57. :show-owner="true"
  58. >
  59. <template #item-icon>
  60. <i
  61. class="material-icons"
  62. v-if="
  63. isAllowedToParty() &&
  64. isSelected(featuredPlaylist._id)
  65. "
  66. content="This playlist is currently selected"
  67. v-tippy
  68. >
  69. radio
  70. </i>
  71. <i
  72. class="material-icons"
  73. v-else-if="
  74. isOwnerOrAdmin() &&
  75. isPlaylistMode() &&
  76. isIncluded(featuredPlaylist._id)
  77. "
  78. content="This playlist is currently included"
  79. v-tippy
  80. >
  81. play_arrow
  82. </i>
  83. <i
  84. class="material-icons excluded-icon"
  85. v-else-if="
  86. isOwnerOrAdmin() &&
  87. isExcluded(featuredPlaylist._id)
  88. "
  89. content="This playlist is currently excluded"
  90. v-tippy
  91. >
  92. block
  93. </i>
  94. <i
  95. class="material-icons"
  96. v-else
  97. :content="
  98. isPartyMode()
  99. ? 'This playlist is currently not selected or excluded'
  100. : 'This playlist is currently not included or excluded'
  101. "
  102. v-tippy
  103. >
  104. play_disabled
  105. </i>
  106. </template>
  107. <template #actions>
  108. <i
  109. v-if="isExcluded(featuredPlaylist._id)"
  110. class="material-icons stop-icon"
  111. content="This playlist is blacklisted in this station"
  112. v-tippy="{ theme: 'info' }"
  113. >play_disabled</i
  114. >
  115. <confirm
  116. v-if="
  117. isPartyMode() &&
  118. isSelected(featuredPlaylist._id)
  119. "
  120. @confirm="
  121. deselectPartyPlaylist(featuredPlaylist._id)
  122. "
  123. >
  124. <i
  125. class="material-icons stop-icon"
  126. content="Stop playing songs from this playlist"
  127. v-tippy
  128. >
  129. stop
  130. </i>
  131. </confirm>
  132. <confirm
  133. v-if="
  134. isOwnerOrAdmin() &&
  135. isPlaylistMode() &&
  136. isIncluded(featuredPlaylist._id)
  137. "
  138. @confirm="
  139. removeIncludedPlaylist(featuredPlaylist._id)
  140. "
  141. >
  142. <i
  143. class="material-icons stop-icon"
  144. content="Stop playing songs from this playlist"
  145. v-tippy
  146. >
  147. stop
  148. </i>
  149. </confirm>
  150. <i
  151. v-if="
  152. isPartyMode() &&
  153. !isSelected(featuredPlaylist._id) &&
  154. !isExcluded(featuredPlaylist._id)
  155. "
  156. @click="selectPartyPlaylist(featuredPlaylist)"
  157. class="material-icons play-icon"
  158. content="Request songs from this playlist"
  159. v-tippy
  160. >play_arrow</i
  161. >
  162. <i
  163. v-if="
  164. isOwnerOrAdmin() &&
  165. isPlaylistMode() &&
  166. !isIncluded(featuredPlaylist._id) &&
  167. !isExcluded(featuredPlaylist._id)
  168. "
  169. @click="includePlaylist(featuredPlaylist)"
  170. class="material-icons play-icon"
  171. :content="'Play songs from this playlist'"
  172. v-tippy
  173. >play_arrow</i
  174. >
  175. <confirm
  176. v-if="
  177. isOwnerOrAdmin() &&
  178. !isExcluded(featuredPlaylist._id)
  179. "
  180. @confirm="
  181. blacklistPlaylist(featuredPlaylist._id)
  182. "
  183. >
  184. <i
  185. class="material-icons stop-icon"
  186. content="Blacklist Playlist"
  187. v-tippy
  188. >block</i
  189. >
  190. </confirm>
  191. <confirm
  192. v-if="
  193. isOwnerOrAdmin() &&
  194. isExcluded(featuredPlaylist._id)
  195. "
  196. @confirm="
  197. removeExcludedPlaylist(featuredPlaylist._id)
  198. "
  199. >
  200. <i
  201. class="material-icons stop-icon"
  202. content="Stop blacklisting songs from this playlist"
  203. v-tippy
  204. >
  205. stop
  206. </i>
  207. </confirm>
  208. <i
  209. v-if="featuredPlaylist.createdBy === myUserId"
  210. @click="showPlaylist(featuredPlaylist._id)"
  211. class="material-icons edit-icon"
  212. content="Edit Playlist"
  213. v-tippy
  214. >edit</i
  215. >
  216. <i
  217. v-if="
  218. featuredPlaylist.createdBy !== myUserId &&
  219. (featuredPlaylist.privacy === 'public' ||
  220. isAdmin())
  221. "
  222. @click="showPlaylist(featuredPlaylist._id)"
  223. class="material-icons edit-icon"
  224. content="View Playlist"
  225. v-tippy
  226. >visibility</i
  227. >
  228. </template>
  229. </playlist-item>
  230. <br />
  231. </div>
  232. <label class="label"> Search for a public playlist </label>
  233. <div class="control is-grouped input-with-button">
  234. <p class="control is-expanded">
  235. <input
  236. class="input"
  237. type="text"
  238. placeholder="Enter your playlist query here..."
  239. v-model="search.query"
  240. @keyup.enter="searchForPlaylists(1)"
  241. />
  242. </p>
  243. <p class="control">
  244. <a class="button is-info" @click="searchForPlaylists(1)"
  245. ><i class="material-icons icon-with-button"
  246. >search</i
  247. >Search</a
  248. >
  249. </p>
  250. </div>
  251. <div v-if="search.results.length > 0">
  252. <playlist-item
  253. v-for="playlist in search.results"
  254. :key="`searchKey-${playlist._id}`"
  255. :playlist="playlist"
  256. :show-owner="true"
  257. >
  258. <template #item-icon>
  259. <i
  260. class="material-icons"
  261. v-if="
  262. isAllowedToParty() &&
  263. isSelected(playlist._id)
  264. "
  265. content="This playlist is currently selected"
  266. v-tippy
  267. >
  268. radio
  269. </i>
  270. <i
  271. class="material-icons"
  272. v-else-if="
  273. isOwnerOrAdmin() &&
  274. isPlaylistMode() &&
  275. isIncluded(playlist._id)
  276. "
  277. content="This playlist is currently included"
  278. v-tippy
  279. >
  280. play_arrow
  281. </i>
  282. <i
  283. class="material-icons excluded-icon"
  284. v-else-if="
  285. isOwnerOrAdmin() && isExcluded(playlist._id)
  286. "
  287. content="This playlist is currently excluded"
  288. v-tippy
  289. >
  290. block
  291. </i>
  292. <i
  293. class="material-icons"
  294. v-else
  295. :content="
  296. isPartyMode()
  297. ? 'This playlist is currently not selected or excluded'
  298. : 'This playlist is currently not included or excluded'
  299. "
  300. v-tippy
  301. >
  302. play_disabled
  303. </i>
  304. </template>
  305. <template #actions>
  306. <i
  307. v-if="isExcluded(playlist._id)"
  308. class="material-icons stop-icon"
  309. content="This playlist is blacklisted in this station"
  310. v-tippy="{ theme: 'info' }"
  311. >play_disabled</i
  312. >
  313. <confirm
  314. v-if="isPartyMode() && isSelected(playlist._id)"
  315. @confirm="deselectPartyPlaylist(playlist._id)"
  316. >
  317. <i
  318. class="material-icons stop-icon"
  319. content="Stop playing songs from this playlist"
  320. v-tippy
  321. >
  322. stop
  323. </i>
  324. </confirm>
  325. <confirm
  326. v-if="
  327. isOwnerOrAdmin() &&
  328. isPlaylistMode() &&
  329. isIncluded(playlist._id)
  330. "
  331. @confirm="removeIncludedPlaylist(playlist._id)"
  332. >
  333. <i
  334. class="material-icons stop-icon"
  335. content="Stop playing songs from this playlist"
  336. v-tippy
  337. >
  338. stop
  339. </i>
  340. </confirm>
  341. <i
  342. v-if="
  343. isPartyMode() &&
  344. !isSelected(playlist._id) &&
  345. !isExcluded(playlist._id)
  346. "
  347. @click="selectPartyPlaylist(playlist)"
  348. class="material-icons play-icon"
  349. content="Request songs from this playlist"
  350. v-tippy
  351. >play_arrow</i
  352. >
  353. <i
  354. v-if="
  355. isOwnerOrAdmin() &&
  356. isPlaylistMode() &&
  357. !isIncluded(playlist._id) &&
  358. !isExcluded(playlist._id)
  359. "
  360. @click="includePlaylist(playlist)"
  361. class="material-icons play-icon"
  362. :content="'Play songs from this playlist'"
  363. v-tippy
  364. >play_arrow</i
  365. >
  366. <confirm
  367. v-if="
  368. isOwnerOrAdmin() &&
  369. !isExcluded(playlist._id)
  370. "
  371. @confirm="blacklistPlaylist(playlist._id)"
  372. >
  373. <i
  374. class="material-icons stop-icon"
  375. content="Blacklist Playlist"
  376. v-tippy
  377. >block</i
  378. >
  379. </confirm>
  380. <confirm
  381. v-if="
  382. isOwnerOrAdmin() && isExcluded(playlist._id)
  383. "
  384. @confirm="removeExcludedPlaylist(playlist._id)"
  385. >
  386. <i
  387. class="material-icons stop-icon"
  388. content="Stop blacklisting songs from this playlist"
  389. v-tippy
  390. >
  391. stop
  392. </i>
  393. </confirm>
  394. <i
  395. v-if="playlist.createdBy === myUserId"
  396. @click="showPlaylist(playlist._id)"
  397. class="material-icons edit-icon"
  398. content="Edit Playlist"
  399. v-tippy
  400. >edit</i
  401. >
  402. <i
  403. v-if="
  404. playlist.createdBy !== myUserId &&
  405. (playlist.privacy === 'public' || isAdmin())
  406. "
  407. @click="showPlaylist(playlist._id)"
  408. class="material-icons edit-icon"
  409. content="View Playlist"
  410. v-tippy
  411. >visibility</i
  412. >
  413. </template>
  414. </playlist-item>
  415. <button
  416. v-if="resultsLeftCount > 0"
  417. class="button is-primary load-more-button"
  418. @click="searchForPlaylists(search.page + 1)"
  419. >
  420. Load {{ nextPageResultsCount }} more results
  421. </button>
  422. </div>
  423. </div>
  424. <div
  425. v-if="station.type === 'community'"
  426. class="tab"
  427. v-show="tab === 'my-playlists'"
  428. >
  429. <button
  430. class="button is-primary"
  431. id="create-new-playlist-button"
  432. @click="openModal('createPlaylist')"
  433. >
  434. Create new playlist
  435. </button>
  436. <div
  437. class="menu-list scrollable-list"
  438. v-if="playlists.length > 0"
  439. >
  440. <draggable
  441. tag="transition-group"
  442. :component-data="{
  443. name: !drag ? 'draggable-list-transition' : null
  444. }"
  445. item-key="_id"
  446. v-model="playlists"
  447. v-bind="dragOptions"
  448. @start="drag = true"
  449. @end="drag = false"
  450. @change="savePlaylistOrder"
  451. >
  452. <template #item="{ element }">
  453. <playlist-item
  454. class="item-draggable"
  455. :playlist="element"
  456. >
  457. <template #item-icon>
  458. <i
  459. class="material-icons"
  460. v-if="
  461. isAllowedToParty() &&
  462. isSelected(element._id)
  463. "
  464. content="This playlist is currently selected"
  465. v-tippy
  466. >
  467. radio
  468. </i>
  469. <i
  470. class="material-icons"
  471. v-else-if="
  472. isOwnerOrAdmin() &&
  473. isPlaylistMode() &&
  474. isIncluded(element._id)
  475. "
  476. content="This playlist is currently included"
  477. v-tippy
  478. >
  479. play_arrow
  480. </i>
  481. <i
  482. class="material-icons excluded-icon"
  483. v-else-if="
  484. isOwnerOrAdmin() &&
  485. isExcluded(element._id)
  486. "
  487. content="This playlist is currently excluded"
  488. v-tippy
  489. >
  490. block
  491. </i>
  492. <i
  493. class="material-icons"
  494. v-else
  495. :content="
  496. isPartyMode()
  497. ? 'This playlist is currently not selected or excluded'
  498. : 'This playlist is currently not included or excluded'
  499. "
  500. v-tippy
  501. >
  502. play_disabled
  503. </i>
  504. </template>
  505. <template #actions>
  506. <i
  507. v-if="
  508. isPartyMode() &&
  509. !isSelected(element._id)
  510. "
  511. @click="selectPartyPlaylist(element)"
  512. class="material-icons play-icon"
  513. content="Request songs from this playlist"
  514. v-tippy
  515. >play_arrow</i
  516. >
  517. <i
  518. v-if="
  519. isPlaylistMode() &&
  520. isOwnerOrAdmin() &&
  521. !isIncluded(element._id)
  522. "
  523. @click="includePlaylist(element)"
  524. class="material-icons play-icon"
  525. content="Play songs from this playlist"
  526. v-tippy
  527. >play_arrow</i
  528. >
  529. <confirm
  530. v-if="
  531. isPartyMode() &&
  532. isSelected(element._id)
  533. "
  534. @confirm="
  535. deselectPartyPlaylist(element._id)
  536. "
  537. >
  538. <i
  539. class="material-icons stop-icon"
  540. content="Stop requesting songs from this playlist"
  541. v-tippy
  542. >stop</i
  543. >
  544. </confirm>
  545. <confirm
  546. v-if="
  547. isPlaylistMode() &&
  548. isOwnerOrAdmin() &&
  549. isIncluded(element._id)
  550. "
  551. @confirm="
  552. removeIncludedPlaylist(element._id)
  553. "
  554. >
  555. <i
  556. class="material-icons stop-icon"
  557. content="Stop playing songs from this playlist"
  558. v-tippy
  559. >stop</i
  560. >
  561. </confirm>
  562. <confirm
  563. v-if="
  564. isOwnerOrAdmin() &&
  565. !isExcluded(element._id)
  566. "
  567. @confirm="
  568. blacklistPlaylist(element._id)
  569. "
  570. >
  571. <i
  572. class="material-icons stop-icon"
  573. content="Blacklist Playlist"
  574. v-tippy
  575. >block</i
  576. >
  577. </confirm>
  578. <confirm
  579. v-if="
  580. isOwnerOrAdmin() &&
  581. isExcluded(element._id)
  582. "
  583. @confirm="
  584. removeExcludedPlaylist(element._id)
  585. "
  586. >
  587. <i
  588. class="material-icons stop-icon"
  589. content="Stop blacklisting songs from this playlist"
  590. v-tippy
  591. >
  592. stop
  593. </i>
  594. </confirm>
  595. <i
  596. @click="showPlaylist(element._id)"
  597. class="material-icons edit-icon"
  598. content="Edit Playlist"
  599. v-tippy
  600. >edit</i
  601. >
  602. </template>
  603. </playlist-item>
  604. </template>
  605. </draggable>
  606. </div>
  607. <p v-else class="has-text-centered scrollable-list">
  608. You don't have any playlists!
  609. </p>
  610. </div>
  611. <div class="tab" v-show="tab === 'party'" v-if="isPartyMode()">
  612. <div v-if="partyPlaylists.length > 0">
  613. <playlist-item
  614. v-for="playlist in partyPlaylists"
  615. :key="`key-${playlist._id}`"
  616. :playlist="playlist"
  617. :show-owner="true"
  618. >
  619. <template #item-icon>
  620. <i
  621. class="material-icons"
  622. content="This playlist is currently selected"
  623. v-tippy
  624. >
  625. radio
  626. </i>
  627. </template>
  628. <template #actions>
  629. <confirm
  630. v-if="isOwnerOrAdmin()"
  631. @confirm="deselectPartyPlaylist(playlist._id)"
  632. >
  633. <i
  634. class="material-icons stop-icon"
  635. content="Stop playing songs from this playlist"
  636. v-tippy
  637. >
  638. stop
  639. </i>
  640. </confirm>
  641. <confirm
  642. v-if="isOwnerOrAdmin()"
  643. @confirm="blacklistPlaylist(playlist._id)"
  644. >
  645. <i
  646. class="material-icons stop-icon"
  647. content="Blacklist Playlist"
  648. v-tippy
  649. >block</i
  650. >
  651. </confirm>
  652. <i
  653. v-if="playlist.createdBy === myUserId"
  654. @click="showPlaylist(playlist._id)"
  655. class="material-icons edit-icon"
  656. content="Edit Playlist"
  657. v-tippy
  658. >edit</i
  659. >
  660. <i
  661. v-if="
  662. playlist.createdBy !== myUserId &&
  663. (playlist.privacy === 'public' || isAdmin())
  664. "
  665. @click="showPlaylist(playlist._id)"
  666. class="material-icons edit-icon"
  667. content="View Playlist"
  668. v-tippy
  669. >visibility</i
  670. >
  671. </template>
  672. </playlist-item>
  673. </div>
  674. <p v-else class="has-text-centered scrollable-list">
  675. No playlists currently being played.
  676. </p>
  677. </div>
  678. <div
  679. class="tab"
  680. v-show="tab === 'included'"
  681. v-if="isPlaylistMode()"
  682. >
  683. <div v-if="includedPlaylists.length > 0">
  684. <playlist-item
  685. v-for="playlist in includedPlaylists"
  686. :key="`key-${playlist._id}`"
  687. :playlist="playlist"
  688. :show-owner="true"
  689. >
  690. <template #item-icon>
  691. <i
  692. class="material-icons"
  693. content="This playlist is currently included"
  694. v-tippy
  695. >
  696. play_arrow
  697. </i>
  698. </template>
  699. <template #actions>
  700. <confirm
  701. v-if="isOwnerOrAdmin()"
  702. @confirm="removeIncludedPlaylist(playlist._id)"
  703. >
  704. <i
  705. class="material-icons stop-icon"
  706. content="Stop playing songs from this playlist"
  707. v-tippy
  708. >
  709. stop
  710. </i>
  711. </confirm>
  712. <confirm
  713. v-if="isOwnerOrAdmin()"
  714. @confirm="blacklistPlaylist(playlist._id)"
  715. >
  716. <i
  717. class="material-icons stop-icon"
  718. content="Blacklist Playlist"
  719. v-tippy
  720. >block</i
  721. >
  722. </confirm>
  723. <i
  724. v-if="playlist.createdBy === myUserId"
  725. @click="showPlaylist(playlist._id)"
  726. class="material-icons edit-icon"
  727. content="Edit Playlist"
  728. v-tippy
  729. >edit</i
  730. >
  731. <i
  732. v-if="
  733. playlist.createdBy !== myUserId &&
  734. (playlist.privacy === 'public' || isAdmin())
  735. "
  736. @click="showPlaylist(playlist._id)"
  737. class="material-icons edit-icon"
  738. content="View Playlist"
  739. v-tippy
  740. >visibility</i
  741. >
  742. </template>
  743. </playlist-item>
  744. </div>
  745. <p v-else class="has-text-centered scrollable-list">
  746. No playlists currently included.
  747. </p>
  748. </div>
  749. <div
  750. class="tab"
  751. v-show="tab === 'excluded'"
  752. v-if="isOwnerOrAdmin()"
  753. >
  754. <div v-if="excludedPlaylists.length > 0">
  755. <playlist-item
  756. :playlist="playlist"
  757. v-for="playlist in excludedPlaylists"
  758. :key="`key-${playlist._id}`"
  759. >
  760. <template #item-icon>
  761. <i
  762. class="material-icons excluded-icon"
  763. content="This playlist is currently excluded"
  764. v-tippy
  765. >
  766. block
  767. </i>
  768. </template>
  769. <template #actions>
  770. <confirm
  771. @confirm="removeExcludedPlaylist(playlist._id)"
  772. >
  773. <i
  774. class="material-icons stop-icon"
  775. content="Stop blacklisting songs from this playlist
  776. "
  777. v-tippy
  778. >stop</i
  779. >
  780. </confirm>
  781. <i
  782. v-if="playlist.createdBy === userId"
  783. @click="showPlaylist(playlist._id)"
  784. class="material-icons edit-icon"
  785. content="Edit Playlist"
  786. v-tippy
  787. >edit</i
  788. >
  789. <i
  790. v-else
  791. @click="showPlaylist(playlist._id)"
  792. class="material-icons edit-icon"
  793. content="View Playlist"
  794. v-tippy
  795. >visibility</i
  796. >
  797. </template>
  798. </playlist-item>
  799. </div>
  800. <p v-else class="has-text-centered scrollable-list">
  801. No playlists currently excluded.
  802. </p>
  803. </div>
  804. </div>
  805. </div>
  806. </template>
  807. <script>
  808. import { mapActions, mapState, mapGetters } from "vuex";
  809. import Toast from "toasters";
  810. import ws from "@/ws";
  811. import PlaylistItem from "@/components/PlaylistItem.vue";
  812. import Confirm from "@/components/Confirm.vue";
  813. import SortablePlaylists from "@/mixins/SortablePlaylists.vue";
  814. export default {
  815. components: {
  816. PlaylistItem,
  817. Confirm
  818. },
  819. mixins: [SortablePlaylists],
  820. data() {
  821. return {
  822. tab: "included",
  823. search: {
  824. query: "",
  825. searchedQuery: "",
  826. page: 0,
  827. count: 0,
  828. resultsLeft: 0,
  829. results: []
  830. },
  831. featuredPlaylists: []
  832. };
  833. },
  834. computed: {
  835. resultsLeftCount() {
  836. return this.search.count - this.search.results.length;
  837. },
  838. nextPageResultsCount() {
  839. return Math.min(this.search.pageSize, this.resultsLeftCount);
  840. },
  841. ...mapState({
  842. loggedIn: state => state.user.auth.loggedIn,
  843. role: state => state.user.auth.role,
  844. userId: state => state.user.auth.userId,
  845. partyPlaylists: state => state.station.partyPlaylists
  846. }),
  847. ...mapState("modals/manageStation", {
  848. parentTab: state => state.tab,
  849. originalStation: state => state.originalStation,
  850. station: state => state.station,
  851. includedPlaylists: state => state.includedPlaylists,
  852. excludedPlaylists: state => state.excludedPlaylists,
  853. songsList: state => state.songsList
  854. }),
  855. ...mapGetters({
  856. socket: "websockets/getSocket"
  857. })
  858. },
  859. watch: {
  860. // eslint-disable-next-line func-names
  861. parentTab(value) {
  862. if (value === "playlists") {
  863. if (this.tab === "included" && this.isPartyMode()) {
  864. this.showTab("party");
  865. } else if (this.tab === "party" && this.isPlaylistMode()) {
  866. this.showTab("included");
  867. }
  868. }
  869. }
  870. },
  871. mounted() {
  872. if (this.station.type === "community" && this.station.partyMode)
  873. this.showTab("search");
  874. ws.onConnect(this.init);
  875. },
  876. methods: {
  877. init() {
  878. this.socket.dispatch("playlists.indexMyPlaylists", true, res => {
  879. if (res.status === "success")
  880. this.setPlaylists(res.data.playlists);
  881. this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
  882. });
  883. this.socket.dispatch("playlists.indexFeaturedPlaylists", res => {
  884. if (res.status === "success")
  885. this.featuredPlaylists = res.data.playlists;
  886. this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
  887. });
  888. this.socket.dispatch(
  889. `stations.getStationIncludedPlaylistsById`,
  890. this.station._id,
  891. res => {
  892. if (res.status === "success") {
  893. this.station.includedPlaylists = res.data.playlists;
  894. this.originalStation.includedPlaylists =
  895. res.data.playlists;
  896. }
  897. }
  898. );
  899. this.socket.dispatch(
  900. `stations.getStationExcludedPlaylistsById`,
  901. this.station._id,
  902. res => {
  903. if (res.status === "success") {
  904. this.station.excludedPlaylists = res.data.playlists;
  905. this.originalStation.excludedPlaylists =
  906. res.data.playlists;
  907. }
  908. }
  909. );
  910. },
  911. showTab(tab) {
  912. this.$refs[`${tab}-tab`].scrollIntoView({ block: "nearest" });
  913. this.tab = tab;
  914. },
  915. isOwner() {
  916. return (
  917. this.loggedIn &&
  918. this.station &&
  919. this.userId === this.station.owner
  920. );
  921. },
  922. isAdmin() {
  923. return this.loggedIn && this.role === "admin";
  924. },
  925. isOwnerOrAdmin() {
  926. return this.isOwner() || this.isAdmin();
  927. },
  928. isPartyMode() {
  929. return (
  930. this.station &&
  931. this.station.type === "community" &&
  932. this.station.partyMode
  933. );
  934. },
  935. isAllowedToParty() {
  936. return (
  937. this.station &&
  938. this.isPartyMode() &&
  939. (!this.station.locked || this.isOwnerOrAdmin()) &&
  940. this.loggedIn
  941. );
  942. },
  943. isPlaylistMode() {
  944. return this.station && !this.isPartyMode();
  945. },
  946. showPlaylist(playlistId) {
  947. this.editPlaylist(playlistId);
  948. this.openModal("editPlaylist");
  949. },
  950. selectPartyPlaylist(playlist) {
  951. if (!this.isSelected(playlist.id)) {
  952. this.partyPlaylists.push(playlist);
  953. this.addPartyPlaylistSongToQueue();
  954. new Toast(
  955. "Successfully selected playlist to auto request songs."
  956. );
  957. } else {
  958. new Toast("Error: Playlist already selected.");
  959. }
  960. },
  961. includePlaylist(playlist) {
  962. this.socket.dispatch(
  963. "stations.includePlaylist",
  964. this.station._id,
  965. playlist._id,
  966. res => {
  967. new Toast(res.message);
  968. }
  969. );
  970. },
  971. deselectPartyPlaylist(id) {
  972. return new Promise(resolve => {
  973. let selected = false;
  974. this.partyPlaylists.forEach((playlist, index) => {
  975. if (playlist._id === id) {
  976. selected = true;
  977. this.partyPlaylists.splice(index, 1);
  978. }
  979. });
  980. if (selected) {
  981. new Toast("Successfully deselected playlist.");
  982. resolve();
  983. } else {
  984. new Toast("Playlist not selected.");
  985. resolve();
  986. }
  987. });
  988. },
  989. removeIncludedPlaylist(id) {
  990. return new Promise(resolve => {
  991. this.socket.dispatch(
  992. "stations.removeIncludedPlaylist",
  993. this.station._id,
  994. id,
  995. res => {
  996. new Toast(res.message);
  997. resolve();
  998. }
  999. );
  1000. });
  1001. },
  1002. removeExcludedPlaylist(id) {
  1003. return new Promise(resolve => {
  1004. this.socket.dispatch(
  1005. "stations.removeExcludedPlaylist",
  1006. this.station._id,
  1007. id,
  1008. res => {
  1009. new Toast(res.message);
  1010. resolve();
  1011. }
  1012. );
  1013. });
  1014. },
  1015. isSelected(id) {
  1016. let selected = false;
  1017. this.partyPlaylists.forEach(playlist => {
  1018. if (playlist._id === id) selected = true;
  1019. });
  1020. return selected;
  1021. },
  1022. isIncluded(id) {
  1023. let included = false;
  1024. this.includedPlaylists.forEach(playlist => {
  1025. if (playlist._id === id) included = true;
  1026. });
  1027. return included;
  1028. },
  1029. isExcluded(id) {
  1030. let selected = false;
  1031. this.excludedPlaylists.forEach(playlist => {
  1032. if (playlist._id === id) selected = true;
  1033. });
  1034. return selected;
  1035. },
  1036. searchForPlaylists(page) {
  1037. if (
  1038. this.search.page >= page ||
  1039. this.search.searchedQuery !== this.search.query
  1040. ) {
  1041. this.search.results = [];
  1042. this.search.page = 0;
  1043. this.search.count = 0;
  1044. this.search.resultsLeft = 0;
  1045. this.search.pageSize = 0;
  1046. }
  1047. const { query } = this.search;
  1048. const action =
  1049. this.station.type === "official"
  1050. ? "playlists.searchOfficial"
  1051. : "playlists.searchCommunity";
  1052. this.search.searchedQuery = this.search.query;
  1053. this.socket.dispatch(action, query, page, res => {
  1054. const { data } = res;
  1055. if (res.status === "success") {
  1056. const { count, pageSize, playlists } = data;
  1057. this.search.results = [
  1058. ...this.search.results,
  1059. ...playlists
  1060. ];
  1061. this.search.page = page;
  1062. this.search.count = count;
  1063. this.search.resultsLeft =
  1064. count - this.search.results.length;
  1065. this.search.pageSize = pageSize;
  1066. } else if (res.status === "error") {
  1067. this.search.results = [];
  1068. this.search.page = 0;
  1069. this.search.count = 0;
  1070. this.search.resultsLeft = 0;
  1071. this.search.pageSize = 0;
  1072. new Toast(res.message);
  1073. }
  1074. });
  1075. },
  1076. async blacklistPlaylist(id) {
  1077. if (this.isIncluded(id)) await this.removeIncludedPlaylist(id);
  1078. this.socket.dispatch(
  1079. "stations.excludePlaylist",
  1080. this.station._id,
  1081. id,
  1082. res => {
  1083. new Toast(res.message);
  1084. }
  1085. );
  1086. },
  1087. addPartyPlaylistSongToQueue() {
  1088. if (
  1089. this.station.type === "community" &&
  1090. this.station.partyMode === true &&
  1091. this.songsList.length < 50 &&
  1092. this.songsList.filter(
  1093. queueSong => queueSong.requestedBy === this.userId
  1094. ).length < 3 &&
  1095. this.partyPlaylists
  1096. ) {
  1097. const selectedPlaylist =
  1098. this.partyPlaylists[
  1099. Math.floor(Math.random() * this.partyPlaylists.length)
  1100. ];
  1101. if (selectedPlaylist._id && selectedPlaylist.songs.length > 0) {
  1102. const selectedSong =
  1103. selectedPlaylist.songs[
  1104. Math.floor(
  1105. Math.random() * selectedPlaylist.songs.length
  1106. )
  1107. ];
  1108. if (selectedSong.youtubeId) {
  1109. this.socket.dispatch(
  1110. "stations.addToQueue",
  1111. this.station._id,
  1112. selectedSong.youtubeId,
  1113. data => {
  1114. if (data.status !== "success")
  1115. this.addPartyPlaylistSongToQueue();
  1116. }
  1117. );
  1118. }
  1119. }
  1120. }
  1121. },
  1122. ...mapActions("station", ["updatePartyPlaylists"]),
  1123. ...mapActions("modalVisibility", ["openModal"]),
  1124. ...mapActions("user/playlists", ["editPlaylist", "setPlaylists"])
  1125. }
  1126. };
  1127. </script>
  1128. <style lang="scss" scoped>
  1129. .night-mode {
  1130. .tabs-container .tab-selection .button {
  1131. background: var(--dark-grey) !important;
  1132. color: var(--white) !important;
  1133. }
  1134. }
  1135. .excluded-icon {
  1136. color: var(--red);
  1137. }
  1138. .included-icon {
  1139. color: var(--green);
  1140. }
  1141. .selected-icon {
  1142. color: var(--purple);
  1143. }
  1144. .station-playlists {
  1145. .tabs-container {
  1146. .tab-selection {
  1147. display: flex;
  1148. overflow-x: auto;
  1149. .button {
  1150. border-radius: 0;
  1151. border: 0;
  1152. text-transform: uppercase;
  1153. font-size: 14px;
  1154. color: var(--dark-grey-3);
  1155. background-color: var(--light-grey-2);
  1156. flex-grow: 1;
  1157. height: 32px;
  1158. &:not(:first-of-type) {
  1159. margin-left: 5px;
  1160. }
  1161. }
  1162. .selected {
  1163. background-color: var(--primary-color) !important;
  1164. color: var(--white) !important;
  1165. font-weight: 600;
  1166. }
  1167. }
  1168. .tab {
  1169. padding: 15px 0;
  1170. border-radius: 0;
  1171. .playlist-item:not(:last-of-type),
  1172. .item.item-draggable:not(:last-of-type) {
  1173. margin-bottom: 10px;
  1174. }
  1175. .load-more-button {
  1176. width: 100%;
  1177. margin-top: 10px;
  1178. }
  1179. }
  1180. }
  1181. }
  1182. .draggable-list-transition-move {
  1183. transition: transform 0.5s;
  1184. }
  1185. .draggable-list-ghost {
  1186. opacity: 0.5;
  1187. filter: brightness(95%);
  1188. }
  1189. </style>