Report.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. <template>
  2. <div>
  3. <modal
  4. class="report-modal"
  5. title="Report"
  6. :size="existingReports.length > 0 ? 'wide' : null"
  7. >
  8. <template #body>
  9. <div class="report-modal-inner-container">
  10. <div id="left-part">
  11. <song-item
  12. :song="song"
  13. :duration="false"
  14. :disabled-actions="['report']"
  15. header="Selected Song.."
  16. />
  17. <div class="columns is-multiline">
  18. <div
  19. v-for="category in predefinedCategories"
  20. class="column is-half"
  21. :key="category.category"
  22. >
  23. <label class="label">{{
  24. category.category
  25. }}</label>
  26. <p
  27. v-for="issue in category.issues"
  28. class="control checkbox-control"
  29. :key="issue.title"
  30. >
  31. <span class="align-horizontally">
  32. <span>
  33. <label class="switch">
  34. <input
  35. type="checkbox"
  36. :id="issue.title"
  37. v-model="issue.enabled"
  38. />
  39. <span
  40. class="slider round"
  41. ></span>
  42. </label>
  43. <label :for="issue.title">
  44. <span></span>
  45. <p>{{ issue.title }}</p>
  46. </label>
  47. </span>
  48. <i
  49. class="material-icons"
  50. content="Provide More info"
  51. v-tippy
  52. @click="
  53. issue.showDescription =
  54. !issue.showDescription
  55. "
  56. >
  57. info
  58. </i>
  59. </span>
  60. <input
  61. type="text"
  62. class="input"
  63. v-model="issue.description"
  64. v-if="issue.showDescription"
  65. placeholder="Provide more information..."
  66. @keyup="issue.enabled = true"
  67. />
  68. </p>
  69. </div>
  70. <!-- allow for multiple custom issues with plus/add button and then a input textbox -->
  71. <!-- do away with textbox -->
  72. <div class="column is-half">
  73. <div id="custom-issues">
  74. <div id="custom-issues-title">
  75. <label class="label"
  76. >Issues not listed</label
  77. >
  78. <button
  79. class="button tab-actionable-button"
  80. content="Add an issue that isn't listed"
  81. v-tippy
  82. @click="customIssues.push('')"
  83. >
  84. <i
  85. class="material-icons icon-with-button"
  86. >add</i
  87. >
  88. <span> Add Custom Issue </span>
  89. </button>
  90. </div>
  91. <div
  92. class="custom-issue control is-grouped input-with-button"
  93. v-for="(issue, index) in customIssues"
  94. :key="index"
  95. >
  96. <p class="control is-expanded">
  97. <input
  98. type="text"
  99. class="input"
  100. v-model="customIssues[index]"
  101. placeholder="Provide information..."
  102. />
  103. </p>
  104. <p class="control">
  105. <button
  106. class="button is-danger"
  107. content="Remove custom issue"
  108. v-tippy
  109. @click="
  110. customIssues.splice(
  111. index,
  112. 1
  113. )
  114. "
  115. >
  116. <i class="material-icons">
  117. delete
  118. </i>
  119. </button>
  120. </p>
  121. </div>
  122. <p
  123. id="no-issues-listed"
  124. v-if="customIssues.length <= 0"
  125. >
  126. <em>
  127. Add any issues that aren't listed
  128. above.
  129. </em>
  130. </p>
  131. </div>
  132. </div>
  133. </div>
  134. </div>
  135. <div id="right-part" v-if="existingReports.length > 0">
  136. <h4 class="section-title">Previous Reports</h4>
  137. <p class="section-description">
  138. You have made
  139. {{
  140. existingReports.length > 1
  141. ? "multiple reports"
  142. : "a report"
  143. }}
  144. about this song already
  145. </p>
  146. <hr class="section-horizontal-rule" />
  147. <div class="report-items">
  148. <div
  149. class="report-item"
  150. v-for="report in existingReports"
  151. :key="report._id"
  152. >
  153. <report-info-item
  154. :created-at="report.createdAt"
  155. :created-by="report.createdBy"
  156. >
  157. <template #actions>
  158. <i
  159. class="material-icons"
  160. content="View Report"
  161. v-tippy
  162. @click="
  163. openModal({
  164. modal: 'viewReport',
  165. data: {
  166. reportId: report._id
  167. }
  168. })
  169. "
  170. >
  171. open_in_full
  172. </i>
  173. </template>
  174. </report-info-item>
  175. </div>
  176. </div>
  177. </div>
  178. </div>
  179. </template>
  180. <template #footer>
  181. <button class="button is-success" @click="create()">
  182. <i class="material-icons save-changes">done</i>
  183. <span>&nbsp;Create</span>
  184. </button>
  185. <a class="button is-danger" @click="closeModal('report')">
  186. <span>&nbsp;Cancel</span>
  187. </a>
  188. </template>
  189. </modal>
  190. </div>
  191. </template>
  192. <script>
  193. import { mapGetters, mapActions } from "vuex";
  194. import Toast from "toasters";
  195. import ws from "@/ws";
  196. import { mapModalState } from "@/vuex_helpers";
  197. import SongItem from "@/components/SongItem.vue";
  198. import ReportInfoItem from "@/components/ReportInfoItem.vue";
  199. export default {
  200. components: { SongItem, ReportInfoItem },
  201. props: {
  202. modalUuid: { type: String, default: "" }
  203. },
  204. data() {
  205. return {
  206. icons: {
  207. duration: "timer",
  208. video: "tv",
  209. thumbnail: "image",
  210. artists: "record_voice_over",
  211. title: "title",
  212. custom: "lightbulb"
  213. },
  214. existingReports: [],
  215. customIssues: [],
  216. predefinedCategories: [
  217. {
  218. category: "video",
  219. issues: [
  220. {
  221. enabled: false,
  222. title: "Doesn't exist",
  223. description: "",
  224. showDescription: false
  225. },
  226. {
  227. enabled: false,
  228. title: "It's private",
  229. description: "",
  230. showDescription: false
  231. },
  232. {
  233. enabled: false,
  234. title: "It's not available in my country",
  235. description: "",
  236. showDescription: false
  237. },
  238. {
  239. enabled: false,
  240. title: "Unofficial",
  241. description: "",
  242. showDescription: false
  243. }
  244. ]
  245. },
  246. {
  247. category: "title",
  248. issues: [
  249. {
  250. enabled: false,
  251. title: "Incorrect",
  252. description: "",
  253. showDescription: false
  254. },
  255. {
  256. enabled: false,
  257. title: "Inappropriate",
  258. description: "",
  259. showDescription: false
  260. }
  261. ]
  262. },
  263. {
  264. category: "duration",
  265. issues: [
  266. {
  267. enabled: false,
  268. title: "Skips too soon",
  269. description: "",
  270. showDescription: false
  271. },
  272. {
  273. enabled: false,
  274. title: "Skips too late",
  275. description: "",
  276. showDescription: false
  277. },
  278. {
  279. enabled: false,
  280. title: "Starts too soon",
  281. description: "",
  282. showDescription: false
  283. },
  284. {
  285. enabled: false,
  286. title: "Starts too late",
  287. description: "",
  288. showDescription: false
  289. }
  290. ]
  291. },
  292. {
  293. category: "artists",
  294. issues: [
  295. {
  296. enabled: false,
  297. title: "Incorrect",
  298. description: "",
  299. showDescription: false
  300. },
  301. {
  302. enabled: false,
  303. title: "Inappropriate",
  304. description: "",
  305. showDescription: false
  306. }
  307. ]
  308. },
  309. {
  310. category: "thumbnail",
  311. issues: [
  312. {
  313. enabled: false,
  314. title: "Incorrect",
  315. description: "",
  316. showDescription: false
  317. },
  318. {
  319. enabled: false,
  320. title: "Inappropriate",
  321. description: "",
  322. showDescription: false
  323. },
  324. {
  325. enabled: false,
  326. title: "Doesn't exist",
  327. description: "",
  328. showDescription: false
  329. }
  330. ]
  331. }
  332. ]
  333. };
  334. },
  335. computed: {
  336. ...mapModalState("modals/report/MODAL_UUID", {
  337. song: state => state.song
  338. }),
  339. ...mapGetters({
  340. socket: "websockets/getSocket"
  341. })
  342. },
  343. mounted() {
  344. ws.onConnect(this.init);
  345. this.socket.on(
  346. "event:admin.report.resolved",
  347. res => {
  348. this.existingReports = this.existingReports.filter(
  349. report => report._id !== res.data.reportId
  350. );
  351. },
  352. { modalUuid: this.modalUuid }
  353. );
  354. },
  355. beforeUnmount() {
  356. // Delete the VueX module that was created for this modal, after all other cleanup tasks are performed
  357. this.$store.unregisterModule(["modals", "report", this.modalUuid]);
  358. },
  359. methods: {
  360. init() {
  361. this.socket.dispatch(
  362. "reports.myReportsForSong",
  363. this.song._id,
  364. res => {
  365. if (res.status === "success") {
  366. this.existingReports = res.data.reports;
  367. this.existingReports.forEach(report =>
  368. this.socket.dispatch(
  369. "apis.joinRoom",
  370. `view-report.${report._id}`
  371. )
  372. );
  373. }
  374. }
  375. );
  376. },
  377. create() {
  378. const issues = [];
  379. // any predefined issues that are enabled
  380. this.predefinedCategories.forEach(category =>
  381. category.issues.forEach(issue => {
  382. if (issue.enabled)
  383. issues.push({
  384. category: category.category,
  385. title: issue.title,
  386. description: issue.description
  387. });
  388. })
  389. );
  390. // any custom issues
  391. this.customIssues.forEach(issue =>
  392. issues.push({ category: "custom", title: issue })
  393. );
  394. if (issues.length === 0)
  395. return new Toast("Reports must have at least one issue");
  396. return this.socket.dispatch(
  397. "reports.create",
  398. {
  399. issues,
  400. youtubeId: this.song.youtubeId
  401. },
  402. res => {
  403. new Toast(res.message);
  404. if (res.status === "success") this.closeModal("report");
  405. }
  406. );
  407. },
  408. ...mapActions("modalVisibility", ["openModal", "closeModal"])
  409. }
  410. };
  411. </script>
  412. <style lang="less">
  413. .report-modal .song-item .thumbnail {
  414. min-width: 130px;
  415. width: 130px;
  416. height: 130px;
  417. }
  418. </style>
  419. <style lang="less" scoped>
  420. .night-mode {
  421. @media screen and (max-width: 900px) {
  422. #right-part {
  423. background-color: var(--dark-grey-3) !important;
  424. }
  425. }
  426. .columns {
  427. background-color: var(--dark-grey-3) !important;
  428. border-radius: @border-radius;
  429. }
  430. }
  431. .report-modal-inner-container {
  432. display: flex;
  433. @media screen and (max-width: 900px) {
  434. flex-wrap: wrap-reverse;
  435. #left-part {
  436. width: 100%;
  437. }
  438. #right-part {
  439. border-left: 0 !important;
  440. margin-left: 0 !important;
  441. width: 100%;
  442. min-width: 0 !important;
  443. margin-bottom: 20px;
  444. padding: 20px;
  445. background-color: var(--light-grey);
  446. border-radius: @border-radius;
  447. }
  448. }
  449. #right-part {
  450. border-left: 1px solid var(--light-grey-3);
  451. padding-left: 20px;
  452. margin-left: 20px;
  453. min-width: 325px;
  454. .report-items {
  455. max-height: 485px;
  456. overflow: auto;
  457. .report-item:not(:first-of-type) {
  458. margin-top: 10px;
  459. }
  460. }
  461. }
  462. }
  463. .label {
  464. text-transform: capitalize;
  465. }
  466. .columns {
  467. display: flex;
  468. flex-wrap: wrap;
  469. margin-left: unset;
  470. margin-right: unset;
  471. margin-top: 20px;
  472. .column {
  473. flex-basis: 50%;
  474. @media screen and (max-width: 900px) {
  475. flex-basis: 100% !important;
  476. }
  477. }
  478. .control {
  479. display: flex;
  480. flex-direction: column;
  481. span.align-horizontally {
  482. width: 100%;
  483. display: flex;
  484. align-items: center;
  485. justify-content: space-between;
  486. span {
  487. display: flex;
  488. }
  489. }
  490. i {
  491. cursor: pointer;
  492. }
  493. input[type="text"] {
  494. height: initial;
  495. margin: 10px 0;
  496. }
  497. }
  498. }
  499. #custom-issues {
  500. height: 100%;
  501. #custom-issues-title {
  502. display: flex;
  503. align-items: center;
  504. justify-content: space-between;
  505. margin-bottom: 15px;
  506. button {
  507. padding: 3px 5px;
  508. height: initial;
  509. }
  510. label {
  511. margin: 0;
  512. }
  513. }
  514. #no-issues-listed {
  515. display: flex;
  516. height: calc(100% - 32px - 15px);
  517. align-items: center;
  518. justify-content: center;
  519. }
  520. .custom-issue {
  521. flex-direction: row;
  522. input {
  523. height: 36px;
  524. margin: 0;
  525. }
  526. }
  527. }
  528. </style>