index.vue 48 KB

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