Update ComposeModal component, fix multi filter bug and allow media re-ordering before upload/posting

pull/4712/head
Daniel Supernault 2023-10-22 21:13:55 -06:00
rodzic 65a048cdd5
commit 56e315f69f
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 23740873EE6F76A1
1 zmienionych plików z 173 dodań i 69 usunięć

Wyświetl plik

@ -178,9 +178,13 @@
</span> </span>
<span v-else> <span v-else>
<a v-if="!pageLoading && (page > 1 && page <= 2) || (page == 1 && ids.length != 0) || page == 'cropPhoto'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="nextPage">Next</a> <a v-if="!pageLoading && (page > 1 && page <= 2) || (page == 1 && ids.length != 0) || page == 'cropPhoto'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="nextPage">Next</a>
<a v-if="!pageLoading && page == 3" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a> <template v-if="!pageLoading && page == 3" >
<b-spinner v-if="isPosting" small />
<a v-else class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a>
</template>
<a v-if="!pageLoading && page == 'addText'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="composeTextPost()">Post</a> <a v-if="!pageLoading && page == 'addText'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="composeTextPost()">Post</a>
<a v-if="!pageLoading && page == 'video-2'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a> <a v-if="!pageLoading && page == 'video-2'" class="font-weight-bold text-decoration-none" href="#" @click.prevent="compose()">Post</a>
<span v-if="!pageLoading && page == 'filteringMedia'" class="font-weight-bold text-decoration-none text-muted">Next</span>
</span> </span>
</div> </div>
</div> </div>
@ -201,10 +205,10 @@
</div> </div>
</div> </div>
<div v-if="page == 'textOptions'" class="w-100 h-100" style="min-height: 280px;"> <div v-else-if="page == 'textOptions'" class="w-100 h-100" style="min-height: 280px;">
</div> </div>
<div v-if="page == 'addText'" class="w-100 h-100" style="min-height: 280px;"> <div v-else-if="page == 'addText'" class="w-100 h-100" style="min-height: 280px;">
<div class="mt-2"> <div class="mt-2">
<div class="media px-3"> <div class="media px-3">
<div class="media-body"> <div class="media-body">
@ -236,7 +240,7 @@
</div> </div>
</div> </div>
<div v-if="page == 1" class="w-100 h-100 d-flex justify-content-center align-items-center" style="min-height: 400px;"> <div v-else-if="page == 1" class="w-100 h-100 d-flex justify-content-center align-items-center" style="min-height: 400px;">
<div class="text-center"> <div class="text-center">
<div v-if="media.length == 0" class="card my-md-3 shadow-none border compose-action text-decoration-none text-dark"> <div v-if="media.length == 0" class="card my-md-3 shadow-none border compose-action text-decoration-none text-dark">
<div @click.prevent="addMedia" class="card-body py-2"> <div @click.prevent="addMedia" class="card-body py-2">
@ -337,7 +341,7 @@
</div> </div>
</div> </div>
<div v-if="page == 'cropPhoto'" class="w-100 h-100"> <div v-else-if="page == 'cropPhoto'" class="w-100 h-100">
<div v-if="ids.length > 0"> <div v-if="ids.length > 0">
<vue-cropper <vue-cropper
ref="cropper" ref="cropper"
@ -352,7 +356,7 @@
</div> </div>
</div> </div>
<div v-if="page == 2" class="w-100 h-100"> <div v-else-if="page == 2" class="w-100 h-100">
<div v-if="media.length == 1"> <div v-if="media.length == 1">
<div slot="img" style="display:flex;min-height: 420px;align-items: center;"> <div slot="img" style="display:flex;min-height: 420px;align-items: center;">
<img :class="'d-block img-fluid w-100 ' + [media[carouselCursor].filter_class?media[carouselCursor].filter_class:'']" :src="media[carouselCursor].url" :alt="media[carouselCursor].description" :title="media[carouselCursor].description"> <img :class="'d-block img-fluid w-100 ' + [media[carouselCursor].filter_class?media[carouselCursor].filter_class:'']" :src="media[carouselCursor].url" :alt="media[carouselCursor].description" :title="media[carouselCursor].description">
@ -368,7 +372,9 @@
</li> </li>
<li class="nav-item" v-for="(filter, index) in filters"> <li class="nav-item" v-for="(filter, index) in filters">
<div class="p-1 pt-3"> <div class="p-1 pt-3">
<img :src="media[carouselCursor].url" width="100px" height="60px" :class="filter[1]" v-on:click.prevent="toggleFilter($event, filter[1])"> <div class="rounded" :class="filter[1]">
<img :src="media[carouselCursor].url" width="100px" height="60px" v-on:click.prevent="toggleFilter($event, filter[1])">
</div>
</div> </div>
<a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a> <a :class="[media[carouselCursor].filter_class == filter[1] ? 'nav-link text-primary active' : 'nav-link text-muted']" href="#" v-on:click.prevent="toggleFilter($event, filter[1])">{{filter[0]}}</a>
</li> </li>
@ -376,20 +382,21 @@
</div> </div>
</div> </div>
<div v-else-if="media.length > 1" class="d-flex-inline px-2 pt-2"> <div v-else-if="media.length > 1" class="d-flex-inline px-2 pt-2">
<ul class="nav media-drawer-filters text-center"> <ul class="nav media-drawer-filters text-center pb-3">
<li class="nav-item mx-md-4">&nbsp;</li> <li class="nav-item mx-md-4">&nbsp;</li>
<li v-for="(m, i) in media" class="nav-item mx-md-4"> <li v-for="(m, i) in media" :key="m.id + ':' + carouselCursor" class="nav-item mx-md-4">
<div class="nav-link" style="display:block;width:300px;height:300px;" @click="carouselCursor = i"> <div class="nav-link" style="display:block;width:300px;height:300px;" @click="carouselCursor = i">
<!-- <img :class="'d-block img-fluid w-100 ' + [m.filter_class?m.filter_class:'']" :src="m.url" :alt="m.description" :title="m.description"> --> <!-- <img :class="'d-block img-fluid w-100 ' + [m.filter_class?m.filter_class:'']" :src="m.url" :alt="m.description" :title="m.description"> -->
<span :class="[m.filter_class?m.filter_class:'']"> <div :class="[m.filter_class?m.filter_class:'']" style="width:100%;height:100%;display:block;">
<div :class="'rounded ' + [i == carouselCursor ? ' border border-primary shadow':'']" :style="'display:block;width:100%;height:100%;background-image: url(' + m.url + ');background-size:cover;'"></div>
<span :class="'rounded border ' + [i == carouselCursor ? ' border-primary shadow':'']" :style="'display:block;padding:5px;width:100%;height:100%;background-image: url(' + m.url + ');background-size:cover;border-width:3px !important;'"></span> </div>
</span>
</div> </div>
<div v-if="i == carouselCursor" class="text-center mb-0 small text-lighter font-weight-bold pt-2"> <div v-if="i == carouselCursor" class="text-center mb-0 small text-lighter font-weight-bold pt-2">
<button class="btn btn-link" @click="mediaReorder('prev')"><i class="far fa-chevron-circle-left"></i></button>
<span class="cursor-pointer" @click.prevent="showCropPhotoCard">Crop</span> <span class="cursor-pointer" @click.prevent="showCropPhotoCard">Crop</span>
<span class="cursor-pointer px-3" @click.prevent="showEditMediaCard()">Edit</span> <span class="cursor-pointer px-3" @click.prevent="showEditMediaCard()">Edit</span>
<span class="cursor-pointer" @click="deleteMedia()">Delete</span> <span class="cursor-pointer" @click="deleteMedia()">Delete</span>
<button class="btn btn-link" @click="mediaReorder('next')"><i class="far fa-chevron-circle-right"></i></button>
</div> </div>
</li> </li>
<li class="nav-item mx-md-4">&nbsp;</li> <li class="nav-item mx-md-4">&nbsp;</li>
@ -417,7 +424,7 @@
</div> </div>
</div> </div>
<div v-if="page == 3" class="w-100 h-100"> <div v-else-if="page == 3" class="w-100 h-100">
<div class="border-bottom mt-2"> <div class="border-bottom mt-2">
<div class="media px-3"> <div class="media px-3">
<img :src="media[0].url" width="42px" height="42px" :class="[media[0].filter_class?'mr-2 ' + media[0].filter_class:'mr-2']"> <img :src="media[0].url" width="42px" height="42px" :class="[media[0].filter_class?'mr-2 ' + media[0].filter_class:'mr-2']">
@ -524,7 +531,7 @@
</div> </div>
</div> </div>
<div v-if="page == 'tagPeople'" class="w-100 h-100 p-3"> <div v-else-if="page == 'tagPeople'" class="w-100 h-100 p-3">
<autocomplete <autocomplete
v-show="taggedUsernames.length < 10" v-show="taggedUsernames.length < 10"
:search="tagSearch" :search="tagSearch"
@ -557,14 +564,14 @@
<p class="font-weight-bold text-center small text-muted pt-3 mb-0">When you tag someone, they are sent a notification.<br>For more information on tagging, <a href="#" class="text-primary" @click.prevent="showTagHelpCard()">click here</a>.</p> <p class="font-weight-bold text-center small text-muted pt-3 mb-0">When you tag someone, they are sent a notification.<br>For more information on tagging, <a href="#" class="text-primary" @click.prevent="showTagHelpCard()">click here</a>.</p>
</div> </div>
<div v-if="page == 'tagPeopleHelp'" class="w-100 h-100 p-3"> <div v-else-if="page == 'tagPeopleHelp'" class="w-100 h-100 p-3">
<p class="mb-0 text-center py-3 px-2 lead">Tagging someone is like mentioning them, with the option to make it private between you.</p> <p class="mb-0 text-center py-3 px-2 lead">Tagging someone is like mentioning them, with the option to make it private between you.</p>
<p class="mb-3 py-3 px-2 font-weight-lighter"> <p class="mb-3 py-3 px-2 font-weight-lighter">
You can choose to tag someone in public or private mode. Public mode will allow others to see who you tagged in the post and private mode tagged users will not be shown to others. You can choose to tag someone in public or private mode. Public mode will allow others to see who you tagged in the post and private mode tagged users will not be shown to others.
</p> </p>
</div> </div>
<div v-if="page == 'addLocation'" class="w-100 h-100 p-3"> <div v-else-if="page == 'addLocation'" class="w-100 h-100 p-3">
<p class="mb-0">Add Location</p> <p class="mb-0">Add Location</p>
<autocomplete <autocomplete
:search="locationSearch" :search="locationSearch"
@ -576,7 +583,7 @@
</autocomplete> </autocomplete>
</div> </div>
<div v-if="page == 'advancedSettings'" class="w-100 h-100"> <div v-else-if="page == 'advancedSettings'" class="w-100 h-100">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<!-- <div class="d-none list-group-item d-flex justify-content-between"> <!-- <div class="d-none list-group-item d-flex justify-content-between">
<div> <div>
@ -650,7 +657,7 @@
</div> </div>
</div> </div>
<div v-if="page == 'visibility'" class="w-100 h-100"> <div v-else-if="page == 'visibility'" class="w-100 h-100">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<div <div
v-if="!profile.locked" v-if="!profile.locked"
@ -675,7 +682,7 @@
</div> </div>
</div> </div>
<div v-if="page == 'altText'" class="w-100 h-100 p-3"> <div v-else-if="page == 'altText'" class="w-100 h-100 p-3">
<div v-for="(m, index) in media"> <div v-for="(m, index) in media">
<div class="media"> <div class="media">
<img :src="m.preview_url" class="mr-3" width="50px" height="50px"> <img :src="m.preview_url" class="mr-3" width="50px" height="50px">
@ -692,7 +699,7 @@
</p> </p>
</div> </div>
<div v-if="page == 'addToCollection'" class="w-100 h-100 p-3"> <div v-else-if="page == 'addToCollection'" class="w-100 h-100 p-3">
<div v-if="collectionsLoaded && collections.length" class="list-group mb-3 collections-list-group"> <div v-if="collectionsLoaded && collections.length" class="list-group mb-3 collections-list-group">
<div <div
v-for="(collection, index) in collections" v-for="(collection, index) in collections"
@ -721,19 +728,19 @@
</p> </p>
</div> </div>
<div v-if="page == 'schedulePost'" class="w-100 h-100 p-3"> <div v-else-if="page == 'schedulePost'" class="w-100 h-100 p-3">
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p> <p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
</div> </div>
<div v-if="page == 'mediaMetadata'" class="w-100 h-100 p-3"> <div v-else-if="page == 'mediaMetadata'" class="w-100 h-100 p-3">
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p> <p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
</div> </div>
<div v-if="page == 'addToStory'" class="w-100 h-100 p-3"> <div v-else-if="page == 'addToStory'" class="w-100 h-100 p-3">
<p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p> <p class="text-center lead text-muted mb-0 py-5">This feature is not available yet.</p>
</div> </div>
<div v-if="page == 'editMedia'" class="w-100 h-100 p-3"> <div v-else-if="page == 'editMedia'" class="w-100 h-100 p-3">
<div class="media"> <div class="media">
<img :src="media[carouselCursor].preview_url" class="mr-3" width="50px" height="50px"> <img :src="media[carouselCursor].preview_url" class="mr-3" width="50px" height="50px">
<div class="media-body"> <div class="media-body">
@ -770,7 +777,7 @@
</p> </p>
</div> </div>
<div v-if="page == 'video-2'" class="w-100 h-100"> <div v-else-if="page == 'video-2'" class="w-100 h-100">
<div v-if="video.title.length" class="border-bottom"> <div v-if="video.title.length" class="border-bottom">
<div class="media p-3"> <div class="media p-3">
<img :src="media[0].url" width="100px" height="70px" :class="[media[0].filter_class?'mr-2 ' + media[0].filter_class:'mr-2']"> <img :src="media[0].url" width="100px" height="70px" :class="[media[0].filter_class?'mr-2 ' + media[0].filter_class:'mr-2']">
@ -833,6 +840,12 @@
</div> </div>
</div> </div>
<div v-else-if="page == 'filteringMedia'" class="w-100 h-100 py-5">
<div class="d-flex flex-column align-items-center justify-content-center py-5">
<b-spinner small />
<p class="font-weight-bold mt-3">Applying filters...</p>
</div>
</div>
</div> </div>
<!-- card-footers --> <!-- card-footers -->
@ -910,6 +923,7 @@ export default {
}, },
namedPages: [ namedPages: [
'filteringMedia',
'cropPhoto', 'cropPhoto',
'tagPeople', 'tagPeople',
'addLocation', 'addLocation',
@ -943,7 +957,6 @@ export default {
cb(res.data); cb(res.data);
}) })
.catch(err => { .catch(err => {
console.log(err);
}) })
}) })
}, },
@ -957,7 +970,6 @@ export default {
cb(res.data); cb(res.data);
}) })
.catch(err => { .catch(err => {
console.log(err);
}) })
}) })
} }
@ -1032,6 +1044,10 @@ export default {
collectionsPage: 1, collectionsPage: 1,
collectionsCanLoadMore: false, collectionsCanLoadMore: false,
spoilerText: undefined, spoilerText: undefined,
isFilteringMedia: false,
filteringMediaTimeout: undefined,
filteringRemainingCount: 0,
isPosting: false,
} }
}, },
@ -1242,6 +1258,50 @@ export default {
}); });
}, },
mediaReorder(dir) {
const m = this.media;
const cur = this.carouselCursor;
const pla = m[cur];
let res = [];
let cursor = 0;
if(dir == 'prev') {
if(cur == 0) {
for (let i = cursor; i < m.length - 1; i++) {
res[i] = m[i+1];
}
res[m.length - 1] = pla;
cursor = 0;
} else {
res = this.handleSwap(m, cur, cur - 1);
cursor = cur - 1;
}
} else {
if(cur == m.length - 1) {
res = m;
let lastItem = res.pop();
res.unshift(lastItem);
cursor = m.length - 1;
} else {
res = this.handleSwap(m, cur, cur + 1);
cursor = cur + 1;
}
}
this.$nextTick(() => {
this.media = res;
this.carouselCursor = cursor;
})
},
handleSwap(arr, index1, index2) {
if (index1 >= 0 && index1 < arr.length && index2 >= 0 && index2 < arr.length) {
const temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
return arr;
}
},
compose() { compose() {
let state = this.composeState; let state = this.composeState;
@ -1254,8 +1314,15 @@ export default {
return; return;
} }
switch(state) { switch(state) {
case 'publish' : case 'publish':
this.isPosting = true;
let count = this.media.filter(m => m.filter_class && !m.hasOwnProperty('is_filtered')).length;
if(count) {
this.applyFilterToMedia();
return;
}
if(this.composeSettings.media_descriptions === true) { if(this.composeSettings.media_descriptions === true) {
let count = this.media.filter(m => { let count = this.media.filter(m => {
return !m.hasOwnProperty('alt') || m.alt.length < 2; return !m.hasOwnProperty('alt') || m.alt.length < 2;
@ -1377,6 +1444,10 @@ export default {
switch(this.mode) { switch(this.mode) {
case 'photo': case 'photo':
switch(this.page) { switch(this.page) {
case 'filteringMedia':
this.page = 2;
break;
case 'addText': case 'addText':
this.page = 1; this.page = 1;
break; break;
@ -1411,6 +1482,10 @@ export default {
case 'video': case 'video':
switch(this.page) { switch(this.page) {
case 'filteringMedia':
this.page = 2;
break;
case 'licensePicker': case 'licensePicker':
this.page = 'video-2'; this.page = 'video-2';
break; break;
@ -1431,6 +1506,10 @@ export default {
this.page = 1; this.page = 1;
break; break;
case 'filteringMedia':
this.page = 2;
break;
case 'textOptions': case 'textOptions':
this.page = 'addText'; this.page = 'addText';
break; break;
@ -1470,6 +1549,9 @@ export default {
this.page = 2; this.page = 2;
break; break;
case 'filteringMedia':
break;
case 'cropPhoto': case 'cropPhoto':
this.pageLoading = true; this.pageLoading = true;
let self = this; let self = this;
@ -1495,14 +1577,7 @@ export default {
break; break;
case 2: case 2:
if(this.currentFilter) {
if(window.confirm('Are you sure you want to apply this filter?')) {
this.applyFilterToMedia();
this.page++;
}
} else {
this.page++; this.page++;
}
break; break;
case 3: case 3:
this.page++; this.page++;
@ -1649,43 +1724,73 @@ export default {
// this is where the magic happens // this is where the magic happens
var ua = navigator.userAgent.toLowerCase(); var ua = navigator.userAgent.toLowerCase();
if(ua.indexOf('firefox') == -1 && ua.indexOf('chrome') == -1) { if(ua.indexOf('firefox') == -1 && ua.indexOf('chrome') == -1) {
this.isPosting = false;
swal('Oops!', 'Your browser does not support the filter feature.', 'error'); swal('Oops!', 'Your browser does not support the filter feature.', 'error');
this.page = 3;
return; return;
} }
let medias = this.media; let count = this.media.filter(m => m.filter_class).length;
let media = null; if(count) {
const canvas = document.getElementById('pr_canvas'); this.page = 'filteringMedia';
const ctx = canvas.getContext('2d'); this.filteringRemainingCount = count;
let image = document.getElementById('pr_img'); this.$nextTick(() => {
let blob = null; this.isFilteringMedia = true;
let data = null; this.media.forEach((media, idx) => this.applyFilterToMediaSave(media, idx));
})
for (var i = medias.length - 1; i >= 0; i--) { } else {
media = medias[i]; this.page = 3;
if(media.filter_class) { }
image.src = media.url;
image.addEventListener('load', e => {
canvas.width = image.width;
canvas.height = image.height;
ctx.filter = App.util.filterCss[media.filter_class];
ctx.drawImage(image, 0, 0, image.width, image.height);
ctx.save();
canvas.toBlob(function(blob) {
data = new FormData();
data.append('file', blob);
data.append('id', media.id);
axios.post('/api/compose/v0/media/update', data).then(res => {
}).catch(err => {
});
});
}, media.mime, 0.9);
ctx.clearRect(0, 0, image.width, image.height);
}
}
}, },
applyFilterToMediaSave(media, idx) {
if(!media.filter_class) {
return;
}
let self = this;
let data = null;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let image = document.createElement('img');
image.src = media.url;
image.addEventListener('load', e => {
canvas.width = image.width;
canvas.height = image.height;
ctx.filter = App.util.filterCss[media.filter_class];
ctx.drawImage(image, 0, 0, image.width, image.height);
ctx.save();
canvas.toBlob(function(blob) {
data = new FormData();
data.append('file', blob);
data.append('id', media.id);
axios.post('/api/compose/v0/media/update', data)
.then(res => {
self.media[idx].is_filtered = true;
self.updateFilteringMedia();
}).catch(err => {
});
});
}, media.mime, 0.9);
ctx.clearRect(0, 0, image.width, image.height);
},
updateFilteringMedia() {
this.filteringRemainingCount--;
this.filteringMediaTimeout = setTimeout(() => this.filteringMediaTimeoutJob(), 500);
},
filteringMediaTimeoutJob() {
if(this.filteringRemainingCount === 0) {
this.isFilteringMedia = false;
clearTimeout(this.filteringMediaTimeout);
setTimeout(() => this.compose(), 500);
} else {
clearTimeout(this.filteringMediaTimeout);
this.filteringMediaTimeout = setTimeout(() => this.filteringMediaTimeoutJob(), 1000);
}
},
tagSearch(input) { tagSearch(input) {
if (input.length < 1) { return []; } if (input.length < 1) { return []; }
let self = this; let self = this;
@ -1800,7 +1905,6 @@ export default {
} }
window.location.href = res.data.url; window.location.href = res.data.url;
}).catch(err => { }).catch(err => {
console.log(err.response.data.error);
if(err.response.data.hasOwnProperty('error')) { if(err.response.data.hasOwnProperty('error')) {
if(err.response.data.error == 'Duplicate detected.') { if(err.response.data.error == 'Duplicate detected.') {
this.postingPoll = false; this.postingPoll = false;