2022-10-17 07:59:23 +00:00
< template >
< div class = "portfolio-settings px-3" >
< div v-if = "loading" class="d-flex justify-content-center align-items-center py-5" >
< b-spinner variant = "primary" / >
< / div >
< div v -else class = "row justify-content-center mb-5 pb-5" >
< div class = "col-12 col-md-8 bg-dark py-2 rounded" >
< ul class = "nav nav-pills nav-fill" >
< li v-for = "(tab, index) in tabs" class="nav-item" :class="{ disabled: index !== 0 && !settings.active}" >
< span v-if = "index !== 0 && !settings.active" class="nav-link" > {{ tab }} < / span >
< a v -else class = "nav-link" : class = "{ active: tab === tabIndex }" href = "#" @click.prevent ="toggleTab(tab)" > { { tab } } < / a >
< / li >
< / ul >
< / div >
< transition name = "slide-fade" >
< div v-if = "tabIndex === 'Configure'" class="col-12 col-md-8 bg-dark mt-3 py-2 rounded" key="0" >
< div v-if = "!user.statuses_count" class="alert alert-danger" >
< p class = "mb-0 small font-weight-bold" > You don ' t have any public posts , once you share public posts you can enable your portfolio . < / p >
< / div >
< div class = "d-flex justify-content-between align-items-center py-2" >
< div class = "setting-label" >
< p class = "lead mb-0" > Portfolio Enabled < / p >
< p class = "small mb-0 text-muted" > You must enable your portfolio before you or anyone can view it . < / p >
< / div >
< div class = "setting-switch mt-n1" >
< b-form-checkbox v-model = "settings.active" name="check-button" size="lg" switch :disabled="!user.statuses_count" / >
< / div >
< / div >
< hr >
< div class = "d-flex justify-content-between align-items-center py-2" >
< div class = "setting-label" style = "max-width: 50%;" >
< p class = "mb-0" > Portfolio Source < / p >
< p class = "small mb-0 text-muted" > Choose how you want to populate your portfolio , select Most Recent posts to automatically update your portfolio with recent posts or Curated Posts to select specific posts for your portfolio . < / p >
< / div >
< div class = "ml-3" >
< b-form-select v-model = "settings.profile_source" :options="profileSourceOptions" :disabled="!user.statuses_count" / >
< / div >
< / div >
< / div >
< div v -else -if = " tabIndex = = = ' Curate ' " class = "col-12 col-md-8 mt-3 py-2 px-0" key = "1" >
< div v-if = "!recentPostsLoaded" class="d-flex align-items-center justify-content-center py-5 my-5" >
< div class = "text-center" >
< div class = "spinner-border" role = "status" >
< span class = "sr-only" > Loading ... < / span >
< / div >
< p class = "text-muted" > Loading recent posts ... < / p >
< / div >
< / div >
< template v-else >
< div class = "mt-n2 mb-4" >
2023-03-17 07:32:29 +00:00
< p class = "text-muted small" > Select up to 100 photos from your 100 most recent posts . You can only select public photo posts , videos are not supported at this time . < / p >
2022-10-17 07:59:23 +00:00
< div class = "d-flex align-items-center justify-content-between" >
2023-03-17 07:32:29 +00:00
< p class = "font-weight-bold mb-0" > Selected { { selectedRecentPosts . length } } / 100 < / p >
2022-10-17 07:59:23 +00:00
< div >
< button
class = "btn btn-link font-weight-bold mr-3 text-decoration-none"
: disabled = "!selectedRecentPosts.length"
@ click = "clearSelected" >
Clear selected
< / button >
< button
class = "btn btn-primary py-0 font-weight-bold"
style = "width: 150px;"
: disabled = "!canSaveCurated"
@ click = "saveCurated()" >
< template v-if = "!isSavingCurated" > Save < / template >
< b-spinner v -else small / >
< / button >
< / div >
< / div >
< / div >
< div class = "d-flex justify-content-between align-items-center" >
< span @click ="recentPostsPrev" >
< i :class = "prevClass" / >
< / span >
< div class = "row flex-grow-1 mx-2" >
< div v-for = "(post, index) in recentPosts.slice(rpStart, rpStart + 9)" class="col-12 col-md-4 mb-1 p-1" >
< div class = "square user-select-none" @click.prevent ="toggleRecentPost(post.id)" >
< transition name = "fade" >
< img
: key = "post.id"
2023-03-17 07:32:29 +00:00
: src = "getPreviewUrl(post)"
2022-10-17 07:59:23 +00:00
width = "100%"
height = "300"
style = "overflow: hidden;object-fit: cover;"
: draggable = "false"
2023-03-17 07:32:29 +00:00
loading = "lazy"
onerror = "this.src='/storage/no-preview.png';this.onerror=null;"
2022-10-17 07:59:23 +00:00
class = "square-content pr-1" >
< / transition >
< div v-if = "selectedRecentPosts.indexOf(post.id) !== -1" style="position: absolute;right: -5px;bottom:-5px;" >
< div class = "selected-badge" > { { selectedRecentPosts . indexOf ( post . id ) + 1 } } < / div >
< / div >
< / div >
< / div >
< / div >
< span @click ="recentPostsNext()" >
< i :class = "nextClass" / >
< / span >
< / div >
< / template >
< / div >
2023-03-17 07:32:29 +00:00
< div v -else -if = " tabIndex = = = ' Customize ' " class = "col-12 mt-3 py-2" key = "2" >
< div class = "row" >
< div class = "col-12 col-md-6" >
< div v-for = "setting in customizeSettings" class="card bg-dark mb-5" >
< div class = "card-header" > { { setting . title } } < / div >
< div class = "list-group bg-dark" >
< div v-for = "item in setting.items" class="list-group-item" >
< div class = "d-flex justify-content-between align-items-center py-2" >
< div class = "setting-label" >
< p class = "mb-0" > { { item . label } } < / p >
< p v-if = "item.description" class="small text-muted mb-0" > {{ item.description }} < / p >
< / div >
< div class = "setting-switch mt-n1" >
< b-form-checkbox
v - model = "settings[item.model]"
name = "check-button"
size = "lg"
switch
: disabled = "item.requiredWithTrue && !settings[item.requiredWithTrue]" / >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "col-12 col-md-6" >
< div class = "card bg-dark mb-5" >
< div class = "card-header" > Portfolio < / div >
< div class = "list-group bg-dark" >
< div class = "list-group-item" >
< div class = "d-flex justify-content-between align-items-center py-2" >
< div class = "setting-label" >
< p class = "mb-0" > Layout < / p >
< / div >
< div >
< b-form-select v-model = "settings.profile_layout" :options="profileLayoutOptions" / >
< / div >
< / div >
< / div >
< div v-if = "settings.profile_source === 'custom'" class="list-group-item" >
< div class = "d-flex justify-content-between align-items-center py-2" >
< div class = "setting-label" >
< p class = "mb-0" > Order < / p >
< / div >
< div >
< b-form-select
v - model = "settings.feed_order"
: options = "profileLayoutFeedOrder" / >
< / div >
< / div >
< / div >
< div class = "list-group-item" >
< div class = "d-flex justify-content-between align-items-center py-2" >
< div class = "setting-label" >
< p class = "mb-0" > Color Scheme < / p >
< / div >
< div >
< b-form-select
v - model = "settings.color_scheme"
: options = "profileLayoutColorSchemeOptions"
: disabled = "settings.color_scheme === 'custom'"
@ change = "updateColorScheme" / >
< / div >
< / div >
< / div >
< div class = "list-group-item" >
< div class = "d-flex justify-content-between align-items-center py-2" >
< div class = "setting-label" >
< p class = "mb-0" > Background Color < / p >
< / div >
< b-col sm = "2" >
< b-form-input
v - model = "settings.background_color"
debounce = "1000"
type = "color"
@ change = "updateBackgroundColor" / >
< b-button
v - if = "!['#000000', null].includes(settings.background_color)"
variant = "link"
@ click = "resetBackgroundColor" >
Reset
< / b-button >
< / b-col >
< / div >
< / div >
< div class = "list-group-item" >
< div class = "d-flex justify-content-between align-items-center py-2" >
< div class = "setting-label" >
< p class = "mb-0" > Text Color < / p >
< / div >
< b-col sm = "2" >
< b-form-input
v - model = "settings.text_color"
debounce = "1000"
type = "color"
@ change = "updateTextColor" / >
< b-button
v - if = "!['#d4d4d8', null].includes(settings.text_color)"
variant = "link"
@ click = "resetTextColor" >
Reset
< / b-button >
< / b-col >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
2022-10-17 07:59:23 +00:00
< / div >
< div v -else -if = " tabIndex = = = ' Share ' " class = "col-12 col-md-8 bg-dark mt-3 py-2 rounded" key = "0" >
< div class = "py-2" >
< p class = "text-muted" > Portfolio URL < / p >
< p class = "lead mb-0" > < a :href = "settings.url" > { { settings . url } } < / a > < / p >
< / div >
< / div >
< / transition >
< / div >
< / div >
< / template >
< script type = "text/javascript" >
export default {
data ( ) {
return {
loading : true ,
tabIndex : "Configure" ,
tabs : [
"Configure" ,
"Customize" ,
"View Portfolio"
] ,
user : undefined ,
settings : undefined ,
recentPostsLoaded : false ,
rpStart : 0 ,
recentPosts : [ ] ,
recentPostsPage : undefined ,
selectedRecentPosts : [ ] ,
isSavingCurated : false ,
canSaveCurated : false ,
customizeSettings : [ ] ,
2023-03-17 07:32:29 +00:00
skipWatch : false ,
2022-10-17 07:59:23 +00:00
profileSourceOptions : [
{ value : null , text : 'Please select an option' , disabled : true } ,
{ value : 'recent' , text : 'Most recent posts' } ,
] ,
profileLayoutOptions : [
{ value : null , text : 'Please select an option' , disabled : true } ,
{ value : 'grid' , text : 'Grid' } ,
{ value : 'masonry' , text : 'Masonry' } ,
{ value : 'album' , text : 'Album' } ,
2023-03-17 07:32:29 +00:00
] ,
profileLayoutColorSchemeOptions : [
{ value : null , text : 'Please select an option' , disabled : true } ,
{ value : 'light' , text : 'Light mode' } ,
{ value : 'dark' , text : 'Dark mode' } ,
{ value : 'custom' , text : 'Custom color scheme' , disabled : true } ,
] ,
profileLayoutFeedOrder : [
{ value : 'oldest' , text : 'Oldest first' } ,
{ value : 'recent' , text : 'Recent first' }
2022-10-17 07:59:23 +00:00
]
}
} ,
computed : {
prevClass ( ) {
return this . rpStart === 0 ?
"fa fa-arrow-circle-left fa-3x text-dark" :
"fa fa-arrow-circle-left fa-3x text-muted cursor-pointer" ;
} ,
nextClass ( ) {
return this . rpStart > ( this . recentPosts . length - 9 ) ?
"fa fa-arrow-circle-right fa-3x text-dark" :
"fa fa-arrow-circle-right fa-3x text-muted cursor-pointer" ;
} ,
} ,
watch : {
settings : {
deep : true ,
immediate : true ,
handler : function ( o , n ) {
2023-03-17 07:32:29 +00:00
if ( this . loading || this . skipWatch ) {
2022-10-17 07:59:23 +00:00
return ;
}
if ( ! n . show _timestamp ) {
this . settings . show _link = false ;
}
this . updateSettings ( ) ;
}
}
} ,
mounted ( ) {
this . fetchUser ( ) ;
} ,
methods : {
fetchUser ( ) {
axios . get ( '/api/v1/accounts/verify_credentials' )
. then ( res => {
this . user = res . data ;
if ( res . data . statuses _count > 0 ) {
this . profileSourceOptions = [
{ value : null , text : 'Please select an option' , disabled : true } ,
{ value : 'recent' , text : 'Most recent posts' } ,
{ value : 'custom' , text : 'Curated posts' } ,
] ;
} else {
setTimeout ( ( ) => {
this . settings . active = false ;
this . settings . profile _source = 'recent' ;
this . tabIndex = 'Configure' ;
} , 1000 ) ;
}
} )
axios . post ( this . apiPath ( '/api/portfolio/self/settings.json' ) )
. then ( res => {
this . settings = res . data ;
this . updateTabs ( ) ;
if ( res . data . metadata && res . data . metadata . posts ) {
this . selectedRecentPosts = res . data . metadata . posts ;
}
2023-03-17 07:32:29 +00:00
if ( res . data . color _scheme != 'dark' ) {
if ( res . data . color _scheme === 'light' ) {
this . updateBackgroundColor ( '#ffffff' ) ;
} else {
if ( res . data . hasOwnProperty ( 'background_color' ) ) {
this . updateBackgroundColor ( res . data . background _color ) ;
}
if ( res . data . hasOwnProperty ( 'text_color' ) ) {
this . updateTextColor ( res . data . text _color ) ;
}
}
}
2022-10-17 07:59:23 +00:00
} )
. then ( ( ) => {
this . initCustomizeSettings ( ) ;
} )
. then ( ( ) => {
const url = new URL ( window . location ) ;
if ( url . searchParams . has ( 'tab' ) ) {
let tab = url . searchParams . get ( 'tab' ) ;
let tabs = this . settings . profile _source === 'custom' ?
[ 'curate' , 'customize' , 'share' ] :
[ 'customize' , 'share' ] ;
if ( tabs . indexOf ( tab ) !== - 1 ) {
this . toggleTab ( tab . slice ( 0 , 1 ) . toUpperCase ( ) + tab . slice ( 1 ) ) ;
}
}
} )
. then ( ( ) => {
setTimeout ( ( ) => {
this . loading = false ;
} , 500 ) ;
} )
} ,
apiPath ( path ) {
return path ;
} ,
toggleTab ( idx ) {
if ( idx === 'Curate' && ! this . recentPostsLoaded ) {
this . loadRecentPosts ( ) ;
}
this . tabIndex = idx ;
this . rpStart = 0 ;
if ( idx == 'Configure' ) {
const url = new URL ( window . location ) ;
url . searchParams . delete ( 'tab' ) ;
window . history . pushState ( { } , '' , url ) ;
} else if ( idx == 'View Portfolio' ) {
this . tabIndex = 'Configure' ;
window . location . href = ` https:// ${ window . _portfolio . domain } ${ window . _portfolio . path } / ${ this . user . username } ` ;
return ;
} else {
const url = new URL ( window . location ) ;
url . searchParams . set ( 'tab' , idx . toLowerCase ( ) ) ;
window . history . pushState ( { } , '' , url ) ;
}
} ,
updateTabs ( ) {
if ( this . settings . profile _source === 'custom' ) {
this . tabs = [
"Configure" ,
"Curate" ,
"Customize" ,
"View Portfolio"
] ;
} else {
this . tabs = [
"Configure" ,
"Customize" ,
"View Portfolio"
] ;
}
} ,
2023-03-17 07:32:29 +00:00
updateSettings ( silent = false ) {
if ( this . skipWatch ) {
return ;
}
2022-10-17 07:59:23 +00:00
axios . post ( this . apiPath ( '/api/portfolio/self/update-settings.json' ) , this . settings )
. then ( res => {
this . updateTabs ( ) ;
2023-03-17 07:32:29 +00:00
if ( ! silent ) {
this . $bvToast . toast ( ` Your settings have been successfully updated! ` , {
variant : 'dark' ,
title : 'Settings Updated' ,
autoHideDelay : 2000 ,
appendToast : false
} )
}
2022-10-17 07:59:23 +00:00
} )
} ,
loadRecentPosts ( ) {
axios . get ( '/api/v1/accounts/' + this . user . id + '/statuses?only_media=1&media_types=photo&limit=100' )
. then ( res => {
if ( res . data . length ) {
this . recentPosts = res . data . filter ( p => p . visibility === "public" ) ;
}
} )
. then ( ( ) => {
setTimeout ( ( ) => {
this . recentPostsLoaded = true ;
} , 500 ) ;
} )
} ,
toggleRecentPost ( id ) {
if ( this . selectedRecentPosts . indexOf ( id ) == - 1 ) {
2023-03-17 07:32:29 +00:00
if ( this . selectedRecentPosts . length === 100 ) {
2022-10-17 07:59:23 +00:00
return ;
}
this . selectedRecentPosts . push ( id ) ;
} else {
this . selectedRecentPosts = this . selectedRecentPosts . filter ( i => i !== id ) ;
}
this . canSaveCurated = true ;
} ,
recentPostsPrev ( ) {
if ( this . rpStart === 0 ) {
return ;
}
this . rpStart = this . rpStart - 9 ;
} ,
recentPostsNext ( ) {
if ( this . rpStart > ( this . recentPosts . length - 9 ) ) {
return ;
}
this . rpStart = this . rpStart + 9 ;
} ,
clearSelected ( ) {
this . selectedRecentPosts = [ ] ;
} ,
saveCurated ( ) {
this . isSavingCurated = true ;
event . currentTarget ? . blur ( ) ;
axios . post ( '/api/portfolio/self/curated.json' , {
ids : this . selectedRecentPosts
} )
. then ( res => {
this . isSavingCurated = false ;
this . $bvToast . toast ( ` Your curated posts have been updated! ` , {
variant : 'dark' ,
title : 'Portfolio Updated' ,
autoHideDelay : 2000 ,
appendToast : false
} )
} )
. catch ( err => {
this . isSavingCurated = false ;
this . $bvToast . toast ( ` An error occured while attempting to update your portfolio, please try again later and contact an admin if this problem persists. ` , {
variant : 'dark' ,
title : 'Error' ,
autoHideDelay : 2000 ,
appendToast : false
} )
} )
} ,
initCustomizeSettings ( ) {
this . customizeSettings = [
{
title : "Post Settings" ,
items : [
{
label : "Show Captions" ,
model : "show_captions"
} ,
{
label : "Show License" ,
model : "show_license"
} ,
{
label : "Show Location" ,
model : "show_location"
} ,
{
label : "Show Timestamp" ,
model : "show_timestamp"
} ,
{
label : "Link to Post" ,
description : "Add link to timestamp to view the original post url, requires show timestamp to be enabled" ,
model : "show_link" ,
requiredWithTrue : "show_timestamp"
}
]
} ,
{
title : "Profile Settings" ,
items : [
{
label : "Show Avatar" ,
model : "show_avatar"
} ,
{
label : "Show Bio" ,
model : "show_bio"
2023-03-17 07:32:29 +00:00
} ,
{
label : "Show View Profile Button" ,
model : "show_profile_button"
} ,
{
label : "Enable RSS Feed" ,
description : "Enable your RSS feed with the 10 most recent portfolio items" ,
model : "rss_enabled"
} ,
{
label : "Show RSS Feed Button" ,
model : "show_rss_button" ,
requiredWithTrue : "rss_enabled"
} ,
2022-10-17 07:59:23 +00:00
]
} ,
]
2023-03-17 07:32:29 +00:00
} ,
updateBackgroundColor ( e ) {
this . skipWatch = true ;
let rs = document . querySelector ( ':root' ) ;
rs . style . setProperty ( '--body-bg' , e ) ;
if ( e !== '#000000' && e !== '#ffffff' ) {
this . settings . color _scheme = 'custom' ;
}
this . $nextTick ( ( ) => {
this . skipWatch = false ;
} ) ;
} ,
updateTextColor ( e ) {
this . skipWatch = true ;
let rs = document . querySelector ( ':root' ) ;
rs . style . setProperty ( '--text-color' , e ) ;
if ( e !== '#d4d4d8' ) {
this . settings . color _scheme = 'custom' ;
}
this . $nextTick ( ( ) => {
this . skipWatch = false ;
} ) ;
} ,
resetBackgroundColor ( ) {
this . skipWatch = true ;
this . $nextTick ( ( ) => {
this . updateBackgroundColor ( '#000000' ) ;
this . settings . color _scheme = 'dark' ;
this . settings . background _color = '#000000' ;
this . updateSettings ( true ) ;
setTimeout ( ( ) => {
this . skipWatch = false ;
} , 1000 ) ;
} ) ;
} ,
resetTextColor ( ) {
this . skipWatch = true ;
this . $nextTick ( ( ) => {
this . updateTextColor ( '#d4d4d8' ) ;
this . settings . color _scheme = 'dark' ;
this . settings . text _color = '#d4d4d8' ;
this . updateSettings ( true ) ;
setTimeout ( ( ) => {
this . skipWatch = false ;
} , 1000 ) ;
} ) ;
} ,
updateColorScheme ( e ) {
if ( e === 'light' ) {
this . updateBackgroundColor ( '#ffffff' ) ;
}
if ( e === 'dark' ) {
this . updateBackgroundColor ( '#000000' ) ;
}
} ,
getPreviewUrl ( post ) {
let media = post . media _attachments [ 0 ] ;
if ( ! media ) { return '/storage/no-preview.png' ; }
if ( media . preview _url && ! media . preview _url . endsWith ( '/no-preview.png' ) ) {
return media . preview _url ;
}
return media . url ;
2022-10-17 07:59:23 +00:00
}
}
}
< / script >