index.vue 48 KB

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