-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add feature of tagging users in photos (#838)
* Add feature of tagging users in photos * Only show tag button if any users tagged * Hide tagging by clicking outside area, add profile link, fix XSS warning * Bugfixes, lint * apply suggestions and comments --------- Co-authored-by: DrumsnChocolate <[email protected]>
- Loading branch information
1 parent
b751aa4
commit 0a74842
Showing
32 changed files
with
372 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Ability } from 'ember-can'; | ||
import { isNone } from '@ember/utils'; | ||
|
||
export default class PhotoTag extends Ability { | ||
get canShow() { | ||
return this.session.hasPermission('photo-tag.read'); | ||
} | ||
|
||
get canCreate() { | ||
return this.session.hasPermission('photo-tag.create'); | ||
} | ||
|
||
get canDestroy() { | ||
return ( | ||
this.session.hasPermission('photo-tag.destroy') || | ||
this.isTagOwner(this.model) || | ||
this.isTagged(this.model) | ||
); | ||
} | ||
|
||
isTagOwner(photoTag) { | ||
const { currentUser } = this.session; | ||
return ( | ||
!isNone(currentUser) && | ||
photoTag.get('author.id') === currentUser.get('id') | ||
); | ||
} | ||
|
||
isTagged(photoTag) { | ||
const { currentUser } = this.session; | ||
return ( | ||
!isNone(currentUser) && | ||
photoTag.get('taggedUser.id') === currentUser.get('id') | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<div class="position-relative photo-tags | ||
{{if showTags 'photo-tags--show'}}" | ||
{{on 'click' this.addTag}} | ||
{{did-insert this.addCloseAddTagListener}} | ||
{{will-destroy this.removeCloseAddTagListener}} | ||
> | ||
{{yield}} | ||
|
||
{{#if (gt @model.amountOfTags 0)}} | ||
<button class="btn btn-info photo-tags-button" type="button" {{on 'click' this.toggleShowTags}}> | ||
<FaIcon @icon='tag' /> | ||
{{ @model.amountOfTags }} | ||
</button> | ||
{{/if}} | ||
|
||
{{#each @model.tags as |tag|}} | ||
<div class="photo-tag" style={{ tag.tagStyle }}> | ||
<LinkTo @route='users.user.photos' @model={{tag.taggedUser.id}}> | ||
{{ tag.taggedUser.fullName }} | ||
</LinkTo> | ||
{{#if (can 'destroy photo-tag' tag)}} | ||
<FaIcon @icon='xmark' {{ on 'click' (fn this.deleteTag tag) }} /> | ||
{{/if}} | ||
</div> | ||
{{/each}} | ||
|
||
{{#if this.newTagStyle }} | ||
<div class="photo-tag photo-tag--new" style={{ this.newTagStyle }}> | ||
<PowerSelect | ||
@options={{this.users}} | ||
@onChange={{this.storeTag}} | ||
@searchEnabled={{true}} | ||
@searchField='fullName' | ||
@registerAPI={{this.openUserSelect}} | ||
as |user| | ||
> | ||
{{user.fullName}} | ||
</PowerSelect> | ||
</div> | ||
{{/if}} | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import Component from '@glimmer/component'; | ||
import { inject as service } from '@ember/service'; | ||
import { tracked } from '@glimmer/tracking'; | ||
import { action } from '@ember/object'; | ||
import { next } from '@ember/runloop'; | ||
import { htmlSafe } from '@ember/template'; | ||
|
||
export default class PhotoTags extends Component { | ||
@service store; | ||
@service flashNotice; | ||
@tracked newTagX; | ||
@tracked newTagY; | ||
@tracked selectApi; | ||
@tracked showTags = false; | ||
|
||
get users() { | ||
return this.store.findAll('user'); | ||
} | ||
|
||
@action | ||
toggleShowTags() { | ||
this.showTags = !this.showTags; | ||
} | ||
|
||
@action | ||
addTag(e) { | ||
if (e.target.tagName.toLowerCase() != 'img' || this.newTagX || this.newTagY) | ||
return; | ||
e.stopPropagation(); | ||
let x = (e.layerX / e.target.width) * 100; | ||
let y = (e.layerY / e.target.height) * 100; | ||
this.newTagX = x; | ||
this.newTagY = y; | ||
next(this, () => { | ||
this.selectApi.actions.open(); | ||
}); | ||
} | ||
|
||
@action | ||
addCloseAddTagListener() { | ||
this.closeAddTagListener = (e) => { | ||
let element = e.target; | ||
if ( | ||
element.closest('.ember-power-select-dropdown') !== null || | ||
element.closest('.photo-tag--new') !== null | ||
) | ||
return; | ||
e.stopPropagation(); | ||
this.newTagX = null; | ||
this.newTagY = null; | ||
console.log('Closed tag', element); | ||
}; | ||
|
||
document.addEventListener('click', this.closeAddTagListener); | ||
} | ||
|
||
@action | ||
removeCloseAddTagListener() { | ||
document.removeEventListener('click', this.closeAddTagListener); | ||
} | ||
|
||
@action | ||
async storeTag(taggedUser) { | ||
let photo = this.args.model; | ||
let photoTag = this.store.createRecord('photo-tag', { | ||
photo, | ||
taggedUser, | ||
x: this.newTagX, | ||
y: this.newTagY, | ||
}); | ||
this.newTagX = null; | ||
this.newTagY = null; | ||
|
||
try { | ||
await photoTag.save(); | ||
this.flashNotice.sendSuccess('Tag opgeslagen!'); | ||
photo.reload(); | ||
this.showTags = true; | ||
} catch (e) { | ||
this.flashNotice.sendError( | ||
'Tag opslaan mislukt. Is deze gebruiker al getagged?' | ||
); | ||
photoTag.deleteRecord(); | ||
} | ||
} | ||
|
||
@action | ||
async deleteTag(tag) { | ||
try { | ||
tag.deleteRecord(); | ||
await tag.save(); | ||
this.flashNotice.sendSuccess('Tag verwijderd!'); | ||
this.args.model.reload(); | ||
} catch (e) { | ||
this.flashNotice.sendError('Tag verwijderen mislukt.'); | ||
tag.rollbackAttributes(); | ||
} | ||
} | ||
|
||
@action | ||
openUserSelect(userSelect) { | ||
if (this.selectApi == null) { | ||
this.selectApi = userSelect; | ||
} | ||
} | ||
|
||
get newTagStyle() { | ||
if (!this.newTagX || !this.newTagY) return null; | ||
return htmlSafe( | ||
`left: ${parseFloat(this.newTagX)}%; top: ${parseFloat(this.newTagY)}%;` | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import Model, { belongsTo, attr } from '@ember-data/model'; | ||
|
||
export default class PhotoTag extends Model { | ||
// Properties | ||
@attr x; | ||
@attr y; | ||
@attr('date') updatedAt; | ||
@attr('date') createdAt; | ||
|
||
// Relations | ||
@belongsTo('user') author; | ||
@belongsTo('user') taggedUser; | ||
@belongsTo photo; | ||
|
||
get tagStyle() { | ||
return `left: ${parseFloat(this.x)}%; top: ${parseFloat(this.y)}%;`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import UserIndexRoute from './index'; | ||
|
||
export default class UserPhotosRoute extends UserIndexRoute { | ||
pageActions = null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
.photo-tag { | ||
position: absolute; | ||
transform: translate(-50%, -50%); | ||
transition: 0.5s ease opacity; | ||
opacity: 0; | ||
border-radius: 10px; | ||
background: rgb(0 0 0 / 80%); | ||
padding: 5px 10px; | ||
color: #fff; | ||
font-size: 12px; | ||
pointer-events: none; | ||
|
||
&--new { | ||
opacity: 1; | ||
z-index: 2; | ||
min-width: 200px; | ||
pointer-events: auto; | ||
} | ||
|
||
&:hover { | ||
z-index: 1; | ||
} | ||
|
||
&:has(.fa-xmark) { | ||
padding-right: 25px; | ||
} | ||
|
||
.fa-xmark { | ||
position: absolute; | ||
top: 50%; | ||
right: 10px; | ||
transform: translateY(-50%); | ||
cursor: pointer; | ||
} | ||
} | ||
|
||
.photo-tags.photo-tags--show .photo-tag { | ||
opacity: 1; | ||
pointer-events: auto; | ||
} | ||
|
||
.photo-tags-button { | ||
position: absolute; | ||
top: 8px; | ||
right: 8px; | ||
color: #fff; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
<TabbedView @tabItems={{tabItems}}> | ||
<TabbedView @tabItems={{this.tabItems}}> | ||
<Users::PrivacySettings @model={{model}} @onSubmit={{action 'submit'}} @onCancel={{action 'cancel'}}/> | ||
</TabbedView> |
Oops, something went wrong.