PortfolioProfile.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <template>
  2. <div class="w-100 h-100">
  3. <div v-if="loading" class="container">
  4. <div class="d-flex justify-content-center align-items-center" style="height: 100vh;">
  5. <b-spinner />
  6. </div>
  7. </div>
  8. <div v-else class="container">
  9. <div class="row py-5">
  10. <div class="col-12">
  11. <div class="d-flex align-items-center flex-column">
  12. <img :src="profile.avatar" width="60" height="60" class="rounded-circle shadow" onerror="this.onerror=null;this.src='/storage/avatars/default.png?v=0';">
  13. <div class="py-3 text-center" style="max-width: 60%">
  14. <h1 class="font-weight-bold">{{ profile.username }}</h1>
  15. <p class="font-weight-light mb-0">{{ profile.note_text }}</p>
  16. </div>
  17. </div>
  18. </div>
  19. </div>
  20. <div class="container mb-5 pb-5">
  21. <div :class="[ settings.profile_layout === 'masonry' ? 'card-columns' : 'row']" id="portContainer">
  22. <template v-if="settings.profile_layout ==='grid'">
  23. <div v-for="(res, index) in feed" class="col-12 col-md-4 mb-1 p-1">
  24. <div class="square">
  25. <a :href="postUrl(res)">
  26. <img :src="res.media_attachments[0].url" width="100%" height="300" style="overflow: hidden;object-fit: cover;" class="square-content pr-1">
  27. </a>
  28. </div>
  29. </div>
  30. </template>
  31. <div v-else-if="settings.profile_layout ==='album'" class="col-12 mb-1 p-1">
  32. <div class="d-flex justify-content-center">
  33. <p class="text-muted font-weight-bold">{{ albumIndex + 1 }} <span class="font-weight-light">/</span> {{ feed.length }}</p>
  34. </div>
  35. <div class="d-flex justify-content-between align-items-center">
  36. <span v-if="albumIndex === 0">
  37. <i class="fa fa-arrow-circle-left fa-3x text-dark" />
  38. </span>
  39. <a v-else @click.prevent="albumPrev()" href="#">
  40. <i class="fa fa-arrow-circle-left fa-3x text-muted"/>
  41. </a>
  42. <transition name="slide-fade">
  43. <a :href="postUrl(feed[albumIndex])" class="mx-4" :key="albumIndex">
  44. <img
  45. :src="feed[albumIndex].media_attachments[0].url"
  46. width="100%"
  47. class="user-select-none"
  48. style="height: 60vh; overflow: hidden;object-fit: contain;"
  49. :draggable="false"
  50. >
  51. </a>
  52. </transition>
  53. <span v-if="albumIndex === feed.length - 1">
  54. <i class="fa fa-arrow-circle-right fa-3x text-dark" />
  55. </span>
  56. <a v-else @click.prevent="albumNext()" href="#">
  57. <i class="fa fa-arrow-circle-right fa-3x text-muted"/>
  58. </a>
  59. </div>
  60. </div>
  61. <div v-else-if="settings.profile_layout ==='masonry'" class="col-12 p-0 m-0">
  62. <div v-for="(res, index) in feed" class="p-1">
  63. <a :href="postUrl(res)" data-fancybox="recent" :data-src="res.media_attachments[0].url" :data-width="res.media_attachments[0].width" :data-height="res.media_attachments[0].height">
  64. <img
  65. :src="res.media_attachments[0].url"
  66. width="100%"
  67. class="user-select-none"
  68. style="overflow: hidden;object-fit: contain;"
  69. :draggable="false"
  70. >
  71. </a>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. <div class="d-flex fixed-bottom p-3 justify-content-between align-items-center">
  77. <a v-if="user" class="logo-mark logo-mark-sm mb-0 p-1" href="/">
  78. <span class="text-gradient-primary">portfolio</span>
  79. </a>
  80. <span v-else class="logo-mark logo-mark-sm mb-0 p-1">
  81. <span class="text-gradient-primary">portfolio</span>
  82. </span>
  83. <p v-if="user && user.id == profile.id" class="text-center mb-0">
  84. <a :href="settingsUrl" class="text-muted"><i class="far fa-cog fa-lg"></i></a>
  85. </p>
  86. </div>
  87. </div>
  88. </div>
  89. </template>
  90. <script type="text/javascript">
  91. import '@fancyapps/fancybox/dist/jquery.fancybox.js';
  92. import '@fancyapps/fancybox/dist/jquery.fancybox.css';
  93. export default {
  94. props: [ 'initialData' ],
  95. data() {
  96. return {
  97. loading: true,
  98. user: undefined,
  99. profile: undefined,
  100. settings: undefined,
  101. feed: [],
  102. albumIndex: 0,
  103. settingsUrl: window._portfolio.path + '/settings'
  104. }
  105. },
  106. mounted() {
  107. const initialData = JSON.parse(this.initialData);
  108. this.profile = initialData.profile;
  109. this.fetchUser();
  110. },
  111. methods: {
  112. async fetchUser() {
  113. axios.get('/api/v1/accounts/verify_credentials')
  114. .then(res => {
  115. this.user = res.data;
  116. })
  117. .catch(err => {
  118. });
  119. await axios.get('/api/portfolio/account/settings.json', {
  120. params: {
  121. id: this.profile.id
  122. }
  123. })
  124. .then(res => {
  125. this.settings = res.data;
  126. })
  127. .then(() => {
  128. this.fetchFeed();
  129. })
  130. },
  131. async fetchFeed() {
  132. axios.get('/api/portfolio/' + this.profile.id + '/feed')
  133. .then(res => {
  134. this.feed = res.data.filter(p => p.pf_type === "photo");
  135. })
  136. .then(() => {
  137. this.setAlbumSlide();
  138. })
  139. .then(() => {
  140. setTimeout(() => {
  141. this.loading = false;
  142. }, 500);
  143. })
  144. .then(() => {
  145. if(this.settings.profile_layout === 'masonry') {
  146. setTimeout(() => {
  147. this.initMasonry();
  148. }, 500);
  149. }
  150. })
  151. },
  152. postUrl(res) {
  153. return `${window._portfolio.path}/${this.profile.username}/${res.id}`;
  154. },
  155. albumPrev() {
  156. if(this.albumIndex === 0) {
  157. return;
  158. }
  159. if(this.albumIndex === 1) {
  160. this.albumIndex--;
  161. const url = new URL(window.location);
  162. url.searchParams.delete('slide');
  163. window.history.pushState({}, '', url);
  164. return;
  165. }
  166. this.albumIndex--;
  167. const url = new URL(window.location);
  168. url.searchParams.set('slide', this.albumIndex + 1);
  169. window.history.pushState({}, '', url);
  170. },
  171. albumNext() {
  172. if(this.albumIndex === this.feed.length - 1) {
  173. return;
  174. }
  175. this.albumIndex++;
  176. const url = new URL(window.location);
  177. url.searchParams.set('slide', this.albumIndex + 1);
  178. window.history.pushState({}, '', url);
  179. },
  180. setAlbumSlide() {
  181. const url = new URL(window.location);
  182. if(url.searchParams.has('slide')) {
  183. const slide = Number.parseInt(url.searchParams.get('slide'));
  184. if(Number.isNaN(slide)) {
  185. return;
  186. }
  187. if(slide <= 0) {
  188. return;
  189. }
  190. if(slide > this.feed.length) {
  191. return;
  192. }
  193. this.albumIndex = url.searchParams.get('slide') - 1;
  194. }
  195. },
  196. initMasonry() {
  197. $('[data-fancybox="recent"]').fancybox({
  198. gutter: 20,
  199. modal: false,
  200. });
  201. }
  202. }
  203. }
  204. </script>