ImportAlbum.vue 22 KB

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