CollectionComponent.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. <template>
  2. <div class="w-100 h-100">
  3. <div v-if="!loaded" style="height: 80vh;" class="d-flex justify-content-center align-items-center">
  4. <img src="/img/pixelfed-icon-grey.svg" class="">
  5. </div>
  6. <div class="row mt-3" v-if="loaded">
  7. <div class="col-12 p-0 mb-3">
  8. <picture class="d-flex align-items-center justify-content-center">
  9. <div class="dims"></div>
  10. <div style="z-index:500;position: absolute;" class="text-white">
  11. <p class="display-4 text-center pt-3">{{title || 'Untitled Collection'}}</p>
  12. <p class="lead text-center mb-3">{{description}}</p>
  13. <p class="text-center">
  14. {{posts.length}} photos · by <a :href="'/' + profileUsername" class="font-weight-bold text-white">{{profileUsername}}</a>
  15. </p>
  16. <p v-if="owner == true" class="pt-3 text-center">
  17. <span>
  18. <button class="btn btn-outline-light btn-sm" @click.prevent="addToCollection">Add Photo</button>
  19. &nbsp; &nbsp;
  20. <button class="btn btn-outline-light btn-sm" @click.prevent="editCollection">Edit</button>
  21. &nbsp; &nbsp;
  22. <button class="btn btn-outline-light btn-sm" @click.prevent="deleteCollection">Delete</button>
  23. </span>
  24. </p>
  25. </div>
  26. <img :src="previewUrl(posts[0])"
  27. alt=""
  28. style="width:100%; height: 600px; object-fit: cover;"
  29. >
  30. </picture>
  31. </div>
  32. <div class="col-12 p-0">
  33. <masonry
  34. :cols="{default: 2, 700: 2, 400: 1}"
  35. :gutter="{default: '5px'}"
  36. >
  37. <div v-for="(s, index) in posts">
  38. <a class="card info-overlay card-md-border-0 mb-1" :href="s.url">
  39. <img :src="previewUrl(s)" class="img-fluid w-100">
  40. </a>
  41. </div>
  42. </masonry>
  43. </div>
  44. </div>
  45. <b-modal ref="editModal" id="edit-modal" hide-footer centered title="Edit Collection" body-class="">
  46. <form>
  47. <div class="form-group">
  48. <label for="title" class="font-weight-bold text-muted">Title</label>
  49. <input type="text" class="form-control" id="title" placeholder="Untitled Collection" v-model="title">
  50. </div>
  51. <div class="form-group">
  52. <label for="description" class="font-weight-bold text-muted">Description</label>
  53. <textarea class="form-control" id="description" placeholder="Add a description here ..." v-model="description" rows="3"></textarea>
  54. </div>
  55. <div class="form-group">
  56. <label for="visibility" class="font-weight-bold text-muted">Visibility</label>
  57. <select class="custom-select" v-model="visibility">
  58. <option value="public">Public</option>
  59. <option value="private">Followers Only</option>
  60. </select>
  61. </div>
  62. <button type="button" class="btn btn-primary btn-sm py-1 font-weight-bold px-3 float-right" @click.prevent="updateCollection">Save</button>
  63. </form>
  64. </b-modal>
  65. <b-modal ref="addPhotoModal" id="add-photo-modal" hide-footer centered title="Add Photo" body-class="">
  66. <form>
  67. <div class="form-group">
  68. <label for="title" class="font-weight-bold text-muted">Add Post by URL</label>
  69. <input type="text" class="form-control" placeholder="https://pixelfed.dev/p/admin/1" v-model="photoId">
  70. <p class="help-text small text-muted">Only local, public posts can be added</p>
  71. </div>
  72. <button type="button" class="btn btn-primary btn-sm py-1 font-weight-bold px-3 float-right" @click.prevent="pushId">Add Photo</button>
  73. </form>
  74. </b-modal>
  75. </div>
  76. </template>
  77. <style type="text/css" scoped>
  78. .dims {
  79. position: absolute;
  80. top: 0;
  81. right: 0;
  82. bottom: 0;
  83. left: 0;
  84. background: rgba(0,0,0,.68);
  85. z-index: 300;
  86. }
  87. </style>
  88. <script type="text/javascript">
  89. import VueMasonry from 'vue-masonry-css'
  90. Vue.use(VueMasonry);
  91. export default {
  92. props: [
  93. 'collection-id',
  94. 'collection-title',
  95. 'collection-description',
  96. 'collection-visibility',
  97. 'profile-id',
  98. 'profile-username'
  99. ],
  100. data() {
  101. return {
  102. loaded: false,
  103. posts: [],
  104. currentUser: false,
  105. owner: false,
  106. title: this.collectionTitle,
  107. description: this.collectionDescription,
  108. visibility: this.collectionVisibility,
  109. photoId: ''
  110. }
  111. },
  112. beforeMount() {
  113. this.fetchCurrentUser();
  114. this.fetchItems();
  115. },
  116. mounted() {
  117. },
  118. methods: {
  119. fetchCurrentUser() {
  120. if(document.querySelectorAll('body')[0].classList.contains('loggedIn') == true) {
  121. axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
  122. this.currentUser = res.data;
  123. this.owner = this.currentUser.id == this.profileId;
  124. });
  125. }
  126. },
  127. fetchItems() {
  128. axios.get('/api/local/collection/items/' + this.collectionId)
  129. .then(res => {
  130. this.posts = res.data;
  131. this.loaded = true;
  132. });
  133. },
  134. previewUrl(status) {
  135. return status.sensitive ? '/storage/no-preview.png?v=' + new Date().getTime() : status.media_attachments[0].preview_url;
  136. },
  137. previewBackground(status) {
  138. let preview = this.previewUrl(status);
  139. return 'background-image: url(' + preview + ');';
  140. },
  141. addToCollection() {
  142. this.$refs.addPhotoModal.show();
  143. },
  144. pushId() {
  145. let max = 18;
  146. if(this.posts.length >= max) {
  147. swal('Error', 'You can only add ' + max + ' posts per collection', 'error');
  148. return;
  149. }
  150. let url = this.photoId;
  151. let origin = window.location.origin;
  152. let split = url.split('/');
  153. if(url.slice(0, origin.length) !== origin) {
  154. swal('Invalid URL', 'You can only add posts from this instance', 'error');
  155. this.photoId = '';
  156. }
  157. if(url.slice(0, origin.length + 3) !== origin + '/p/' || split.length !== 6) {
  158. swal('Invalid URL', 'Invalid URL', 'error');
  159. this.photoId = '';
  160. }
  161. axios.post('/api/local/collection/item', {
  162. collection_id: this.collectionId,
  163. post_id: split[5]
  164. }).then(res => {
  165. location.reload();
  166. }).catch(err => {
  167. swal('Invalid URL', 'The post you entered was invalid', 'error');
  168. this.photoId = '';
  169. });
  170. },
  171. editCollection() {
  172. this.$refs.editModal.show();
  173. },
  174. deleteCollection() {
  175. if(this.owner == false) {
  176. return;
  177. }
  178. let confirmed = window.confirm('Are you sure you want to delete this collection?');
  179. if(confirmed) {
  180. axios.delete('/api/local/collection/' + this.collectionId)
  181. .then(res => {
  182. window.location.href = '/';
  183. });
  184. } else {
  185. return;
  186. }
  187. },
  188. updateCollection() {
  189. this.$refs.editModal.hide();
  190. axios.post('/api/local/collection/' + this.collectionId, {
  191. title: this.title,
  192. description: this.description,
  193. visibility: this.visibility
  194. }).then(res => {
  195. console.log(res.data);
  196. });
  197. }
  198. }
  199. }
  200. </script>