Stations.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <template>
  2. <div>
  3. <div class="container">
  4. <table class="table is-striped">
  5. <thead>
  6. <tr>
  7. <td>ID</td>
  8. <td>Name</td>
  9. <td>Type</td>
  10. <td>Display Name</td>
  11. <td>Description</td>
  12. <td>Owner</td>
  13. <td>Options</td>
  14. </tr>
  15. </thead>
  16. <tbody>
  17. <tr v-for="(station, index) in stations" :key="index">
  18. <td>
  19. <span>{{ station._id }}</span>
  20. </td>
  21. <td>
  22. <span>{{ station.name }}</span>
  23. </td>
  24. <td>
  25. <span>{{ station.type }}</span>
  26. </td>
  27. <td>
  28. <span>{{ station.displayName }}</span>
  29. </td>
  30. <td>
  31. <span>{{ station.description }}</span>
  32. </td>
  33. <td>
  34. <user-id-to-username
  35. :userId="station.owner"
  36. :link="true"
  37. />
  38. </td>
  39. <td>
  40. <a class="button is-info" v-on:click="edit(station)"
  41. >Edit</a
  42. >
  43. <a
  44. class="button is-danger"
  45. href="#"
  46. @click="removeStation(index)"
  47. >Remove</a
  48. >
  49. </td>
  50. </tr>
  51. </tbody>
  52. </table>
  53. </div>
  54. <div class="container">
  55. <div class="card is-fullwidth">
  56. <header class="card-header">
  57. <p class="card-header-title">
  58. Create official station
  59. </p>
  60. </header>
  61. <div class="card-content">
  62. <div class="content">
  63. <div class="control is-horizontal">
  64. <div class="control is-grouped">
  65. <p class="control is-expanded">
  66. <input
  67. v-model="newStation.name"
  68. class="input"
  69. type="text"
  70. placeholder="Name"
  71. />
  72. </p>
  73. <p class="control is-expanded">
  74. <input
  75. v-model="newStation.displayName"
  76. class="input"
  77. type="text"
  78. placeholder="Display Name"
  79. />
  80. </p>
  81. </div>
  82. </div>
  83. <label class="label">Description</label>
  84. <p class="control is-expanded">
  85. <input
  86. v-model="newStation.description"
  87. class="input"
  88. type="text"
  89. placeholder="Short description"
  90. />
  91. </p>
  92. <div class="control is-grouped genre-wrapper">
  93. <div class="sector">
  94. <p class="control has-addons">
  95. <input
  96. id="new-genre"
  97. class="input"
  98. type="text"
  99. placeholder="Genre"
  100. @keyup.enter="addGenre()"
  101. />
  102. <a
  103. class="button is-info"
  104. href="#"
  105. @click="addGenre()"
  106. >Add genre</a
  107. >
  108. </p>
  109. <span
  110. v-for="(genre, index) in newStation.genres"
  111. :key="index"
  112. class="tag is-info"
  113. >
  114. {{ genre }}
  115. <button
  116. class="delete is-info"
  117. @click="removeGenre(index)"
  118. />
  119. </span>
  120. </div>
  121. <div class="sector">
  122. <p class="control has-addons">
  123. <input
  124. id="new-blacklisted-genre"
  125. class="input"
  126. type="text"
  127. placeholder="Blacklisted Genre"
  128. @keyup.enter="addBlacklistedGenre()"
  129. />
  130. <a
  131. class="button is-info"
  132. href="#"
  133. @click="addBlacklistedGenre()"
  134. >Add blacklisted genre</a
  135. >
  136. </p>
  137. <span
  138. v-for="(genre,
  139. index) in newStation.blacklistedGenres"
  140. :key="index"
  141. class="tag is-info"
  142. >
  143. {{ genre }}
  144. <button
  145. class="delete is-info"
  146. @click="removeBlacklistedGenre(index)"
  147. />
  148. </span>
  149. </div>
  150. </div>
  151. </div>
  152. </div>
  153. <footer class="card-footer">
  154. <a
  155. class="card-footer-item"
  156. href="#"
  157. @click="createStation()"
  158. >Create</a
  159. >
  160. </footer>
  161. </div>
  162. </div>
  163. <edit-station v-if="modals.editStation" />
  164. </div>
  165. </template>
  166. <script>
  167. import { mapState, mapActions } from "vuex";
  168. import { Toast } from "vue-roaster";
  169. import io from "../../io";
  170. import EditStation from "./EditStation.vue";
  171. import UserIdToUsername from "../UserIdToUsername.vue";
  172. export default {
  173. components: { EditStation, UserIdToUsername },
  174. data() {
  175. return {
  176. stations: [],
  177. newStation: {
  178. genres: [],
  179. blacklistedGenres: []
  180. }
  181. };
  182. },
  183. computed: {
  184. ...mapState("modals", {
  185. modals: state => state.modals.station
  186. })
  187. },
  188. methods: {
  189. createStation() {
  190. const {
  191. newStation: {
  192. name,
  193. displayName,
  194. description,
  195. genres,
  196. blacklistedGenres
  197. }
  198. } = this;
  199. if (name === undefined)
  200. return Toast.methods.addToast(
  201. "Field (Name) cannot be empty",
  202. 3000
  203. );
  204. if (displayName === undefined)
  205. return Toast.methods.addToast(
  206. "Field (Display Name) cannot be empty",
  207. 3000
  208. );
  209. if (description === undefined)
  210. return Toast.methods.addToast(
  211. "Field (Description) cannot be empty",
  212. 3000
  213. );
  214. return this.socket.emit(
  215. "stations.create",
  216. {
  217. name,
  218. type: "official",
  219. displayName,
  220. description,
  221. genres,
  222. blacklistedGenres
  223. },
  224. result => {
  225. Toast.methods.addToast(result.message, 3000);
  226. if (result.status === "success")
  227. this.newStation = {
  228. genres: [],
  229. blacklistedGenres: []
  230. };
  231. }
  232. );
  233. },
  234. removeStation(index) {
  235. this.socket.emit(
  236. "stations.remove",
  237. this.stations[index]._id,
  238. res => {
  239. Toast.methods.addToast(res.message, 3000);
  240. }
  241. );
  242. },
  243. edit(station) {
  244. this.editStation({
  245. _id: station._id,
  246. name: station.name,
  247. type: station.type,
  248. partyMode: station.partyMode,
  249. description: station.description,
  250. privacy: station.privacy,
  251. displayName: station.displayName,
  252. genres: station.genres,
  253. blacklistedGenres: station.blacklistedGenres
  254. });
  255. this.openModal({
  256. sector: "station",
  257. modal: "editStation"
  258. });
  259. },
  260. addGenre() {
  261. const genre = document
  262. .getElementById(`new-genre`)
  263. .value.toLowerCase()
  264. .trim();
  265. if (this.newStation.genres.indexOf(genre) !== -1)
  266. return Toast.methods.addToast("Genre already exists", 3000);
  267. if (genre) {
  268. this.newStation.genres.push(genre);
  269. document.getElementById(`new-genre`).value = "";
  270. return true;
  271. }
  272. return Toast.methods.addToast("Genre cannot be empty", 3000);
  273. },
  274. removeGenre(index) {
  275. this.newStation.genres.splice(index, 1);
  276. },
  277. addBlacklistedGenre() {
  278. const genre = document
  279. .getElementById(`new-blacklisted-genre`)
  280. .value.toLowerCase()
  281. .trim();
  282. if (this.newStation.blacklistedGenres.indexOf(genre) !== -1)
  283. return Toast.methods.addToast("Genre already exists", 3000);
  284. if (genre) {
  285. this.newStation.blacklistedGenres.push(genre);
  286. document.getElementById(`new-blacklisted-genre`).value = "";
  287. return true;
  288. }
  289. return Toast.methods.addToast("Genre cannot be empty", 3000);
  290. },
  291. removeBlacklistedGenre(index) {
  292. this.newStation.blacklistedGenres.splice(index, 1);
  293. },
  294. init() {
  295. this.socket.emit("stations.index", data => {
  296. this.stations = data.stations;
  297. });
  298. this.socket.emit("apis.joinAdminRoom", "stations", () => {});
  299. },
  300. ...mapActions("modals", ["openModal"]),
  301. ...mapActions("admin/stations", ["editStation"])
  302. },
  303. mounted() {
  304. io.getSocket(socket => {
  305. this.socket = socket;
  306. if (this.socket.connected) this.init();
  307. this.socket.on("event:admin.station.added", station => {
  308. this.stations.push(station);
  309. });
  310. this.socket.on("event:admin.station.removed", stationId => {
  311. this.stations = this.stations.filter(station => {
  312. return station._id !== stationId;
  313. });
  314. });
  315. io.onConnect(() => {
  316. this.init();
  317. });
  318. });
  319. }
  320. };
  321. </script>
  322. <style lang="scss" scoped>
  323. @import "styles/global.scss";
  324. .tag {
  325. margin-top: 5px;
  326. &:not(:last-child) {
  327. margin-right: 5px;
  328. }
  329. }
  330. td {
  331. word-wrap: break-word;
  332. max-width: 10vw;
  333. vertical-align: middle;
  334. }
  335. .is-info:focus {
  336. background-color: #0398db;
  337. }
  338. .genre-wrapper {
  339. display: flex;
  340. justify-content: space-around;
  341. }
  342. .card-footer-item {
  343. color: #029ce3;
  344. }
  345. </style>