ImportAlbum.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932
  1. <template>
  2. <div>
  3. <modal title="Import Album" class="import-album-modal">
  4. <template #body>
  5. <div class="search-discogs-album">
  6. <p class="control is-expanded">
  7. <label class="label">Search query</label>
  8. <input
  9. class="input"
  10. type="text"
  11. ref="discogs-input"
  12. v-model="discogsQuery"
  13. @keyup.enter="searchDiscogsForPage(1)"
  14. @change="onDiscogsQueryChange"
  15. v-focus
  16. />
  17. </p>
  18. <button
  19. class="button is-fullwidth is-info"
  20. @click="searchDiscogsForPage(1)"
  21. >
  22. Search
  23. </button>
  24. <button
  25. class="button is-fullwidth is-danger"
  26. @click="clearDiscogsResults()"
  27. >
  28. Clear
  29. </button>
  30. <label class="label" v-if="discogs.apiResults.length > 0"
  31. >API results</label
  32. >
  33. <div
  34. class="api-results-container"
  35. v-if="discogs.apiResults.length > 0"
  36. >
  37. <div
  38. class="api-result"
  39. v-for="(result, index) in discogs.apiResults"
  40. :key="result.album.id"
  41. tabindex="0"
  42. @keydown.space.prevent
  43. @keyup.enter="toggleAPIResult(index)"
  44. >
  45. <div class="top-container">
  46. <img :src="result.album.albumArt" />
  47. <div class="right-container">
  48. <p class="album-title">
  49. {{ result.album.title }}
  50. </p>
  51. <div class="bottom-row">
  52. <img
  53. src="/assets/arrow_up.svg"
  54. v-if="result.expanded"
  55. @click="toggleAPIResult(index)"
  56. />
  57. <img
  58. src="/assets/arrow_down.svg"
  59. v-if="!result.expanded"
  60. @click="toggleAPIResult(index)"
  61. />
  62. <p class="type-year">
  63. <span>{{ result.album.type }}</span>
  64. <span>{{ result.album.year }}</span>
  65. </p>
  66. </div>
  67. </div>
  68. </div>
  69. <div
  70. class="bottom-container"
  71. v-if="result.expanded"
  72. >
  73. <p class="bottom-container-field">
  74. Artists:
  75. <span>{{
  76. result.album.artists.join(", ")
  77. }}</span>
  78. </p>
  79. <p class="bottom-container-field">
  80. Genres:
  81. <span>{{
  82. result.album.genres.join(", ")
  83. }}</span>
  84. </p>
  85. <p class="bottom-container-field">
  86. Data quality:
  87. <span>{{ result.dataQuality }}</span>
  88. </p>
  89. <button
  90. class="button is-primary"
  91. @click="selectAlbum(result)"
  92. >
  93. Import album
  94. </button>
  95. <div class="tracks">
  96. <div
  97. class="track"
  98. v-for="track in result.tracks"
  99. :key="
  100. `${track.position}-${track.title}`
  101. "
  102. >
  103. <span>{{ track.position }}.</span>
  104. <p>{{ track.title }}</p>
  105. </div>
  106. </div>
  107. </div>
  108. </div>
  109. </div>
  110. <button
  111. v-if="
  112. discogs.apiResults.length > 0 &&
  113. !discogs.disableLoadMore &&
  114. discogs.page < discogs.pages
  115. "
  116. class="button is-fullwidth is-info discogs-load-more"
  117. @click="loadNextDiscogsPage()"
  118. >
  119. Load more...
  120. </button>
  121. </div>
  122. <div
  123. class="discogs-album"
  124. v-if="discogsAlbum && discogsAlbum.album"
  125. >
  126. <div class="top-container">
  127. <img :src="discogsAlbum.album.albumArt" />
  128. <div class="right-container">
  129. <p class="album-title">
  130. {{ discogsAlbum.album.title }}
  131. </p>
  132. <div class="bottom-row">
  133. <img
  134. src="/assets/arrow_up.svg"
  135. v-if="discogsAlbum.expanded"
  136. @click="toggleDiscogsAlbum()"
  137. />
  138. <img
  139. src="/assets/arrow_down.svg"
  140. v-if="!discogsAlbum.expanded"
  141. @click="toggleDiscogsAlbum()"
  142. />
  143. <p class="type-year">
  144. <span>{{ discogsAlbum.album.type }}</span>
  145. <span>{{ discogsAlbum.album.year }}</span>
  146. </p>
  147. </div>
  148. </div>
  149. </div>
  150. <div class="bottom-container" v-if="discogsAlbum.expanded">
  151. <p class="bottom-container-field">
  152. Artists:
  153. <span>{{
  154. discogsAlbum.album.artists.join(", ")
  155. }}</span>
  156. </p>
  157. <p class="bottom-container-field">
  158. Genres:
  159. <span>{{
  160. discogsAlbum.album.genres.join(", ")
  161. }}</span>
  162. </p>
  163. <p class="bottom-container-field">
  164. Data quality:
  165. <span>{{ discogsAlbum.dataQuality }}</span>
  166. </p>
  167. <div class="tracks">
  168. <div
  169. class="track"
  170. tabindex="0"
  171. v-for="track in discogsAlbum.tracks"
  172. :key="`${track.position}-${track.title}`"
  173. >
  174. <span>{{ track.position }}.</span>
  175. <p>{{ track.title }}</p>
  176. </div>
  177. </div>
  178. </div>
  179. </div>
  180. <div class="break"></div>
  181. <div
  182. class="import-youtube-playlist"
  183. v-if="discogsAlbum && discogsAlbum.album"
  184. >
  185. <p class="control is-expanded">
  186. <input
  187. class="input"
  188. type="text"
  189. placeholder="Enter YouTube Playlist URL here..."
  190. v-model="search.playlist.query"
  191. @keyup.enter="importPlaylist()"
  192. />
  193. </p>
  194. <button
  195. class="button is-fullwidth is-info"
  196. @click="importPlaylist()"
  197. >
  198. <i class="material-icons icon-with-button">publish</i
  199. >Import
  200. </button>
  201. <button
  202. class="button is-fullwidth is-danger"
  203. @click="resetTrackSongs()"
  204. >
  205. Reset
  206. </button>
  207. <draggable
  208. v-if="playlistSongs.length > 0"
  209. group="songs"
  210. v-model="playlistSongs"
  211. item-key="_id"
  212. @start="drag = true"
  213. @end="drag = false"
  214. @change="log"
  215. >
  216. <template #item="{element}">
  217. <song-item
  218. :key="`playlist-song-${element._id}`"
  219. :song="element"
  220. >
  221. </song-item>
  222. </template>
  223. </draggable>
  224. </div>
  225. <div
  226. class="track-boxes"
  227. v-if="discogsAlbum && discogsAlbum.album"
  228. >
  229. <div
  230. class="track-box"
  231. v-for="(track, index) in discogsAlbum.tracks"
  232. :key="`${track.position}-${track.title}`"
  233. >
  234. <div class="track-position-title">
  235. <span>{{ track.position }}.</span>
  236. <p>{{ track.title }}</p>
  237. </div>
  238. <draggable
  239. class="track-box-songs-drag-area"
  240. group="songs"
  241. v-model="trackSongs[index]"
  242. item-key="_id"
  243. @start="drag = true"
  244. @end="drag = false"
  245. @change="log"
  246. >
  247. <template #item="{element}">
  248. <song-item
  249. :key="`track-song-${element._id}`"
  250. :song="element"
  251. >
  252. </song-item>
  253. </template>
  254. </draggable>
  255. </div>
  256. </div>
  257. </template>
  258. <template #footer>
  259. <button class="button is-primary" @click="tryToAutoMove()">
  260. Try to auto move
  261. </button>
  262. <button class="button is-primary" @click="editSongs()">
  263. Edit songs
  264. </button>
  265. </template>
  266. </modal>
  267. </div>
  268. </template>
  269. <script>
  270. import { mapState, mapGetters, mapActions } from "vuex";
  271. import draggable from "vuedraggable";
  272. import Toast from "toasters";
  273. import Modal from "../Modal.vue";
  274. import SongItem from "../SongItem.vue";
  275. export default {
  276. components: { Modal, SongItem, draggable },
  277. props: {
  278. // songType: { type: String, default: null },
  279. sector: { type: String, default: "admin" }
  280. },
  281. data() {
  282. return {
  283. stuff: false,
  284. isImportingPlaylist: false,
  285. trackSongs: [],
  286. songsToEdit: [],
  287. currentEditSongIndex: 0,
  288. search: {
  289. playlist: {
  290. query: ""
  291. }
  292. },
  293. discogsQuery: "",
  294. discogs: {
  295. apiResults: [],
  296. page: 1,
  297. pages: 1,
  298. disableLoadMore: false
  299. }
  300. };
  301. },
  302. computed: {
  303. playlistSongs: {
  304. get() {
  305. return this.$store.state.modals.importAlbum.playlistSongs;
  306. },
  307. set(playlistSongs) {
  308. this.$store.commit(
  309. "modals/importAlbum/updatePlaylistSongs",
  310. playlistSongs
  311. );
  312. }
  313. },
  314. ...mapState("modals/importAlbum", {
  315. discogsAlbum: state => state.discogsAlbum,
  316. editingSongs: state => state.editingSongs
  317. }),
  318. ...mapState("modalVisibility", {
  319. modals: state => state.modals
  320. }),
  321. ...mapGetters({
  322. socket: "websockets/getSocket"
  323. })
  324. },
  325. watch: {
  326. /* eslint-disable */
  327. "modals.editSong": function(value) {
  328. if (!value) this.editNextSong();
  329. }
  330. /* eslint-enable */
  331. },
  332. beforeUnmount() {
  333. this.selectDiscogsAlbum({});
  334. this.setPlaylistSongs([]);
  335. },
  336. methods: {
  337. editSongs() {
  338. this.updateEditingSongs(true);
  339. this.songsToEdit = [];
  340. this.trackSongs.forEach((songs, index) => {
  341. songs.forEach(song => {
  342. const discogsAlbum = JSON.parse(
  343. JSON.stringify(this.discogsAlbum)
  344. );
  345. discogsAlbum.track = discogsAlbum.tracks[index];
  346. delete discogsAlbum.tracks;
  347. delete discogsAlbum.expanded;
  348. delete discogsAlbum.gotMoreInfo;
  349. this.songsToEdit.push({
  350. songId: song._id,
  351. discogs: discogsAlbum
  352. });
  353. });
  354. });
  355. this.editNextSong();
  356. },
  357. editNextSong() {
  358. if (this.editingSongs) {
  359. setTimeout(() => {
  360. this.editSong({
  361. _id: this.songsToEdit[this.currentEditSongIndex].songId,
  362. discogs: this.songsToEdit[this.currentEditSongIndex]
  363. .discogs
  364. });
  365. this.currentEditSongIndex += 1;
  366. this.openModal("editSong");
  367. }, 500);
  368. }
  369. },
  370. log(evt) {
  371. window.console.log(evt);
  372. },
  373. importPlaylist() {
  374. if (this.isImportingPlaylist)
  375. return new Toast("A playlist is already importing.");
  376. this.isImportingPlaylist = true;
  377. // import query is blank
  378. if (!this.search.playlist.query)
  379. return new Toast("Please enter a YouTube playlist URL.");
  380. const regex = new RegExp(`[\\?&]list=([^&#]*)`);
  381. const splitQuery = regex.exec(this.search.playlist.query);
  382. if (!splitQuery) {
  383. return new Toast({
  384. content: "Please enter a valid YouTube playlist URL.",
  385. timeout: 4000
  386. });
  387. }
  388. // don't give starting import message instantly in case of instant error
  389. setTimeout(() => {
  390. if (this.isImportingPlaylist) {
  391. new Toast(
  392. "Starting to import your playlist. This can take some time to do."
  393. );
  394. }
  395. }, 750);
  396. return this.socket.dispatch(
  397. "songs.requestSet",
  398. this.search.playlist.query,
  399. false,
  400. true,
  401. res => {
  402. this.isImportingPlaylist = false;
  403. const songs = res.songs.filter(
  404. song => song.status !== "verified"
  405. );
  406. const songsAlreadyVerified =
  407. res.songs.length - songs.length;
  408. this.setPlaylistSongs(songs);
  409. this.trackSongs = this.discogsAlbum.tracks.map(() => []);
  410. this.tryToAutoMove();
  411. if (songsAlreadyVerified > 0)
  412. new Toast(
  413. `${songsAlreadyVerified} songs were already verified, skipping those.`
  414. );
  415. return new Toast({ content: res.message, timeout: 20000 });
  416. }
  417. );
  418. },
  419. tryToAutoMove() {
  420. const { tracks } = this.discogsAlbum;
  421. const { trackSongs } = this;
  422. const playlistSongs = JSON.parse(
  423. JSON.stringify(this.playlistSongs)
  424. );
  425. tracks.forEach((track, index) => {
  426. playlistSongs.forEach(playlistSong => {
  427. if (
  428. playlistSong.title
  429. .toLowerCase()
  430. .trim()
  431. .indexOf(track.title.toLowerCase().trim()) !== -1
  432. ) {
  433. playlistSongs.splice(
  434. playlistSongs.indexOf(playlistSong),
  435. 1
  436. );
  437. trackSongs[index].push(playlistSong);
  438. }
  439. });
  440. });
  441. this.updatePlaylistSongs(playlistSongs);
  442. },
  443. resetTrackSongs() {
  444. this.resetPlaylistSongs();
  445. this.trackSongs = this.discogsAlbum.tracks.map(() => []);
  446. },
  447. selectAlbum(result) {
  448. this.selectDiscogsAlbum(result);
  449. this.clearDiscogsResults();
  450. },
  451. toggleAPIResult(index) {
  452. const apiResult = this.discogs.apiResults[index];
  453. if (apiResult.expanded === true) apiResult.expanded = false;
  454. else if (apiResult.gotMoreInfo === true) apiResult.expanded = true;
  455. else {
  456. fetch(apiResult.album.resourceUrl)
  457. .then(response => {
  458. return response.json();
  459. })
  460. .then(data => {
  461. apiResult.album.artists = [];
  462. apiResult.album.artistIds = [];
  463. const artistRegex = new RegExp(" \\([0-9]+\\)$");
  464. apiResult.dataQuality = data.data_quality;
  465. data.artists.forEach(artist => {
  466. apiResult.album.artists.push(
  467. artist.name.replace(artistRegex, "")
  468. );
  469. apiResult.album.artistIds.push(artist.id);
  470. });
  471. apiResult.tracks = data.tracklist.map(track => {
  472. return {
  473. position: track.position,
  474. title: track.title
  475. };
  476. });
  477. apiResult.expanded = true;
  478. apiResult.gotMoreInfo = true;
  479. });
  480. }
  481. },
  482. clearDiscogsResults() {
  483. this.discogs.apiResults = [];
  484. this.discogs.page = 1;
  485. this.discogs.pages = 1;
  486. this.discogs.disableLoadMore = false;
  487. },
  488. searchDiscogsForPage(page) {
  489. const query = this.discogsQuery;
  490. this.socket.dispatch("apis.searchDiscogs", query, page, res => {
  491. if (res.status === "success") {
  492. if (page === 1)
  493. new Toast(
  494. `Successfully searched. Got ${res.data.results.length} results.`
  495. );
  496. else
  497. new Toast(
  498. `Successfully got ${res.data.results.length} more results.`
  499. );
  500. if (page === 1) {
  501. this.discogs.apiResults = [];
  502. }
  503. this.discogs.pages = res.data.pages;
  504. this.discogs.apiResults = this.discogs.apiResults.concat(
  505. res.data.results.map(result => {
  506. const type =
  507. result.type.charAt(0).toUpperCase() +
  508. result.type.slice(1);
  509. return {
  510. expanded: false,
  511. gotMoreInfo: false,
  512. album: {
  513. id: result.id,
  514. title: result.title,
  515. type,
  516. year: result.year,
  517. genres: result.genre,
  518. albumArt: result.cover_image,
  519. resourceUrl: result.resource_url
  520. }
  521. };
  522. })
  523. );
  524. this.discogs.page = page;
  525. this.discogs.disableLoadMore = false;
  526. } else new Toast(res.message);
  527. });
  528. },
  529. loadNextDiscogsPage() {
  530. this.discogs.disableLoadMore = true;
  531. this.searchDiscogsForPage(this.discogs.page + 1);
  532. },
  533. onDiscogsQueryChange() {
  534. this.discogs.page = 1;
  535. this.discogs.pages = 1;
  536. this.discogs.apiResults = [];
  537. this.discogs.disableLoadMore = false;
  538. },
  539. ...mapActions("modals/importAlbum", [
  540. "toggleDiscogsAlbum",
  541. "setPlaylistSongs",
  542. "updatePlaylistSongs",
  543. "selectDiscogsAlbum",
  544. "updateEditingSongs",
  545. "resetPlaylistSongs"
  546. ]),
  547. ...mapActions("modals/editSong", ["editSong"]),
  548. ...mapActions("modalVisibility", ["closeModal", "openModal"])
  549. }
  550. };
  551. </script>
  552. <style lang="scss">
  553. .import-album-modal {
  554. .modal-card-title {
  555. text-align: center;
  556. margin-left: 24px;
  557. }
  558. // .import-album-modal-body {
  559. // display: flex;
  560. // flex-direction: row;
  561. // flex-wrap: wrap;
  562. // justify-content: space-evenly;
  563. // }
  564. .modal-card {
  565. width: 100%;
  566. height: 100%;
  567. .modal-card-body {
  568. padding: 16px;
  569. display: flex;
  570. flex-direction: row;
  571. flex-wrap: wrap;
  572. justify-content: space-evenly;
  573. }
  574. .modal-card-foot {
  575. .button {
  576. margin: 0;
  577. }
  578. div div {
  579. margin-right: 5px;
  580. }
  581. .right {
  582. display: flex;
  583. margin-left: auto;
  584. margin-right: 0;
  585. }
  586. }
  587. }
  588. }
  589. </style>
  590. <style lang="scss" scoped>
  591. .break {
  592. flex-basis: 100%;
  593. height: 0;
  594. border: 1px solid var(--dark-grey);
  595. margin-top: 16px;
  596. margin-bottom: 16px;
  597. }
  598. .search-discogs-album {
  599. width: 376px;
  600. background-color: var(--light-grey);
  601. border: 1px rgba(163, 224, 255, 0.75) solid;
  602. border-radius: 5px;
  603. padding: 16px;
  604. overflow: auto;
  605. height: 100%;
  606. > label {
  607. margin-top: 12px;
  608. }
  609. .top-container {
  610. display: flex;
  611. img {
  612. height: 85px;
  613. width: 85px;
  614. }
  615. .right-container {
  616. padding: 8px;
  617. display: flex;
  618. flex-direction: column;
  619. flex: 1;
  620. .album-title {
  621. flex: 1;
  622. font-weight: 600;
  623. }
  624. .bottom-row {
  625. display: flex;
  626. flex-flow: row;
  627. line-height: 15px;
  628. img {
  629. height: 15px;
  630. align-self: end;
  631. flex: 1;
  632. user-select: none;
  633. -moz-user-select: none;
  634. -ms-user-select: none;
  635. -webkit-user-select: none;
  636. cursor: pointer;
  637. }
  638. p {
  639. text-align: right;
  640. }
  641. .type-year {
  642. font-size: 13px;
  643. align-self: end;
  644. }
  645. }
  646. }
  647. }
  648. .bottom-container {
  649. padding: 12px;
  650. .bottom-container-field {
  651. line-height: 16px;
  652. margin-bottom: 8px;
  653. font-weight: 600;
  654. span {
  655. font-weight: 400;
  656. }
  657. }
  658. .bottom-container-field:last-of-type {
  659. margin-bottom: 8px;
  660. }
  661. }
  662. .api-result {
  663. background-color: var(--white);
  664. border: 0.5px solid var(--primary-color);
  665. border-radius: 5px;
  666. margin-bottom: 16px;
  667. }
  668. button {
  669. &:focus,
  670. &:hover {
  671. filter: contrast(0.75);
  672. }
  673. }
  674. .tracks {
  675. margin-top: 12px;
  676. .track:first-child {
  677. margin-top: 0;
  678. border-radius: 3px 3px 0 0;
  679. }
  680. .track:last-child {
  681. border-radius: 0 0 3px 3px;
  682. }
  683. .track {
  684. border: 0.5px solid var(--black);
  685. margin-top: -1px;
  686. line-height: 16px;
  687. display: flex;
  688. span {
  689. font-weight: 600;
  690. display: inline-block;
  691. margin-top: 7px;
  692. margin-bottom: 7px;
  693. margin-left: 7px;
  694. }
  695. p {
  696. display: inline-block;
  697. margin: 7px;
  698. flex: 1;
  699. }
  700. }
  701. }
  702. .discogs-load-more {
  703. margin-bottom: 8px;
  704. }
  705. }
  706. .discogs-album {
  707. width: 376px;
  708. background-color: var(--light-grey);
  709. border: 1px rgba(163, 224, 255, 0.75) solid;
  710. border-radius: 5px;
  711. padding: 16px;
  712. overflow: auto;
  713. height: 100%;
  714. .top-container {
  715. display: flex;
  716. img {
  717. height: 85px;
  718. width: 85px;
  719. }
  720. .right-container {
  721. padding: 8px;
  722. display: flex;
  723. flex-direction: column;
  724. flex: 1;
  725. .album-title {
  726. flex: 1;
  727. font-weight: 600;
  728. }
  729. .bottom-row {
  730. display: flex;
  731. flex-flow: row;
  732. line-height: 15px;
  733. img {
  734. height: 15px;
  735. align-self: end;
  736. flex: 1;
  737. user-select: none;
  738. -moz-user-select: none;
  739. -ms-user-select: none;
  740. -webkit-user-select: none;
  741. cursor: pointer;
  742. }
  743. p {
  744. text-align: right;
  745. }
  746. .type-year {
  747. font-size: 13px;
  748. align-self: end;
  749. }
  750. }
  751. }
  752. }
  753. .bottom-container {
  754. padding: 12px;
  755. .bottom-container-field {
  756. line-height: 16px;
  757. margin-bottom: 8px;
  758. font-weight: 600;
  759. span {
  760. font-weight: 400;
  761. }
  762. }
  763. .bottom-container-field:last-of-type {
  764. margin-bottom: 0;
  765. }
  766. .tracks {
  767. margin-top: 12px;
  768. .track:first-child {
  769. margin-top: 0;
  770. border-radius: 3px 3px 0 0;
  771. }
  772. .track:last-child {
  773. border-radius: 0 0 3px 3px;
  774. }
  775. .track {
  776. border: 0.5px solid var(--black);
  777. margin-top: -1px;
  778. line-height: 16px;
  779. display: flex;
  780. span {
  781. font-weight: 600;
  782. display: inline-block;
  783. margin-top: 7px;
  784. margin-bottom: 7px;
  785. margin-left: 7px;
  786. }
  787. p {
  788. display: inline-block;
  789. margin: 7px;
  790. flex: 1;
  791. }
  792. }
  793. .track:hover,
  794. .track:focus {
  795. background-color: var(--light-grey);
  796. }
  797. }
  798. }
  799. }
  800. .import-youtube-playlist {
  801. width: 376px;
  802. background-color: var(--light-grey);
  803. border: 1px rgba(163, 224, 255, 0.75) solid;
  804. border-radius: 5px;
  805. padding: 16px;
  806. overflow: auto;
  807. height: 100%;
  808. }
  809. .track-boxes {
  810. width: 376px;
  811. background-color: var(--light-grey);
  812. border: 1px rgba(163, 224, 255, 0.75) solid;
  813. border-radius: 5px;
  814. padding: 16px;
  815. overflow: auto;
  816. height: 100%;
  817. .track-box:first-child {
  818. margin-top: 0;
  819. border-radius: 3px 3px 0 0;
  820. }
  821. .track-box:last-child {
  822. border-radius: 0 0 3px 3px;
  823. }
  824. .track-box {
  825. border: 0.5px solid var(--black);
  826. margin-top: -1px;
  827. line-height: 16px;
  828. display: flex;
  829. flex-flow: column;
  830. .track-position-title {
  831. display: flex;
  832. span {
  833. font-weight: 600;
  834. display: inline-block;
  835. margin-top: 7px;
  836. margin-bottom: 7px;
  837. margin-left: 7px;
  838. }
  839. p {
  840. display: inline-block;
  841. margin: 7px;
  842. flex: 1;
  843. }
  844. }
  845. .track-box-songs-drag-area {
  846. flex: 1;
  847. min-height: 100px;
  848. }
  849. }
  850. }
  851. </style>