more options button to follow/unfollow

This commit is contained in:
Nolan Lawson 2018-03-11 19:40:32 -07:00
parent 92176df3ab
commit c13771c3a0
8 changed files with 164 additions and 75 deletions

View file

@ -3,7 +3,7 @@ import { followAccount, unfollowAccount } from '../_api/follow'
import { database } from '../_database/database' import { database } from '../_database/database'
import { toast } from '../_utils/toast' import { toast } from '../_utils/toast'
export async function setAccountFollowed (accountId, follow) { export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
let accessToken = store.get('accessToken') let accessToken = store.get('accessToken')
try { try {
@ -15,6 +15,9 @@ export async function setAccountFollowed (accountId, follow) {
let relationship = await database.getRelationship(instanceName, accountId) let relationship = await database.getRelationship(instanceName, accountId)
relationship.following = follow relationship.following = follow
await database.setRelationship(instanceName, relationship) await database.setRelationship(instanceName, relationship)
if (toastOnSuccess) {
toast.say(`${follow ? 'Followed' : 'Unfollowed'}`)
}
} catch (e) { } catch (e) {
console.error(e) console.error(e)
toast.say(`Unable to ${follow ? 'follow' : 'unfollow'} account: ` + (e.message || '')) toast.say(`Unable to ${follow ? 'follow' : 'unfollow'} account: ` + (e.message || ''))

View file

@ -0,0 +1,57 @@
<ul class="generic-dialog-list">
{{#each items as item @key}}
<li class="generic-dialog-list-item">
<button class="generic-dialog-list-button" on:click="fire('click', item)">
<svg>
<use xlink:href="{{item.icon}}" />
</svg>
<span>
{{item.label}}
</span>
<svg class="{{item.selected ? '' : 'hidden'}}" aria-hidden="{{!item.selected}}">
<use xlink:href="#fa-check" />
</svg>
</button>
</li>
{{/each}}
</ul>
<style>
.generic-dialog-list {
list-style: none;
width: 100%;
border: 1px solid var(--settings-list-item-border);
box-sizing: border-box;
min-width: 300px;
max-width: calc(100vw - 20px);
}
.generic-dialog-list-item {
border: 1px solid var(--settings-list-item-border);
font-size: 1.2em;
display: flex;
}
.generic-dialog-list-item svg {
width: 24px;
height: 24px;
fill: var(--svg-fill);
}
.generic-dialog-list-button {
flex: 1;
padding: 20px;
background: var(--settings-list-item-bg);
border: none;
margin: 0;
display: flex;
flex-direction: row;
}
.generic-dialog-list-button span {
flex: 1;
text-align: left;
margin-left: 20px;
}
.generic-dialog-list-button:hover {
background: var(--settings-list-item-bg-hover);
}
.generic-dialog-list-button:active {
background: var(--settings-list-item-bg-active);
}
</style>

View file

@ -1,86 +1,28 @@
<ModalDialog :label :shown :closed :title background="var(--main-bg)"> <ModalDialog :label :shown :closed :title background="var(--main-bg)">
<ul class="post-privacy"> <GenericDialogList :items on:click="onClick(event)" />
{{#each postPrivacyOptions as option}}
<li class="post-privacy-item">
<button class="post-privacy-button" on:click="onClick(option)">
<svg>
<use xlink:href="{{option.icon}}" />
</svg>
<span>
{{option.label}}
</span>
<svg class="{{isSelected(option, postPrivacy) ? '' : 'hidden'}}"
aria-hidden="{{!isSelected(option, postPrivacy)}}">
<use xlink:href="#fa-check" />
</svg>
</button>
</li>
{{/each}}
</ul>
</ModalDialog> </ModalDialog>
<style>
.post-privacy {
list-style: none;
width: 100%;
border: 1px solid var(--settings-list-item-border);
box-sizing: border-box;
min-width: 300px;
max-width: calc(100vw - 20px);
}
.post-privacy-item {
border: 1px solid var(--settings-list-item-border);
font-size: 1.2em;
display: flex;
}
.post-privacy-item svg {
width: 24px;
height: 24px;
fill: var(--svg-fill);
}
.post-privacy-button {
flex: 1;
padding: 20px;
background: var(--settings-list-item-bg);
border: none;
margin: 0;
display: flex;
flex-direction: row;
}
.post-privacy-button span {
flex: 1;
text-align: left;
margin-left: 20px;
}
.post-privacy-button:hover {
background: var(--settings-list-item-bg-hover);
}
.post-privacy-button:active {
background: var(--settings-list-item-bg-active);
}
</style>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import { store } from '../../_store/store' import { store } from '../../_store/store'
import { POST_PRIVACY_OPTIONS } from '../../_static/statuses' import { POST_PRIVACY_OPTIONS } from '../../_static/statuses'
import { setPostPrivacy } from '../../_actions/postPrivacy' import { setPostPrivacy } from '../../_actions/postPrivacy'
import GenericDialogList from './GenericDialogList.html'
export default { export default {
components: { components: {
ModalDialog ModalDialog,
GenericDialogList
}, },
store: () => store, store: () => store,
data: () => ({ data: () => ({
postPrivacyOptions: POST_PRIVACY_OPTIONS postPrivacyOptions: POST_PRIVACY_OPTIONS
}), }),
helpers: {
isSelected: (option, postPrivacy) => postPrivacy.key === option.key
},
methods: { methods: {
async show() { async show() {
this.set({shown: true}) this.set({shown: true})
}, },
onClick(option) { onClick(item) {
setPostPrivacy(this.get('realm'), option.key) setPostPrivacy(this.get('realm'), item.key)
this.set({closed: true}) this.set({closed: true})
} }
}, },
@ -91,6 +33,14 @@
}, },
postPrivacyKey: (composeData, $currentVerifyCredentials) => { postPrivacyKey: (composeData, $currentVerifyCredentials) => {
return composeData.postPrivacy || $currentVerifyCredentials.source.privacy return composeData.postPrivacy || $currentVerifyCredentials.source.privacy
},
items: (postPrivacy, postPrivacyOptions) => {
return postPrivacyOptions.map(option => ({
key: option.key,
label: option.label,
icon: option.icon,
selected: postPrivacy.key === option.key
}))
} }
} }
} }

View file

@ -0,0 +1,52 @@
<ModalDialog :label :shown :closed :title background="var(--main-bg)">
<GenericDialogList :items on:click="onClick(event)"/>
</ModalDialog>
<script>
import ModalDialog from './ModalDialog.html'
import { store } from '../../_store/store'
import GenericDialogList from './GenericDialogList.html'
import { setAccountFollowed } from '../../_actions/follow'
export default {
computed: {
relationship: ($currentAccountRelationship) => $currentAccountRelationship,
account: ($currentAccountProfile) => $currentAccountProfile,
following: (relationship) => relationship && !!relationship.following,
accountName: (account) => account && (account.display_name || account.acct),
accountId: (account) => account && account.id,
followLabel: (following, accountName) => {
if (typeof following === 'undefined' || !accountName) {
return ''
}
return following ? `Unfollow ${accountName}` : `Follow ${accountName}`
},
items: (followLabel, following) => {
return [
{
key: 'follow',
label: followLabel,
icon: following ? '#fa-user-times' : '#fa-user-plus'
}
]
}
},
components: {
ModalDialog,
GenericDialogList
},
store: () => store,
methods: {
async show() {
this.set({shown: true})
},
async onClick(item) {
if (item.key === 'follow') {
let accountId = this.get('accountId')
let following = this.get('following')
await setAccountFollowed(accountId, !following, true)
this.set({closed: true})
}
}
}
}
</script>

View file

@ -3,3 +3,4 @@ export * from './showImageDialog'
export * from './showVideoDialog' export * from './showVideoDialog'
export * from './showEmojiDialog' export * from './showEmojiDialog'
export * from './showPostPrivacyDialog' export * from './showPostPrivacyDialog'
export * from './showStatusOptionsDialog'

View file

@ -0,0 +1,13 @@
import StatusOptionsDialog from './StatusOptionsDialog.html'
export function showStatusOptionsDialog (statusId) {
let dialog = new StatusOptionsDialog({
target: document.getElementById('modal-dialog'),
data: {
label: 'Status options dialog',
title: 'Status options',
statusId: statusId
}
})
dialog.show()
}

View file

@ -4,7 +4,6 @@
href="#fa-reply" href="#fa-reply"
disabled="{{disableReply}}" disabled="{{disableReply}}"
delegateKey="{{replyKey}}" delegateKey="{{replyKey}}"
ref:replyNode
/> />
<IconButton <IconButton
label="{{reblogLabel}}" label="{{reblogLabel}}"
@ -13,7 +12,6 @@
disabled="{{reblogDisabled}}" disabled="{{reblogDisabled}}"
href="{{reblogIcon}}" href="{{reblogIcon}}"
delegateKey="{{reblogKey}}" delegateKey="{{reblogKey}}"
ref:reblogNode
/> />
<IconButton <IconButton
label="Favorite" label="Favorite"
@ -21,11 +19,11 @@
pressed="{{favorited}}" pressed="{{favorited}}"
href="#fa-star" href="#fa-star"
delegateKey="{{favoriteKey}}" delegateKey="{{favoriteKey}}"
ref:favoriteNode
/> />
<IconButton <IconButton
label="Show more actions" label="Show more options"
href="#fa-ellipsis-h" href="#fa-ellipsis-h"
delegateKey="{{optionsKey}}"
/> />
</div> </div>
<style> <style>
@ -45,17 +43,21 @@
import { setFavorited } from '../../_actions/favorite' import { setFavorited } from '../../_actions/favorite'
import { setReblogged } from '../../_actions/reblog' import { setReblogged } from '../../_actions/reblog'
import { goto } from 'sapper/runtime.js' import { goto } from 'sapper/runtime.js'
import { importDialogs } from '../../_utils/asyncModules'
import { updateProfileAndRelationship } from '../../_actions/accounts'
export default { export default {
oncreate() { oncreate() {
registerClickDelegate(this.get('favoriteKey'), () => this.onFavoriteClick()) registerClickDelegate(this.get('favoriteKey'), () => this.onFavoriteClick())
registerClickDelegate(this.get('reblogKey'), () => this.onReblogClick()) registerClickDelegate(this.get('reblogKey'), () => this.onReblogClick())
registerClickDelegate(this.get('replyKey'), () => this.onReplyClick()) registerClickDelegate(this.get('replyKey'), () => this.onReplyClick())
registerClickDelegate(this.get('optionsKey'), () => this.onOptionsClick())
}, },
ondestroy() { ondestroy() {
unregisterClickDelegate(this.get('favoriteKey')) unregisterClickDelegate(this.get('favoriteKey'))
unregisterClickDelegate(this.get('reblogKey')) unregisterClickDelegate(this.get('reblogKey'))
unregisterClickDelegate(this.get('replyKey')) unregisterClickDelegate(this.get('replyKey'))
unregisterClickDelegate(this.get('optionsKey'))
}, },
components: { components: {
IconButton IconButton
@ -75,6 +77,14 @@
onReplyClick() { onReplyClick() {
let statusId = this.get('statusId') let statusId = this.get('statusId')
goto(`/statuses/${statusId}/reply`) goto(`/statuses/${statusId}/reply`)
},
async onOptionsClick() {
let statusId = this.get('statusId')
let accountId = this.get('accountId')
let updateRelationshipPromise = updateProfileAndRelationship(accountId)
let dialogs = await importDialogs()
await updateRelationshipPromise
dialogs.showStatusOptionsDialog(statusId)
} }
}, },
computed: { computed: {
@ -115,10 +125,13 @@
return status.favourited return status.favourited
}, },
statusId: (status) => status.id, statusId: (status) => status.id,
accountId: (status) => status.account.id,
disableReply: (statusId, timelineType, timelineValue) => timelineType === 'reply' && statusId === timelineValue, disableReply: (statusId, timelineType, timelineValue) => timelineType === 'reply' && statusId === timelineValue,
favoriteKey: (statusId, timelineType, timelineValue) => `fav-${timelineType}-${timelineValue}-${statusId}`, keyPrefix: (timelineType, timelineValue, statusId) => `${timelineType}-${timelineValue}-${statusId}`,
reblogKey: (statusId, timelineType, timelineValue) => `reblog-${timelineType}-${timelineValue}-${statusId}`, favoriteKey: (keyPrefix) => `${keyPrefix}-fav`,
replyKey: (statusId, timelineType, timelineValue) => `reply-${timelineType}-${timelineValue}-${statusId}`, reblogKey: (keyPrefix) => `${keyPrefix}-reblog`,
replyKey: (keyPrefix) => `${keyPrefix}-reply`,
optionsKey: (keyPrefix) => `${keyPrefix}-options`,
} }
} }
</script> </script>

View file

@ -8,9 +8,9 @@ test('Changes post privacy', async t => {
await t.useRole(foobarRole) await t.useRole(foobarRole)
.expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)') .expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)')
.click(postPrivacyButton) .click(postPrivacyButton)
.click('.post-privacy li:nth-child(2) button') .click('.generic-dialog-list li:nth-child(2) button')
.expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Unlisted)') .expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Unlisted)')
.click(postPrivacyButton) .click(postPrivacyButton)
.click('.post-privacy li:nth-child(1) button') .click('.generic-dialog-list li:nth-child(1) button')
.expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)') .expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)')
}) })