EditSong.vue 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072
  1. <template>
  2. <div>
  3. <modal title="Edit Song" class="song-modal">
  4. <div slot="body">
  5. <div class="left-section">
  6. <div class="top-section">
  7. <div class="player-section">
  8. <div id="player"></div>
  9. <canvas
  10. id="durationCanvas"
  11. height="20"
  12. width="530"
  13. ></canvas>
  14. <div class="player-footer">
  15. <div class="player-footer-left">
  16. <i
  17. class="material-icons player-play-pause"
  18. v-on:click="settings('play')"
  19. v-on:keyup.enter="settings('play')"
  20. tabindex="0"
  21. v-if="video.paused"
  22. >play_arrow</i
  23. >
  24. <i
  25. class="material-icons player-play-pause"
  26. v-on:click="settings('pause')"
  27. v-on:keyup.enter="settings('pause')"
  28. tabindex="0"
  29. v-if="!video.paused"
  30. >pause</i
  31. >
  32. <i
  33. class="material-icons player-stop"
  34. v-on:click="settings('stop')"
  35. v-on:keyup.enter="settings('stop')"
  36. tabindex="0"
  37. >stop</i
  38. >
  39. <i
  40. class="material-icons player-fast-forward"
  41. v-on:click="
  42. settings('skipToLast10Secs')
  43. "
  44. v-on:keyup.enter="
  45. settings('skipToLast10Secs')
  46. "
  47. tabindex="0"
  48. >fast_forward</i
  49. >
  50. </div>
  51. <div class="player-footer-center">
  52. <img src="/assets/social/youtube.svg" />
  53. <span>
  54. <span>
  55. {{ youtubeVideoCurrentTime }}
  56. </span>
  57. /
  58. <span>
  59. {{ youtubeVideoDuration }}
  60. {{ youtubeVideoNote }}
  61. </span>
  62. </span>
  63. </div>
  64. <div class="player-footer-right">
  65. <input
  66. type="range"
  67. v-model="volumeSliderValue"
  68. min="0"
  69. max="10000"
  70. class="active"
  71. v-on:change="changeVolume()"
  72. v-on:input="changeVolume()"
  73. />
  74. </div>
  75. </div>
  76. </div>
  77. <img
  78. class="thumbnail-preview"
  79. :src="editing.song.thumbnail"
  80. onerror="this.src='/assets/notes-transparent.png'"
  81. />
  82. </div>
  83. <div class="edit-section">
  84. <div class="control is-grouped">
  85. <div class="title-container">
  86. <label class="label">Title</label>
  87. <p class="control has-addons">
  88. <input
  89. class="input"
  90. type="text"
  91. id="title-input"
  92. v-model="editing.song.title"
  93. v-on:keyup.ctrl.alt.68="
  94. getAlbumData('title')
  95. "
  96. />
  97. <button
  98. class="button album-get-button"
  99. v-on:click="getAlbumData('title')"
  100. >
  101. <i class="material-icons">album</i>
  102. </button>
  103. </p>
  104. </div>
  105. <div class="duration-container">
  106. <label class="label">Duration</label>
  107. <p class="control has-addons">
  108. <input
  109. class="input"
  110. type="text"
  111. v-model.number="editing.song.duration"
  112. />
  113. <button
  114. class="button duration-fill-button"
  115. v-on:click="fillDuration()"
  116. >
  117. <i class="material-icons">sync</i>
  118. </button>
  119. </p>
  120. </div>
  121. <div class="skip-duration-container">
  122. <label class="label">Skip duration</label>
  123. <p class="control">
  124. <input
  125. class="input"
  126. type="text"
  127. v-model.number="
  128. editing.song.skipDuration
  129. "
  130. />
  131. </p>
  132. </div>
  133. </div>
  134. <div class="control is-grouped">
  135. <div class="album-art-container">
  136. <label class="label">Album art</label>
  137. <p class="control has-addons">
  138. <input
  139. class="input"
  140. type="text"
  141. v-model="editing.song.thumbnail"
  142. v-on:keyup.ctrl.alt.68="
  143. getAlbumData('albumArt')
  144. "
  145. />
  146. <button
  147. class="button album-get-button"
  148. v-on:click="getAlbumData('albumArt')"
  149. >
  150. <i class="material-icons">album</i>
  151. </button>
  152. </p>
  153. </div>
  154. </div>
  155. <div class="control is-grouped">
  156. <div class="artists-container">
  157. <label class="label">Artists</label>
  158. <p class="control has-addons">
  159. <input
  160. class="input"
  161. type="text"
  162. id="new-artist"
  163. v-model="artistInputValue"
  164. v-on:blur="blurArtistInput()"
  165. v-on:focus="focusArtistInput()"
  166. v-on:keydown="keydownArtistInput()"
  167. v-on:keyup.enter="addTag('artists')"
  168. v-on:keyup.ctrl.alt.68="
  169. getAlbumData('artists')
  170. "
  171. />
  172. <button
  173. class="button album-get-button"
  174. v-on:click="getAlbumData('artists')"
  175. >
  176. <i class="material-icons">album</i>
  177. </button>
  178. <button
  179. class="button is-info add-button"
  180. v-on:click="addTag('artists')"
  181. >
  182. <i class="material-icons">add</i>
  183. </button>
  184. </p>
  185. <div
  186. class="autosuggest-container"
  187. v-if="
  188. (artistInputFocussed ||
  189. artistAutosuggestContainerFocussed) &&
  190. artistAutosuggestItems.length > 0
  191. "
  192. @mouseover="focusArtistContainer()"
  193. @mouseleave="blurArtistContainer()"
  194. >
  195. <span
  196. class="autosuggest-item"
  197. tabindex="0"
  198. v-on:click="
  199. selectArtistAutosuggest(item)
  200. "
  201. v-for="(item,
  202. index) in artistAutosuggestItems"
  203. :key="index"
  204. >{{ item }}</span
  205. >
  206. </div>
  207. <div class="list-container">
  208. <div
  209. class="list-item"
  210. v-for="(artist, index) in editing.song
  211. .artists"
  212. :key="index"
  213. >
  214. <div
  215. class="list-item-circle"
  216. v-on:click="
  217. removeTag('artists', index)
  218. "
  219. >
  220. <i class="material-icons">close</i>
  221. </div>
  222. <p>{{ artist }}</p>
  223. </div>
  224. </div>
  225. </div>
  226. <div class="genres-container">
  227. <label class="label">
  228. <span>Genres</span>
  229. <i
  230. class="material-icons"
  231. @click="toggleGenreHelper"
  232. @dblclick="resetGenreHelper"
  233. >info</i
  234. >
  235. </label>
  236. <p class="control has-addons">
  237. <input
  238. class="input"
  239. type="text"
  240. id="new-genre"
  241. v-model="genreInputValue"
  242. v-on:blur="blurGenreInput()"
  243. v-on:focus="focusGenreInput()"
  244. v-on:keydown="keydownGenreInput()"
  245. v-on:keyup.enter="addTag('genres')"
  246. v-on:keyup.ctrl.alt.68="
  247. getAlbumData('genres')
  248. "
  249. />
  250. <button
  251. class="button album-get-button"
  252. v-on:click="getAlbumData('genres')"
  253. >
  254. <i class="material-icons">album</i>
  255. </button>
  256. <button
  257. class="button is-info add-button"
  258. v-on:click="addTag('genres')"
  259. >
  260. <i class="material-icons">add</i>
  261. </button>
  262. </p>
  263. <div
  264. class="autosuggest-container"
  265. v-if="
  266. (genreInputFocussed ||
  267. genreAutosuggestContainerFocussed) &&
  268. genreAutosuggestItems.length > 0
  269. "
  270. @mouseover="focusGenreContainer()"
  271. @mouseleave="blurGenreContainer()"
  272. >
  273. <span
  274. class="autosuggest-item"
  275. v-on:click="
  276. selectGenreAutosuggest(item)
  277. "
  278. v-for="(item,
  279. index) in genreAutosuggestItems"
  280. :key="index"
  281. >{{ item }}</span
  282. >
  283. </div>
  284. <div class="list-container">
  285. <div
  286. class="list-item"
  287. v-for="(genre, index) in editing.song
  288. .genres"
  289. :key="index"
  290. >
  291. <div
  292. class="list-item-circle"
  293. v-on:click="
  294. removeTag('genres', index)
  295. "
  296. >
  297. <i class="material-icons">close</i>
  298. </div>
  299. <p>{{ genre }}</p>
  300. </div>
  301. </div>
  302. </div>
  303. <div class="youtube-id-container">
  304. <label class="label">YouTube ID</label>
  305. <p class="control">
  306. <input
  307. class="input"
  308. type="text"
  309. v-model="editing.song.songId"
  310. />
  311. </p>
  312. </div>
  313. </div>
  314. </div>
  315. </div>
  316. <div class="right-section">
  317. <div class="api-section">
  318. <div
  319. class="selected-discogs-info"
  320. v-if="!editing.song.discogs"
  321. >
  322. <p class="selected-discogs-info-none">None</p>
  323. </div>
  324. <div
  325. class="selected-discogs-info"
  326. v-if="editing.song.discogs"
  327. >
  328. <div class="top-container">
  329. <img
  330. :src="editing.song.discogs.album.albumArt"
  331. />
  332. <div class="right-container">
  333. <p class="album-title">
  334. {{ editing.song.discogs.album.title }}
  335. </p>
  336. <div class="bottom-row">
  337. <p class="type-year">
  338. <span>{{
  339. editing.song.discogs.album.type
  340. }}</span>
  341. <span>{{
  342. editing.song.discogs.album.year
  343. }}</span>
  344. </p>
  345. </div>
  346. </div>
  347. </div>
  348. <div class="bottom-container">
  349. <p class="bottom-container-field">
  350. Artists:
  351. <span>{{
  352. editing.song.discogs.album.artists.join(
  353. ", "
  354. )
  355. }}</span>
  356. </p>
  357. <p class="bottom-container-field">
  358. Genres:
  359. <span>{{
  360. editing.song.discogs.album.genres.join(
  361. ", "
  362. )
  363. }}</span>
  364. </p>
  365. <p class="bottom-container-field">
  366. Data quality:
  367. <span>{{
  368. editing.song.discogs.dataQuality
  369. }}</span>
  370. </p>
  371. <p class="bottom-container-field">
  372. Track:
  373. <span
  374. >{{
  375. editing.song.discogs.track.position
  376. }}.
  377. {{
  378. editing.song.discogs.track.title
  379. }}</span
  380. >
  381. </p>
  382. </div>
  383. </div>
  384. <p class="control is-expanded">
  385. <label class="label">Search query</label>
  386. <input
  387. class="input"
  388. type="text"
  389. id="discogs-input"
  390. v-model="discogsQuery"
  391. v-on:keyup.enter="searchDiscogsForPage(1)"
  392. @change="onDiscogsQueryChange"
  393. v-focus
  394. />
  395. </p>
  396. <button
  397. class="button is-info is-fullwidth"
  398. v-on:click="searchDiscogsForPage(1)"
  399. >
  400. Search
  401. </button>
  402. <label
  403. class="label"
  404. v-if="discogs.apiResults.length > 0"
  405. >API results</label
  406. >
  407. <div
  408. class="api-results-container"
  409. v-if="discogs.apiResults.length > 0"
  410. >
  411. <div
  412. class="api-result"
  413. v-for="(result, index) in discogs.apiResults"
  414. :key="index"
  415. tabindex="0"
  416. v-on:keydown.prevent.32
  417. v-on:keyup.13="toggleAPIResult(index)"
  418. >
  419. <div class="top-container">
  420. <img :src="result.album.albumArt" />
  421. <div class="right-container">
  422. <p class="album-title">
  423. {{ result.album.title }}
  424. </p>
  425. <div class="bottom-row">
  426. <img
  427. src="/assets/arrow_up.svg"
  428. v-if="result.expanded"
  429. v-on:click="
  430. toggleAPIResult(index)
  431. "
  432. />
  433. <img
  434. src="/assets/arrow_down.svg"
  435. v-if="!result.expanded"
  436. v-on:click="
  437. toggleAPIResult(index)
  438. "
  439. />
  440. <p class="type-year">
  441. <span>{{
  442. result.album.type
  443. }}</span>
  444. <span>{{
  445. result.album.year
  446. }}</span>
  447. </p>
  448. </div>
  449. </div>
  450. </div>
  451. <div
  452. class="bottom-container"
  453. v-if="result.expanded"
  454. >
  455. <p class="bottom-container-field">
  456. Artists:
  457. <span>{{
  458. result.album.artists.join(", ")
  459. }}</span>
  460. </p>
  461. <p class="bottom-container-field">
  462. Genres:
  463. <span>{{
  464. result.album.genres.join(", ")
  465. }}</span>
  466. </p>
  467. <p class="bottom-container-field">
  468. Data quality:
  469. <span>{{ result.dataQuality }}</span>
  470. </p>
  471. <div class="tracks">
  472. <div
  473. class="track"
  474. tabindex="0"
  475. v-for="(track,
  476. trackIndex) in result.tracks"
  477. :key="trackIndex"
  478. v-on:click="
  479. selectTrack(index, trackIndex)
  480. "
  481. v-on:keyup.enter="
  482. selectTrack(index, trackIndex)
  483. "
  484. >
  485. <span>{{ track.position }}.</span>
  486. <p>{{ track.title }}</p>
  487. </div>
  488. </div>
  489. </div>
  490. </div>
  491. </div>
  492. <button
  493. v-if="
  494. discogs.apiResults.length > 0 &&
  495. !discogs.disableLoadMore &&
  496. discogs.page < discogs.pages
  497. "
  498. class="button is-fullwidth is-info discogs-load-more"
  499. @click="loadNextDiscogsPage()"
  500. >
  501. Load more...
  502. </button>
  503. </div>
  504. </div>
  505. </div>
  506. <div slot="footer" class="footer-buttons">
  507. <button
  508. class="button is-success"
  509. v-on:click="save(editing.song, false)"
  510. >
  511. <i class="material-icons save-changes">done</i>
  512. <span>&nbsp;Save</span>
  513. </button>
  514. <button
  515. class="button is-success"
  516. v-on:click="save(editing.song, true)"
  517. >
  518. <i class="material-icons save-changes">done</i>
  519. <span>&nbsp;Save and close</span>
  520. </button>
  521. <button
  522. class="button is-danger"
  523. v-on:click="
  524. closeModal({ sector: 'admin', modal: 'editSong' })
  525. "
  526. >
  527. <span>&nbsp;Close</span>
  528. </button>
  529. </div>
  530. </modal>
  531. <div
  532. id="genre-helper-container"
  533. v-bind:style="{
  534. width: genreHelper.width + 'px',
  535. height: genreHelper.height + 'px',
  536. top: genreHelper.top + 'px',
  537. left: genreHelper.left + 'px'
  538. }"
  539. v-if="genreHelper.shown"
  540. @mousedown="onResizeGenreHelper"
  541. >
  542. <div
  543. class="genre-helper-header"
  544. @mousedown="onDragGenreHelper"
  545. ></div>
  546. <div class="genre-helper-body">
  547. <span>Blues</span><span>Country</span><span>Disco</span
  548. ><span>Funk</span><span>Hip-Hop</span><span>Jazz</span
  549. ><span>Metal</span><span>Oldies</span><span>Other</span
  550. ><span>Pop</span><span>Rap</span><span>Reggae</span
  551. ><span>Rock</span><span>Techno</span><span>Trance</span
  552. ><span>Classical</span><span>Instrumental</span
  553. ><span>House</span><span>Electronic</span
  554. ><span>Christian Rap</span><span>Lo-Fi</span><span>Musical</span
  555. ><span>Rock 'n' Roll</span><span>Opera</span
  556. ><span>Drum & Bass</span><span>Club-House</span
  557. ><span>Indie</span><span>Heavy Metal</span
  558. ><span>Christian rock</span><span>Dubstep</span>
  559. </div>
  560. </div>
  561. </div>
  562. </template>
  563. <script>
  564. import { mapState, mapActions } from "vuex";
  565. import Toast from "toasters";
  566. import io from "../../io";
  567. import keyboardShortcuts from "../../keyboardShortcuts";
  568. import validation from "../../validation";
  569. import Modal from "./Modal.vue";
  570. export default {
  571. components: { Modal },
  572. data() {
  573. return {
  574. discogsQuery: "",
  575. youtubeVideoDuration: 0.0,
  576. youtubeVideoCurrentTime: 0.0,
  577. youtubeVideoNote: "",
  578. useHTTPS: false,
  579. discogs: {
  580. apiResults: [],
  581. page: 1,
  582. pages: 1,
  583. disableLoadMore: false
  584. },
  585. volumeSliderValue: 0,
  586. artistInputValue: "",
  587. genreInputValue: "",
  588. artistInputFocussed: false,
  589. genreInputFocussed: false,
  590. genreAutosuggestContainerFocussed: false,
  591. artistAutosuggestContainerFocussed: false,
  592. keydownArtistInputTimeout: 0,
  593. keydownGenreInputTimeout: 0,
  594. artistAutosuggestItems: [],
  595. genreAutosuggestItems: [],
  596. genreHelper: {
  597. width: 200,
  598. height: 200,
  599. top: 0,
  600. left: 0,
  601. shown: false,
  602. pos1: 0,
  603. pos2: 0,
  604. pos3: 0,
  605. pos4: 0
  606. },
  607. genres: [
  608. "Blues",
  609. "Country",
  610. "Disco",
  611. "Funk",
  612. "Hip-Hop",
  613. "Jazz",
  614. "Metal",
  615. "Oldies",
  616. "Other",
  617. "Pop",
  618. "Rap",
  619. "Reggae",
  620. "Rock",
  621. "Techno",
  622. "Trance",
  623. "Classical",
  624. "Instrumental",
  625. "House",
  626. "Electronic",
  627. "Christian Rap",
  628. "Lo-Fi",
  629. "Musical",
  630. "Rock 'n' Roll",
  631. "Opera",
  632. "Drum & Bass",
  633. "Club-House",
  634. "Indie",
  635. "Heavy Metal",
  636. "Christian rock",
  637. "Dubstep"
  638. ]
  639. };
  640. },
  641. computed: {
  642. ...mapState("admin/songs", {
  643. video: state => state.video,
  644. editing: state => state.editing,
  645. songs: state => state.songs
  646. }),
  647. ...mapState("modals", {
  648. modals: state => state.modals.admin
  649. })
  650. },
  651. watch: {
  652. /* eslint-disable */
  653. "editing.song.duration": function() {
  654. this.drawCanvas();
  655. },
  656. "editing.song.skipDuration": function() {
  657. this.drawCanvas();
  658. }
  659. /* eslint-enable */
  660. },
  661. methods: {
  662. save(songToCopy, close) {
  663. const song = JSON.parse(JSON.stringify(songToCopy));
  664. if (!song.title)
  665. return new Toast({
  666. content: "Please fill in all fields",
  667. timeout: 8000
  668. });
  669. if (!song.thumbnail)
  670. return new Toast({
  671. content: "Please fill in all fields",
  672. timeout: 8000
  673. });
  674. // Duration
  675. if (
  676. Number(song.skipDuration) + Number(song.duration) >
  677. this.youtubeVideoDuration
  678. ) {
  679. return new Toast({
  680. content:
  681. "Duration can't be higher than the length of the video",
  682. timeout: 8000
  683. });
  684. }
  685. // Title
  686. if (!validation.isLength(song.title, 1, 100))
  687. return new Toast({
  688. content: "Title must have between 1 and 100 characters.",
  689. timeout: 8000
  690. });
  691. // Artists
  692. if (song.artists.length < 1 || song.artists.length > 10)
  693. return new Toast({
  694. content:
  695. "Invalid artists. You must have at least 1 artist and a maximum of 10 artists.",
  696. timeout: 8000
  697. });
  698. let error;
  699. song.artists.forEach(artist => {
  700. if (!validation.isLength(artist, 1, 64)) {
  701. error = "Artist must have between 1 and 64 characters.";
  702. return error;
  703. }
  704. if (artist === "NONE") {
  705. error =
  706. 'Invalid artist format. Artists are not allowed to be named "NONE".';
  707. return error;
  708. }
  709. return false;
  710. });
  711. if (error) return new Toast({ content: error, timeout: 8000 });
  712. // Genres
  713. error = undefined;
  714. song.genres.forEach(genre => {
  715. if (!validation.isLength(genre, 1, 32)) {
  716. error = "Genre must have between 1 and 32 characters.";
  717. return error;
  718. }
  719. if (!validation.regex.ascii.test(genre)) {
  720. error =
  721. "Invalid genre format. Only ascii characters are allowed.";
  722. return error;
  723. }
  724. return false;
  725. });
  726. if (song.genres.length < 1 || song.genres.length > 16)
  727. error = "You must have between 1 and 16 genres.";
  728. if (error) return new Toast({ content: error, timeout: 8000 });
  729. // Thumbnail
  730. if (!validation.isLength(song.thumbnail, 1, 256))
  731. return new Toast({
  732. content:
  733. "Thumbnail must have between 8 and 256 characters.",
  734. timeout: 8000
  735. });
  736. if (this.useHTTPS && song.thumbnail.indexOf("https://") !== 0) {
  737. return new Toast({
  738. content: 'Thumbnail must start with "https://".',
  739. timeout: 8000
  740. });
  741. }
  742. if (
  743. !this.useHTTPS &&
  744. song.thumbnail.indexOf("http://") !== 0 &&
  745. song.thumbnail.indexOf("https://") !== 0
  746. ) {
  747. return new Toast({
  748. content: 'Thumbnail must start with "http://".',
  749. timeout: 8000
  750. });
  751. }
  752. return this.socket.emit(
  753. `${this.editing.type}.update`,
  754. song._id,
  755. song,
  756. res => {
  757. new Toast({ content: res.message, timeout: 4000 });
  758. if (res.status === "success") {
  759. this.songs.forEach(originalSong => {
  760. const updatedSong = song;
  761. if (originalSong._id === updatedSong._id) {
  762. Object.keys(originalSong).forEach(n => {
  763. updatedSong[n] = originalSong[n];
  764. return originalSong[n];
  765. });
  766. }
  767. });
  768. }
  769. if (close)
  770. this.closeModal({
  771. sector: "admin",
  772. modal: "editSong"
  773. });
  774. }
  775. );
  776. },
  777. toggleAPIResult(index) {
  778. const apiResult = this.discogs.apiResults[index];
  779. if (apiResult.expanded === true) apiResult.expanded = false;
  780. else if (apiResult.gotMoreInfo === true) apiResult.expanded = true;
  781. else {
  782. fetch(apiResult.album.resourceUrl)
  783. .then(response => {
  784. return response.json();
  785. })
  786. .then(data => {
  787. apiResult.album.artists = [];
  788. apiResult.album.artistIds = [];
  789. const artistRegex = new RegExp(" \\([0-9]+\\)$");
  790. apiResult.dataQuality = data.data_quality;
  791. data.artists.forEach(artist => {
  792. apiResult.album.artists.push(
  793. artist.name.replace(artistRegex, "")
  794. );
  795. apiResult.album.artistIds.push(artist.id);
  796. });
  797. apiResult.tracks = data.tracklist.map(track => {
  798. return {
  799. position: track.position,
  800. title: track.title
  801. };
  802. });
  803. apiResult.expanded = true;
  804. apiResult.gotMoreInfo = true;
  805. });
  806. }
  807. },
  808. fillDuration() {
  809. this.editing.song.duration =
  810. this.youtubeVideoDuration - this.editing.song.skipDuration;
  811. },
  812. getAlbumData(type) {
  813. if (!this.editing.song.discogs) return;
  814. if (type === "title")
  815. this.updateSongField({
  816. field: "title",
  817. value: this.editing.song.discogs.track.title
  818. });
  819. if (type === "albumArt")
  820. this.updateSongField({
  821. field: "thumbnail",
  822. value: this.editing.song.discogs.album.albumArt
  823. });
  824. if (type === "genres")
  825. this.updateSongField({
  826. field: "genres",
  827. value: JSON.parse(
  828. JSON.stringify(this.editing.song.discogs.album.genres)
  829. )
  830. });
  831. if (type === "artists")
  832. this.updateSongField({
  833. field: "artists",
  834. value: JSON.parse(
  835. JSON.stringify(this.editing.song.discogs.album.artists)
  836. )
  837. });
  838. },
  839. searchDiscogsForPage(page) {
  840. const query = this.discogsQuery;
  841. this.socket.emit("apis.searchDiscogs", query, page, res => {
  842. if (res.status === "success") {
  843. if (page === 1)
  844. new Toast({
  845. content: `Successfully searched. Got ${res.results.length} results.`,
  846. timeout: 4000
  847. });
  848. else
  849. new Toast({
  850. content: `Successfully got ${res.results.length} more results.`,
  851. timeout: 4000
  852. });
  853. if (page === 1) {
  854. this.discogs.apiResults = [];
  855. }
  856. this.discogs.pages = res.pages;
  857. this.discogs.apiResults = this.discogs.apiResults.concat(
  858. res.results.map(result => {
  859. const type =
  860. result.type.charAt(0).toUpperCase() +
  861. result.type.slice(1);
  862. return {
  863. expanded: false,
  864. gotMoreInfo: false,
  865. album: {
  866. id: result.id,
  867. title: result.title,
  868. type,
  869. year: result.year,
  870. genres: result.genre,
  871. albumArt: result.cover_image,
  872. resourceUrl: result.resource_url
  873. }
  874. };
  875. })
  876. );
  877. this.discogs.page = page;
  878. this.discogs.disableLoadMore = false;
  879. } else new Toast({ content: res.message, timeout: 8000 });
  880. });
  881. },
  882. loadNextDiscogsPage() {
  883. this.discogs.disableLoadMore = true;
  884. this.searchDiscogsForPage(this.discogs.page + 1);
  885. },
  886. onDiscogsQueryChange() {
  887. this.discogs.page = 1;
  888. this.discogs.pages = 1;
  889. this.discogs.apiResults = [];
  890. this.discogs.disableLoadMore = false;
  891. },
  892. selectTrack(apiResultIndex, trackIndex) {
  893. const apiResult = JSON.parse(
  894. JSON.stringify(this.discogs.apiResults[apiResultIndex])
  895. );
  896. apiResult.track = apiResult.tracks[trackIndex];
  897. delete apiResult.tracks;
  898. delete apiResult.expanded;
  899. delete apiResult.gotMoreInfo;
  900. this.selectDiscogsInfo(apiResult);
  901. },
  902. blurArtistInput() {
  903. this.artistInputFocussed = false;
  904. },
  905. focusArtistInput() {
  906. this.artistInputFocussed = true;
  907. },
  908. blurArtistContainer() {
  909. this.artistAutosuggestContainerFocussed = false;
  910. },
  911. focusArtistContainer() {
  912. this.artistAutosuggestContainerFocussed = true;
  913. },
  914. keydownArtistInput() {
  915. clearTimeout(this.keydownArtistInputTimeout);
  916. this.keydownArtistInputTimeout = setTimeout(() => {
  917. // Do things here to query the artist
  918. }, 1000);
  919. },
  920. selectArtistAutosuggest(value) {
  921. this.artistInputValue = value;
  922. },
  923. blurGenreInput() {
  924. this.genreInputFocussed = false;
  925. },
  926. focusGenreInput() {
  927. this.genreInputFocussed = true;
  928. },
  929. blurGenreContainer() {
  930. this.genreAutosuggestContainerFocussed = false;
  931. },
  932. focusGenreContainer() {
  933. this.genreAutosuggestContainerFocussed = true;
  934. },
  935. keydownGenreInput() {
  936. clearTimeout(this.keydownGenreInputTimeout);
  937. this.keydownGenreInputTimeout = setTimeout(() => {
  938. if (this.genreInputValue.length > 1) {
  939. this.genreAutosuggestItems = this.genres.filter(genre => {
  940. return genre
  941. .toLowerCase()
  942. .startsWith(this.genreInputValue.toLowerCase());
  943. });
  944. } else this.genreAutosuggestItems = [];
  945. }, 1000);
  946. },
  947. selectGenreAutosuggest(value) {
  948. this.genreInputValue = value;
  949. },
  950. settings(type) {
  951. switch (type) {
  952. default:
  953. break;
  954. case "stop":
  955. this.stopVideo();
  956. this.pauseVideo(true);
  957. break;
  958. case "pause":
  959. this.pauseVideo(true);
  960. break;
  961. case "play":
  962. this.pauseVideo(false);
  963. break;
  964. case "skipToLast10Secs":
  965. if (this.video.paused) this.pauseVideo(false);
  966. this.video.player.seekTo(
  967. this.editing.song.duration -
  968. 10 +
  969. this.editing.song.skipDuration
  970. );
  971. break;
  972. }
  973. },
  974. changeVolume() {
  975. const volume = this.volumeSliderValue;
  976. localStorage.setItem("volume", volume / 100);
  977. this.video.player.setVolume(volume / 100);
  978. if (volume > 0) this.video.player.unMute();
  979. },
  980. addTag(type) {
  981. if (type === "genres") {
  982. const genre = document
  983. .getElementById("new-genre")
  984. .value.toLowerCase()
  985. .trim();
  986. if (this.editing.song.genres.indexOf(genre) !== -1)
  987. return new Toast({
  988. content: "Genre already exists",
  989. timeout: 3000
  990. });
  991. if (genre) {
  992. this.editing.song.genres.push(genre);
  993. document.getElementById("new-genre").value = "";
  994. return false;
  995. }
  996. return new Toast({
  997. content: "Genre cannot be empty",
  998. timeout: 3000
  999. });
  1000. }
  1001. if (type === "artists") {
  1002. const artist = document.getElementById("new-artist").value;
  1003. if (this.editing.song.artists.indexOf(artist) !== -1)
  1004. return new Toast({
  1005. content: "Artist already exists",
  1006. timeout: 3000
  1007. });
  1008. if (document.getElementById("new-artist").value !== "") {
  1009. this.editing.song.artists.push(artist);
  1010. document.getElementById("new-artist").value = "";
  1011. return false;
  1012. }
  1013. return new Toast({
  1014. content: "Artist cannot be empty",
  1015. timeout: 3000
  1016. });
  1017. }
  1018. return false;
  1019. },
  1020. removeTag(type, index) {
  1021. if (type === "genres") this.editing.song.genres.splice(index, 1);
  1022. else if (type === "artists")
  1023. this.editing.song.artists.splice(index, 1);
  1024. },
  1025. drawCanvas() {
  1026. const canvasElement = document.getElementById("durationCanvas");
  1027. const ctx = canvasElement.getContext("2d");
  1028. const videoDuration = Number(this.youtubeVideoDuration);
  1029. const skipDuration = Number(this.editing.song.skipDuration);
  1030. const duration = Number(this.editing.song.duration);
  1031. const afterDuration = videoDuration - (skipDuration + duration);
  1032. const width = 530;
  1033. const currentTime = this.video.player.getCurrentTime();
  1034. const widthSkipDuration = (skipDuration / videoDuration) * width;
  1035. const widthDuration = (duration / videoDuration) * width;
  1036. const widthAfterDuration = (afterDuration / videoDuration) * width;
  1037. const widthCurrentTime = (currentTime / videoDuration) * width;
  1038. const skipDurationColor = "#F42003";
  1039. const durationColor = "#03A9F4";
  1040. const afterDurationColor = "#41E841";
  1041. const currentDurationColor = "#3b25e8";
  1042. ctx.fillStyle = skipDurationColor;
  1043. ctx.fillRect(0, 0, widthSkipDuration, 20);
  1044. ctx.fillStyle = durationColor;
  1045. ctx.fillRect(widthSkipDuration, 0, widthDuration, 20);
  1046. ctx.fillStyle = afterDurationColor;
  1047. ctx.fillRect(
  1048. widthSkipDuration + widthDuration,
  1049. 0,
  1050. widthAfterDuration,
  1051. 20
  1052. );
  1053. ctx.fillStyle = currentDurationColor;
  1054. ctx.fillRect(widthCurrentTime, 0, 1, 20);
  1055. },
  1056. onDragGenreHelper(e) {
  1057. const e1 = e || window.event;
  1058. e1.preventDefault();
  1059. this.genreHelper.pos3 = e1.clientX;
  1060. this.genreHelper.pos4 = e1.clientY;
  1061. document.onmousemove = e => {
  1062. const e2 = e || window.event;
  1063. e2.preventDefault();
  1064. // calculate the new cursor position:
  1065. this.genreHelper.pos1 = this.genreHelper.pos3 - e.clientX;
  1066. this.genreHelper.pos2 = this.genreHelper.pos4 - e.clientY;
  1067. this.genreHelper.pos3 = e.clientX;
  1068. this.genreHelper.pos4 = e.clientY;
  1069. // set the element's new position:
  1070. this.genreHelper.top -= this.genreHelper.pos2;
  1071. this.genreHelper.left -= this.genreHelper.pos1;
  1072. };
  1073. document.onmouseup = () => {
  1074. document.onmouseup = null;
  1075. document.onmousemove = null;
  1076. this.saveGenreHelper();
  1077. };
  1078. },
  1079. onResizeGenreHelper(e) {
  1080. if (e.target.id !== "genre-helper-container") return;
  1081. document.onmouseup = () => {
  1082. document.onmouseup = null;
  1083. const { height, width } = e.target.style;
  1084. this.genreHelper.height = Number(
  1085. height
  1086. .split("")
  1087. .splice(0, height.length - 2)
  1088. .join("")
  1089. );
  1090. this.genreHelper.width = Number(
  1091. width
  1092. .split("")
  1093. .splice(0, width.length - 2)
  1094. .join("")
  1095. );
  1096. this.saveGenreHelper();
  1097. };
  1098. },
  1099. toggleGenreHelper() {
  1100. this.genreHelper.shown = !this.genreHelper.shown;
  1101. this.saveGenreHelper();
  1102. },
  1103. resetGenreHelper() {
  1104. this.genreHelper.top = 0;
  1105. this.genreHelper.left = 0;
  1106. this.genreHelper.width = 200;
  1107. this.genreHelper.height = 200;
  1108. this.saveGenreHelper();
  1109. },
  1110. saveGenreHelper() {
  1111. localStorage.setItem(
  1112. "genreHelper",
  1113. JSON.stringify({
  1114. height: this.genreHelper.height,
  1115. width: this.genreHelper.width,
  1116. top: this.genreHelper.top,
  1117. left: this.genreHelper.left,
  1118. shown: this.genreHelper.shown
  1119. })
  1120. );
  1121. },
  1122. ...mapActions("admin/songs", [
  1123. "stopVideo",
  1124. "loadVideoById",
  1125. "pauseVideo",
  1126. "getCurrentTime",
  1127. "editSong",
  1128. "updateSongField",
  1129. "selectDiscogsInfo"
  1130. ]),
  1131. ...mapActions("modals", ["closeModal"])
  1132. },
  1133. mounted() {
  1134. // if (this.modals.editSong = false) this.video.player.stopVideo();
  1135. // this.loadVideoById(
  1136. // this.editing.song.songId,
  1137. // this.editing.song.skipDuration
  1138. // );
  1139. if (localStorage.genreHelper) {
  1140. const genreHelper = JSON.parse(localStorage.getItem("genreHelper"));
  1141. this.genreHelper.height = genreHelper.height;
  1142. this.genreHelper.width = genreHelper.width;
  1143. this.genreHelper.top = genreHelper.top;
  1144. this.genreHelper.left = genreHelper.left;
  1145. this.genreHelper.shown = genreHelper.shown;
  1146. }
  1147. this.discogsQuery = this.editing.song.title;
  1148. lofig.get("cookie.secure").then(useHTTPS => {
  1149. this.useHTTPS = useHTTPS;
  1150. });
  1151. io.getSocket(socket => {
  1152. this.socket = socket;
  1153. });
  1154. this.interval = setInterval(() => {
  1155. if (
  1156. this.editing.song.duration !== -1 &&
  1157. this.video.paused === false &&
  1158. this.playerReady &&
  1159. this.video.player.getCurrentTime() -
  1160. this.editing.song.skipDuration >
  1161. this.editing.song.duration
  1162. ) {
  1163. this.video.paused = false;
  1164. this.video.player.stopVideo();
  1165. this.drawCanvas();
  1166. }
  1167. if (this.playerReady) {
  1168. this.youtubeVideoCurrentTime = this.video.player
  1169. .getCurrentTime()
  1170. .toFixed(3);
  1171. }
  1172. if (this.video.paused === false) this.drawCanvas();
  1173. }, 200);
  1174. this.video.player = new window.YT.Player("player", {
  1175. height: 298,
  1176. width: 530,
  1177. videoId: this.editing.song.songId,
  1178. host: "https://www.youtube-nocookie.com",
  1179. playerVars: {
  1180. controls: 0,
  1181. iv_load_policy: 3,
  1182. rel: 0,
  1183. showinfo: 0,
  1184. autoplay: 1
  1185. },
  1186. startSeconds: this.editing.song.skipDuration,
  1187. events: {
  1188. onReady: () => {
  1189. let volume = parseInt(localStorage.getItem("volume"));
  1190. volume = typeof volume === "number" ? volume : 20;
  1191. console.log(`Seekto: ${this.editing.song.skipDuration}`);
  1192. this.video.player.seekTo(this.editing.song.skipDuration);
  1193. this.video.player.setVolume(volume);
  1194. if (volume > 0) this.video.player.unMute();
  1195. this.youtubeVideoDuration = this.video.player.getDuration();
  1196. this.youtubeVideoNote = "(~)";
  1197. this.playerReady = true;
  1198. this.drawCanvas();
  1199. },
  1200. onStateChange: event => {
  1201. this.drawCanvas();
  1202. if (event.data === 1) {
  1203. if (!this.video.autoPlayed) {
  1204. this.video.autoPlayed = true;
  1205. return this.video.player.stopVideo();
  1206. }
  1207. this.video.paused = false;
  1208. let youtubeDuration = this.video.player.getDuration();
  1209. this.youtubeVideoDuration = youtubeDuration;
  1210. this.youtubeVideoNote = "";
  1211. if (this.editing.song.duration === -1)
  1212. this.editing.song.duration = youtubeDuration;
  1213. youtubeDuration -= this.editing.song.skipDuration;
  1214. if (this.editing.song.duration > youtubeDuration + 1) {
  1215. this.video.player.stopVideo();
  1216. this.video.paused = true;
  1217. return new Toast({
  1218. content:
  1219. "Video can't play. Specified duration is bigger than the YouTube song duration.",
  1220. timeout: 4000
  1221. });
  1222. }
  1223. if (this.editing.song.duration <= 0) {
  1224. this.video.player.stopVideo();
  1225. this.video.paused = true;
  1226. return new Toast({
  1227. content:
  1228. "Video can't play. Specified duration has to be more than 0 seconds.",
  1229. timeout: 4000
  1230. });
  1231. }
  1232. if (
  1233. this.video.player.getCurrentTime() <
  1234. this.editing.song.skipDuration
  1235. ) {
  1236. return this.video.player.seekTo(
  1237. this.editing.song.skipDuration
  1238. );
  1239. }
  1240. } else if (event.data === 2) {
  1241. this.video.paused = true;
  1242. }
  1243. return false;
  1244. }
  1245. }
  1246. });
  1247. let volume = parseFloat(localStorage.getItem("volume"));
  1248. volume =
  1249. typeof volume === "number" && !Number.isNaN(volume) ? volume : 20;
  1250. localStorage.setItem("volume", volume);
  1251. this.volumeSliderValue = volume * 100;
  1252. keyboardShortcuts.registerShortcut("editSong.pauseResumeVideo", {
  1253. keyCode: 101,
  1254. preventDefault: true,
  1255. handler: () => {
  1256. if (this.video.paused) this.settings("play");
  1257. else this.settings("pause");
  1258. }
  1259. });
  1260. keyboardShortcuts.registerShortcut("editSong.stopVideo", {
  1261. keyCode: 101,
  1262. ctrl: true,
  1263. preventDefault: true,
  1264. handler: () => {
  1265. this.settings("stop");
  1266. }
  1267. });
  1268. keyboardShortcuts.registerShortcut("editSong.skipToLast10Secs", {
  1269. keyCode: 102,
  1270. preventDefault: true,
  1271. handler: () => {
  1272. this.settings("skipToLast10Secs");
  1273. }
  1274. });
  1275. keyboardShortcuts.registerShortcut("editSong.lowerVolumeLarge", {
  1276. keyCode: 98,
  1277. preventDefault: true,
  1278. handler: () => {
  1279. this.volumeSliderValue = Math.max(
  1280. 0,
  1281. this.volumeSliderValue - 1000
  1282. );
  1283. this.changeVolume();
  1284. }
  1285. });
  1286. keyboardShortcuts.registerShortcut("editSong.lowerVolumeSmall", {
  1287. keyCode: 98,
  1288. ctrl: true,
  1289. preventDefault: true,
  1290. handler: () => {
  1291. this.volumeSliderValue = Math.max(
  1292. 0,
  1293. this.volumeSliderValue - 100
  1294. );
  1295. this.changeVolume();
  1296. }
  1297. });
  1298. keyboardShortcuts.registerShortcut("editSong.increaseVolumeLarge", {
  1299. keyCode: 104,
  1300. preventDefault: true,
  1301. handler: () => {
  1302. this.volumeSliderValue = Math.min(
  1303. 10000,
  1304. this.volumeSliderValue + 1000
  1305. );
  1306. this.changeVolume();
  1307. }
  1308. });
  1309. keyboardShortcuts.registerShortcut("editSong.increaseVolumeSmall", {
  1310. keyCode: 104,
  1311. ctrl: true,
  1312. preventDefault: true,
  1313. handler: () => {
  1314. this.volumeSliderValue = Math.min(
  1315. 10000,
  1316. this.volumeSliderValue + 100
  1317. );
  1318. this.changeVolume();
  1319. }
  1320. });
  1321. keyboardShortcuts.registerShortcut("editSong.save", {
  1322. keyCode: 83,
  1323. ctrl: true,
  1324. preventDefault: true,
  1325. handler: () => {
  1326. this.save(this.editing.song, false);
  1327. }
  1328. });
  1329. keyboardShortcuts.registerShortcut("editSong.close", {
  1330. keyCode: 88,
  1331. ctrl: true,
  1332. preventDefault: true,
  1333. handler: () => {
  1334. this.closeModal({
  1335. sector: "admin",
  1336. modal: "editSong"
  1337. });
  1338. }
  1339. });
  1340. keyboardShortcuts.registerShortcut("editSong.focusTitle", {
  1341. keyCode: 36,
  1342. preventDefault: true,
  1343. handler: () => {
  1344. document.getElementById("title-input").focus();
  1345. }
  1346. });
  1347. keyboardShortcuts.registerShortcut("editSong.focusDiscogs", {
  1348. keyCode: 35,
  1349. preventDefault: true,
  1350. handler: () => {
  1351. document.getElementById("discogs-input").focus();
  1352. }
  1353. });
  1354. keyboardShortcuts.registerShortcut("editSong.useAllDiscogs", {
  1355. keyCode: 68,
  1356. alt: true,
  1357. ctrl: true,
  1358. preventDefault: true,
  1359. handler: () => {
  1360. this.getAlbumData("title");
  1361. this.getAlbumData("albumArt");
  1362. this.getAlbumData("artists");
  1363. this.getAlbumData("genres");
  1364. }
  1365. });
  1366. keyboardShortcuts.registerShortcut("editSong.resetDuration", {
  1367. keyCode: 82,
  1368. alt: true,
  1369. ctrl: true,
  1370. preventDefault: true,
  1371. handler: () => {
  1372. this.fillDuration();
  1373. }
  1374. });
  1375. /*
  1376. editSong.pauseResume - Num 5 - Pause/resume song
  1377. editSong.stopVideo - Ctrl - Num 5 - Stop
  1378. editSong.skipToLast10Secs - Num 6 - Skip to last 10 seconds
  1379. editSong.volumeDown5 - Num 2 - Volume down by 10
  1380. editSong.volumeDown1 - Ctrl - Num 2 - Volume down by 1
  1381. editSong.volumeUp5 - Num 8 - Volume up by 10
  1382. editSong.volumeUp1 - Ctrl - Num 8 - Volume up by 1
  1383. editSong.focusTitle - Home - Focus the title input
  1384. editSong.focusDicogs - End - Focus the discogs input
  1385. editSong.save - Ctrl - S - Saves song
  1386. editSong.close - Ctrl - X - Closes modal
  1387. editSong.useAllDiscogs - Ctrl - Alt - D - Sets all fields to the Discogs data
  1388. editSong.resetDuration - Ctrl - Alt - R - Resets the duration
  1389. Inside Discogs inputs: Ctrl - D - Sets this field to the Discogs data
  1390. */
  1391. },
  1392. beforeDestroy() {
  1393. this.playerReady = false;
  1394. clearInterval(this.interval);
  1395. const shortcutNames = [
  1396. "editSong.pauseResume",
  1397. "editSong.stopVideo",
  1398. "editSong.skipToLast10Secs",
  1399. "editSong.volumeDown5",
  1400. "editSong.volumeDown1",
  1401. "editSong.volumeUp5",
  1402. "editSong.volumeUp1",
  1403. "editSong.focusTitle",
  1404. "editSong.focusDicogs",
  1405. "editSong.save",
  1406. "editSong.close",
  1407. "editSong.useAllDiscogs",
  1408. "editSong.resetDuration"
  1409. ];
  1410. shortcutNames.forEach(shortcutName => {
  1411. keyboardShortcuts.unregisterShortcut(shortcutName);
  1412. });
  1413. }
  1414. };
  1415. </script>
  1416. <style lang="scss">
  1417. @import "styles/global.scss";
  1418. #genre-helper-container {
  1419. background-color: white;
  1420. position: fixed;
  1421. z-index: 10000000;
  1422. resize: both;
  1423. overflow: auto;
  1424. border: 1px solid #d3d3d3;
  1425. min-height: 50px !important;
  1426. min-width: 50px !important;
  1427. .genre-helper-header {
  1428. cursor: move;
  1429. z-index: 100000001;
  1430. background-color: $musareBlue;
  1431. padding: 10px;
  1432. display: block;
  1433. height: 10px;
  1434. width: 100%;
  1435. }
  1436. .genre-helper-body {
  1437. display: flex;
  1438. flex-wrap: wrap;
  1439. justify-content: space-evenly;
  1440. span {
  1441. padding: 3px 6px;
  1442. }
  1443. }
  1444. }
  1445. .song-modal {
  1446. .modal-card-title {
  1447. text-align: center;
  1448. margin-left: 24px;
  1449. }
  1450. .modal-card {
  1451. width: 1160px;
  1452. height: 100%;
  1453. .modal-card-body {
  1454. padding: 16px;
  1455. }
  1456. }
  1457. }
  1458. </style>
  1459. <style lang="scss" scoped>
  1460. @import "styles/global.scss";
  1461. .modal-card-body > div {
  1462. display: flex;
  1463. height: 100%;
  1464. }
  1465. .left-section {
  1466. display: flex;
  1467. flex-direction: column;
  1468. margin-right: 16px;
  1469. .top-section {
  1470. display: flex;
  1471. .player-section {
  1472. width: 530px;
  1473. display: flex;
  1474. flex-direction: column;
  1475. .player-footer {
  1476. background-color: #f4f4f4;
  1477. border: 1px rgba(163, 224, 255, 0.75) solid;
  1478. border-radius: 0px 0px 5px 5px;
  1479. display: flex;
  1480. justify-content: space-between;
  1481. height: 54px;
  1482. > * {
  1483. width: 33.3%;
  1484. display: flex;
  1485. align-items: center;
  1486. }
  1487. .player-footer-left {
  1488. flex: 1;
  1489. .material-icons {
  1490. font-size: 38px;
  1491. cursor: pointer;
  1492. }
  1493. .player-play-pause {
  1494. color: $musareBlue;
  1495. }
  1496. .player-stop {
  1497. color: $red;
  1498. }
  1499. .player-fast-forward {
  1500. color: $green;
  1501. }
  1502. }
  1503. .player-footer-center {
  1504. justify-content: center;
  1505. align-items: center;
  1506. flex: 2;
  1507. font-size: 21px;
  1508. font-weight: 400;
  1509. img {
  1510. height: 21px;
  1511. margin-right: 12px;
  1512. filter: invert(26%) sepia(54%) saturate(6317%)
  1513. hue-rotate(2deg) brightness(92%) contrast(115%);
  1514. }
  1515. }
  1516. .player-footer-right {
  1517. justify-content: right;
  1518. flex: 1;
  1519. #volumeSlider {
  1520. width: 126px;
  1521. margin-right: 10px;
  1522. background-color: #f4f4f4;
  1523. }
  1524. }
  1525. }
  1526. }
  1527. .thumbnail-preview {
  1528. width: 189px;
  1529. height: 189px;
  1530. margin-left: 16px;
  1531. }
  1532. }
  1533. .edit-section {
  1534. width: 735px;
  1535. background-color: #f4f4f4;
  1536. border: 1px rgba(163, 224, 255, 0.75) solid;
  1537. margin-top: 16px;
  1538. flex: 1;
  1539. overflow: auto;
  1540. border-radius: 5px;
  1541. .album-get-button {
  1542. background-color: $purple;
  1543. color: white;
  1544. width: 32px;
  1545. text-align: center;
  1546. border-width: 0;
  1547. }
  1548. .duration-fill-button {
  1549. background-color: $red;
  1550. color: $white;
  1551. width: 32px;
  1552. text-align: center;
  1553. border-width: 0;
  1554. }
  1555. .add-button {
  1556. background-color: $musareBlue !important;
  1557. width: 32px;
  1558. i {
  1559. font-size: 32px;
  1560. }
  1561. }
  1562. .album-get-button,
  1563. .duration-fill-button,
  1564. .add-button {
  1565. &:focus,
  1566. &:hover {
  1567. filter: contrast(0.75);
  1568. border: 1px solid black !important;
  1569. }
  1570. }
  1571. > div {
  1572. margin: 16px;
  1573. }
  1574. input {
  1575. width: 100%;
  1576. }
  1577. .title-container {
  1578. width: calc((100% - 32px) / 2);
  1579. }
  1580. .duration-container {
  1581. margin-right: 16px;
  1582. margin-left: 16px;
  1583. width: calc((100% - 32px) / 4);
  1584. }
  1585. .skip-duration-container {
  1586. width: calc((100% - 32px) / 4);
  1587. }
  1588. .album-art-container {
  1589. width: 100%;
  1590. }
  1591. .artists-container {
  1592. width: calc((100% - 32px) / 3);
  1593. position: relative;
  1594. }
  1595. .genres-container {
  1596. width: calc((100% - 32px) / 3);
  1597. margin-left: 16px;
  1598. margin-right: 16px;
  1599. position: relative;
  1600. label {
  1601. display: flex;
  1602. i {
  1603. font-size: 15px;
  1604. align-self: center;
  1605. margin-left: 5px;
  1606. color: $musareBlue;
  1607. cursor: pointer;
  1608. -webkit-user-select: none;
  1609. -moz-user-select: none;
  1610. -ms-user-select: none;
  1611. user-select: none;
  1612. }
  1613. }
  1614. }
  1615. .youtube-id-container {
  1616. width: calc((100% - 32px) / 3);
  1617. }
  1618. .list-item-circle {
  1619. background-color: $musareBlue;
  1620. width: 16px;
  1621. height: 16px;
  1622. border-radius: 8px;
  1623. cursor: pointer;
  1624. margin-right: 8px;
  1625. float: left;
  1626. -webkit-touch-callout: none;
  1627. -webkit-user-select: none;
  1628. -khtml-user-select: none;
  1629. -moz-user-select: none;
  1630. -ms-user-select: none;
  1631. user-select: none;
  1632. i {
  1633. color: $musareBlue;
  1634. font-size: 14px;
  1635. margin-left: 1px;
  1636. }
  1637. }
  1638. .list-item-circle:hover,
  1639. .list-item-circle:focus {
  1640. i {
  1641. color: white;
  1642. }
  1643. }
  1644. .list-item > p {
  1645. line-height: 16px;
  1646. word-wrap: break-word;
  1647. width: calc(100% - 24px);
  1648. left: 24px;
  1649. float: left;
  1650. margin-bottom: 8px;
  1651. }
  1652. .list-item:last-child > p {
  1653. margin-bottom: 0;
  1654. }
  1655. .autosuggest-container {
  1656. position: absolute;
  1657. background: white;
  1658. width: calc(100% + 1px);
  1659. top: 57px;
  1660. z-index: 200;
  1661. overflow: auto;
  1662. max-height: 100%;
  1663. clear: both;
  1664. .autosuggest-item {
  1665. padding: 8px;
  1666. display: block;
  1667. border: 1px solid #dbdbdb;
  1668. margin-top: -1px;
  1669. line-height: 16px;
  1670. cursor: pointer;
  1671. -webkit-user-select: none;
  1672. -ms-user-select: none;
  1673. -moz-user-select: none;
  1674. user-select: none;
  1675. }
  1676. .autosuggest-item:hover,
  1677. .autosuggest-item:focus {
  1678. background-color: #eee;
  1679. }
  1680. .autosuggest-item:first-child {
  1681. border-top: none;
  1682. }
  1683. .autosuggest-item:last-child {
  1684. border-radius: 0 0 3px 3px;
  1685. }
  1686. }
  1687. }
  1688. }
  1689. .right-section {
  1690. display: flex;
  1691. flex-wrap: wrap;
  1692. .api-section {
  1693. width: 376px;
  1694. background-color: #f4f4f4;
  1695. border: 1px rgba(163, 224, 255, 0.75) solid;
  1696. border-radius: 5px;
  1697. padding: 16px;
  1698. overflow: auto;
  1699. height: 100%;
  1700. > label {
  1701. margin-top: 12px;
  1702. }
  1703. .top-container {
  1704. display: flex;
  1705. img {
  1706. height: 85px;
  1707. width: 85px;
  1708. }
  1709. .right-container {
  1710. padding: 8px;
  1711. display: flex;
  1712. flex-direction: column;
  1713. flex: 1;
  1714. .album-title {
  1715. flex: 1;
  1716. font-weight: 600;
  1717. }
  1718. .bottom-row {
  1719. display: flex;
  1720. flex-flow: row;
  1721. line-height: 15px;
  1722. img {
  1723. height: 15px;
  1724. align-self: end;
  1725. flex: 1;
  1726. user-select: none;
  1727. -moz-user-select: none;
  1728. -ms-user-select: none;
  1729. -webkit-user-select: none;
  1730. cursor: pointer;
  1731. }
  1732. p {
  1733. text-align: right;
  1734. }
  1735. .type-year {
  1736. font-size: 13px;
  1737. align-self: end;
  1738. }
  1739. }
  1740. }
  1741. }
  1742. .bottom-container {
  1743. padding: 12px;
  1744. .bottom-container-field {
  1745. line-height: 16px;
  1746. margin-bottom: 8px;
  1747. font-weight: 600;
  1748. span {
  1749. font-weight: 400;
  1750. }
  1751. }
  1752. .bottom-container-field:last-of-type {
  1753. margin-bottom: 0;
  1754. }
  1755. }
  1756. .selected-discogs-info {
  1757. background-color: white;
  1758. border: 1px solid $purple;
  1759. border-radius: 5px;
  1760. margin-bottom: 16px;
  1761. .selected-discogs-info-none {
  1762. font-size: 18px;
  1763. text-align: center;
  1764. }
  1765. .bottom-row > p {
  1766. flex: 1;
  1767. }
  1768. }
  1769. .api-result {
  1770. background-color: white;
  1771. border: 0.5px solid $musareBlue;
  1772. border-radius: 5px;
  1773. margin-bottom: 16px;
  1774. }
  1775. button {
  1776. background-color: $musareBlue !important;
  1777. &:focus,
  1778. &:hover {
  1779. filter: contrast(0.75);
  1780. }
  1781. }
  1782. .tracks {
  1783. margin-top: 12px;
  1784. .track:first-child {
  1785. margin-top: 0;
  1786. border-radius: 3px 3px 0 0;
  1787. }
  1788. .track:last-child {
  1789. border-radius: 0 0 3px 3px;
  1790. }
  1791. .track {
  1792. border: 0.5px solid black;
  1793. margin-top: -1px;
  1794. line-height: 16px;
  1795. display: flex;
  1796. cursor: pointer;
  1797. span {
  1798. font-weight: 600;
  1799. display: inline-block;
  1800. margin-top: 7px;
  1801. margin-bottom: 7px;
  1802. margin-left: 7px;
  1803. }
  1804. p {
  1805. display: inline-block;
  1806. margin: 7px;
  1807. flex: 1;
  1808. }
  1809. }
  1810. .track:hover,
  1811. .track:focus {
  1812. background-color: #f4f4f4;
  1813. }
  1814. }
  1815. .discogs-load-more {
  1816. margin-bottom: 8px;
  1817. }
  1818. }
  1819. }
  1820. .footer-buttons {
  1821. margin-left: auto;
  1822. margin-right: auto;
  1823. }
  1824. input[type="range"] {
  1825. -webkit-appearance: none;
  1826. width: 100%;
  1827. margin: 8.5px 0;
  1828. }
  1829. input[type="range"]:focus {
  1830. outline-style: outset;
  1831. }
  1832. input[type="range"]::-webkit-slider-runnable-track {
  1833. width: 100%;
  1834. height: 3px;
  1835. cursor: pointer;
  1836. box-shadow: none;
  1837. background: #7e7e7e;
  1838. border-radius: none;
  1839. border: none;
  1840. }
  1841. input[type="range"]::-webkit-slider-thumb {
  1842. box-shadow: none;
  1843. border: none;
  1844. height: 20px;
  1845. width: 20px;
  1846. border-radius: 100px;
  1847. background: #03a9f4;
  1848. cursor: pointer;
  1849. -webkit-appearance: none;
  1850. margin-top: -8.5px;
  1851. }
  1852. input[type="range"]:focus::-webkit-slider-runnable-track {
  1853. background: #7e7e7e;
  1854. }
  1855. input[type="range"]::-moz-range-track {
  1856. width: 100%;
  1857. height: 3px;
  1858. cursor: pointer;
  1859. box-shadow: none;
  1860. background: #7e7e7e;
  1861. border-radius: none;
  1862. border: none;
  1863. }
  1864. input[type="range"]::-moz-range-thumb {
  1865. box-shadow: none;
  1866. border: none;
  1867. height: 20px;
  1868. width: 20px;
  1869. border-radius: 100px;
  1870. background: #03a9f4;
  1871. cursor: pointer;
  1872. }
  1873. input[type="range"]::-ms-track {
  1874. width: 100%;
  1875. height: 3px;
  1876. cursor: pointer;
  1877. background: transparent;
  1878. border-color: transparent;
  1879. color: transparent;
  1880. }
  1881. input[type="range"]::-ms-fill-lower {
  1882. background: #717171;
  1883. border: none;
  1884. border-radius: none;
  1885. box-shadow: none;
  1886. }
  1887. input[type="range"]::-ms-fill-upper {
  1888. background: #7e7e7e;
  1889. border: none;
  1890. border-radius: none;
  1891. box-shadow: none;
  1892. }
  1893. input[type="range"]::-ms-thumb {
  1894. box-shadow: none;
  1895. border: none;
  1896. height: 20px;
  1897. width: 20px;
  1898. border-radius: 100px;
  1899. background: #03a9f4;
  1900. cursor: pointer;
  1901. height: 3px;
  1902. }
  1903. input[type="range"]:focus::-ms-fill-lower {
  1904. background: #7e7e7e;
  1905. }
  1906. input[type="range"]:focus::-ms-fill-upper {
  1907. background: #7e7e7e;
  1908. }
  1909. </style>