Stations.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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 _this = this;
  191. const {
  192. newStation: {
  193. name,
  194. displayName,
  195. description,
  196. genres,
  197. blacklistedGenres
  198. }
  199. } = this;
  200. if (name === undefined)
  201. return Toast.methods.addToast(
  202. "Field (Name) cannot be empty",
  203. 3000
  204. );
  205. if (displayName === undefined)
  206. return Toast.methods.addToast(
  207. "Field (Display Name) cannot be empty",
  208. 3000
  209. );
  210. if (description === undefined)
  211. return Toast.methods.addToast(
  212. "Field (Description) cannot be empty",
  213. 3000
  214. );
  215. return _this.socket.emit(
  216. "stations.create",
  217. {
  218. name,
  219. type: "official",
  220. displayName,
  221. description,
  222. genres,
  223. blacklistedGenres
  224. },
  225. result => {
  226. Toast.methods.addToast(result.message, 3000);
  227. if (result.status === "success")
  228. this.newStation = {
  229. genres: [],
  230. blacklistedGenres: []
  231. };
  232. }
  233. );
  234. },
  235. removeStation(index) {
  236. this.socket.emit(
  237. "stations.remove",
  238. this.stations[index]._id,
  239. res => {
  240. Toast.methods.addToast(res.message, 3000);
  241. }
  242. );
  243. },
  244. edit(station) {
  245. this.editStation({
  246. _id: station._id,
  247. name: station.name,
  248. type: station.type,
  249. partyMode: station.partyMode,
  250. description: station.description,
  251. privacy: station.privacy,
  252. displayName: station.displayName,
  253. genres: station.genres,
  254. blacklistedGenres: station.blacklistedGenres
  255. });
  256. this.openModal({
  257. sector: "station",
  258. modal: "editStation"
  259. });
  260. },
  261. addGenre() {
  262. const genre = document
  263. .getElementById(`new-genre`)
  264. .value.toLowerCase()
  265. .trim();
  266. if (this.newStation.genres.indexOf(genre) !== -1)
  267. return Toast.methods.addToast("Genre already exists", 3000);
  268. if (genre) {
  269. this.newStation.genres.push(genre);
  270. document.getElementById(`new-genre`).value = "";
  271. return true;
  272. }
  273. return Toast.methods.addToast("Genre cannot be empty", 3000);
  274. },
  275. removeGenre(index) {
  276. this.newStation.genres.splice(index, 1);
  277. },
  278. addBlacklistedGenre() {
  279. const genre = document
  280. .getElementById(`new-blacklisted-genre`)
  281. .value.toLowerCase()
  282. .trim();
  283. if (this.newStation.blacklistedGenres.indexOf(genre) !== -1)
  284. return Toast.methods.addToast("Genre already exists", 3000);
  285. if (genre) {
  286. this.newStation.blacklistedGenres.push(genre);
  287. document.getElementById(`new-blacklisted-genre`).value = "";
  288. return true;
  289. }
  290. return Toast.methods.addToast("Genre cannot be empty", 3000);
  291. },
  292. removeBlacklistedGenre(index) {
  293. this.newStation.blacklistedGenres.splice(index, 1);
  294. },
  295. init() {
  296. const _this = this;
  297. _this.socket.emit("stations.index", data => {
  298. _this.stations = data.stations;
  299. });
  300. _this.socket.emit("apis.joinAdminRoom", "stations", () => {});
  301. },
  302. ...mapActions("modals", ["openModal"]),
  303. ...mapActions("admin/stations", ["editStation"])
  304. },
  305. mounted() {
  306. const _this = this;
  307. io.getSocket(socket => {
  308. _this.socket = socket;
  309. if (_this.socket.connected) _this.init();
  310. _this.socket.on("event:admin.station.added", station => {
  311. _this.stations.push(station);
  312. });
  313. _this.socket.on("event:admin.station.removed", stationId => {
  314. _this.stations = _this.stations.filter(station => {
  315. return station._id !== stationId;
  316. });
  317. });
  318. io.onConnect(() => {
  319. _this.init();
  320. });
  321. });
  322. }
  323. };
  324. </script>
  325. <style lang="scss" scoped>
  326. .tag {
  327. margin-top: 5px;
  328. &:not(:last-child) {
  329. margin-right: 5px;
  330. }
  331. }
  332. td {
  333. word-wrap: break-word;
  334. max-width: 10vw;
  335. vertical-align: middle;
  336. }
  337. .is-info:focus {
  338. background-color: #0398db;
  339. }
  340. .genre-wrapper {
  341. display: flex;
  342. justify-content: space-around;
  343. }
  344. .card-footer-item {
  345. color: #029ce3;
  346. }
  347. </style>