Report.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  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="view(report._id)"
  163. >
  164. open_in_full
  165. </i>
  166. </template>
  167. </report-info-item>
  168. </div>
  169. </div>
  170. </div>
  171. </div>
  172. </template>
  173. <template #footer>
  174. <button class="button is-success" @click="create()">
  175. <i class="material-icons save-changes">done</i>
  176. <span>&nbsp;Create</span>
  177. </button>
  178. <a class="button is-danger" @click="closeModal('report')">
  179. <span>&nbsp;Cancel</span>
  180. </a>
  181. </template>
  182. </modal>
  183. <view-report v-if="modals.viewReport" />
  184. </div>
  185. </template>
  186. <script>
  187. import { mapState, mapGetters, mapActions } from "vuex";
  188. import Toast from "toasters";
  189. import ws from "@/ws";
  190. import ViewReport from "@/components/modals/ViewReport.vue";
  191. import SongItem from "@/components/SongItem.vue";
  192. import ReportInfoItem from "@/components/ReportInfoItem.vue";
  193. import Modal from "../Modal.vue";
  194. export default {
  195. components: { Modal, ViewReport, SongItem, ReportInfoItem },
  196. data() {
  197. return {
  198. icons: {
  199. duration: "timer",
  200. video: "tv",
  201. thumbnail: "image",
  202. artists: "record_voice_over",
  203. title: "title",
  204. custom: "lightbulb"
  205. },
  206. existingReports: [],
  207. customIssues: [],
  208. predefinedCategories: [
  209. {
  210. category: "video",
  211. issues: [
  212. {
  213. enabled: false,
  214. title: "Doesn't exist",
  215. description: "",
  216. showDescription: false
  217. },
  218. {
  219. enabled: false,
  220. title: "It's private",
  221. description: "",
  222. showDescription: false
  223. },
  224. {
  225. enabled: false,
  226. title: "It's not available in my country",
  227. description: "",
  228. showDescription: false
  229. },
  230. {
  231. enabled: false,
  232. title: "Unofficial",
  233. description: "",
  234. showDescription: false
  235. }
  236. ]
  237. },
  238. {
  239. category: "title",
  240. issues: [
  241. {
  242. enabled: false,
  243. title: "Incorrect",
  244. description: "",
  245. showDescription: false
  246. },
  247. {
  248. enabled: false,
  249. title: "Inappropriate",
  250. description: "",
  251. showDescription: false
  252. }
  253. ]
  254. },
  255. {
  256. category: "duration",
  257. issues: [
  258. {
  259. enabled: false,
  260. title: "Skips too soon",
  261. description: "",
  262. showDescription: false
  263. },
  264. {
  265. enabled: false,
  266. title: "Skips too late",
  267. description: "",
  268. showDescription: false
  269. },
  270. {
  271. enabled: false,
  272. title: "Starts too soon",
  273. description: "",
  274. showDescription: false
  275. },
  276. {
  277. enabled: false,
  278. title: "Starts too late",
  279. description: "",
  280. showDescription: false
  281. }
  282. ]
  283. },
  284. {
  285. category: "artists",
  286. issues: [
  287. {
  288. enabled: false,
  289. title: "Incorrect",
  290. description: "",
  291. showDescription: false
  292. },
  293. {
  294. enabled: false,
  295. title: "Inappropriate",
  296. description: "",
  297. showDescription: false
  298. }
  299. ]
  300. },
  301. {
  302. category: "thumbnail",
  303. issues: [
  304. {
  305. enabled: false,
  306. title: "Incorrect",
  307. description: "",
  308. showDescription: false
  309. },
  310. {
  311. enabled: false,
  312. title: "Inappropriate",
  313. description: "",
  314. showDescription: false
  315. },
  316. {
  317. enabled: false,
  318. title: "Doesn't exist",
  319. description: "",
  320. showDescription: false
  321. }
  322. ]
  323. }
  324. ]
  325. };
  326. },
  327. computed: {
  328. ...mapState({
  329. song: state => state.modals.report.song
  330. }),
  331. ...mapState("modalVisibility", {
  332. modals: state => state.modals
  333. }),
  334. ...mapGetters({
  335. socket: "websockets/getSocket"
  336. })
  337. },
  338. mounted() {
  339. ws.onConnect(this.init);
  340. this.socket.on(
  341. "event:admin.report.resolved",
  342. res => {
  343. this.existingReports = this.existingReports.filter(
  344. report => report._id !== res.data.reportId
  345. );
  346. },
  347. { modal: "report" }
  348. );
  349. },
  350. methods: {
  351. init() {
  352. this.socket.dispatch(
  353. "reports.myReportsForSong",
  354. this.song._id,
  355. res => {
  356. if (res.status === "success") {
  357. this.existingReports = res.data.reports;
  358. this.existingReports.forEach(report =>
  359. this.socket.dispatch(
  360. "apis.joinRoom",
  361. `view-report.${report._id}`
  362. )
  363. );
  364. }
  365. }
  366. );
  367. },
  368. view(reportId) {
  369. this.viewReport(reportId);
  370. this.openModal("viewReport");
  371. },
  372. create() {
  373. const issues = [];
  374. // any predefined issues that are enabled
  375. this.predefinedCategories.forEach(category =>
  376. category.issues.forEach(issue => {
  377. if (issue.enabled)
  378. issues.push({
  379. category: category.category,
  380. title: issue.title,
  381. description: issue.description
  382. });
  383. })
  384. );
  385. // any custom issues
  386. this.customIssues.forEach(issue =>
  387. issues.push({ category: "custom", title: issue })
  388. );
  389. if (issues.length === 0)
  390. return new Toast("Reports must have at least one issue");
  391. return this.socket.dispatch(
  392. "reports.create",
  393. {
  394. issues,
  395. youtubeId: this.song.youtubeId
  396. },
  397. res => {
  398. new Toast(res.message);
  399. if (res.status === "success") this.closeModal("report");
  400. }
  401. );
  402. },
  403. ...mapActions("modalVisibility", ["openModal", "closeModal"]),
  404. ...mapActions("modals/viewReport", ["viewReport"])
  405. }
  406. };
  407. </script>
  408. <style lang="less">
  409. .report-modal .song-item .thumbnail {
  410. min-width: 130px;
  411. width: 130px;
  412. height: 130px;
  413. }
  414. </style>
  415. <style lang="less" scoped>
  416. .night-mode {
  417. @media screen and (max-width: 900px) {
  418. #right-part {
  419. background-color: var(--dark-grey-3) !important;
  420. }
  421. }
  422. .columns {
  423. background-color: var(--dark-grey-3) !important;
  424. border-radius: 5px;
  425. }
  426. }
  427. .report-modal-inner-container {
  428. display: flex;
  429. @media screen and (max-width: 900px) {
  430. flex-wrap: wrap-reverse;
  431. #left-part {
  432. width: 100%;
  433. }
  434. #right-part {
  435. border-left: 0 !important;
  436. margin-left: 0 !important;
  437. width: 100%;
  438. min-width: 0 !important;
  439. margin-bottom: 20px;
  440. padding: 20px;
  441. background-color: var(--light-grey);
  442. border-radius: 5px;
  443. }
  444. }
  445. #right-part {
  446. border-left: 1px solid var(--light-grey-3);
  447. padding-left: 20px;
  448. margin-left: 20px;
  449. min-width: 325px;
  450. .report-items {
  451. max-height: 485px;
  452. overflow: auto;
  453. .report-item:not(:first-of-type) {
  454. margin-top: 10px;
  455. }
  456. }
  457. }
  458. }
  459. .label {
  460. text-transform: capitalize;
  461. }
  462. .columns {
  463. display: flex;
  464. flex-wrap: wrap;
  465. margin-left: unset;
  466. margin-right: unset;
  467. margin-top: 20px;
  468. .column {
  469. flex-basis: 50%;
  470. @media screen and (max-width: 900px) {
  471. flex-basis: 100% !important;
  472. }
  473. }
  474. .control {
  475. display: flex;
  476. flex-direction: column;
  477. span.align-horizontally {
  478. width: 100%;
  479. display: flex;
  480. align-items: center;
  481. justify-content: space-between;
  482. span {
  483. display: flex;
  484. }
  485. }
  486. i {
  487. cursor: pointer;
  488. }
  489. input[type="text"] {
  490. height: initial;
  491. margin: 10px 0;
  492. }
  493. }
  494. }
  495. #custom-issues {
  496. height: 100%;
  497. #custom-issues-title {
  498. display: flex;
  499. align-items: center;
  500. justify-content: space-between;
  501. margin-bottom: 15px;
  502. button {
  503. padding: 3px 5px;
  504. height: initial;
  505. }
  506. label {
  507. margin: 0;
  508. }
  509. }
  510. #no-issues-listed {
  511. display: flex;
  512. height: calc(100% - 32px - 15px);
  513. align-items: center;
  514. justify-content: center;
  515. }
  516. .custom-issue {
  517. flex-direction: row;
  518. input {
  519. height: 36px;
  520. margin: 0;
  521. }
  522. }
  523. }
  524. </style>