Skip to content

feat(circle-details): allow setting avatar image for circle#5293

Draft
cristianscheid wants to merge 1 commit into
mainfrom
feat/2433/circle-preview-avatar
Draft

feat(circle-details): allow setting avatar image for circle#5293
cristianscheid wants to merge 1 commit into
mainfrom
feat/2433/circle-preview-avatar

Conversation

@cristianscheid
Copy link
Copy Markdown
Member

@cristianscheid cristianscheid commented Apr 30, 2026

Summary

  • Adds support for setting avatar image for circle

Screenshots

Screenshot from 2026-05-12 07-15-12 Screenshot from 2026-05-12 07-15-21
Screenshot from 2026-05-12 07-15-34 Screenshot from 2026-05-12 07-15-44
demo.webm

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

❌ Patch coverage is 0% with 81 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/components/CircleDetails.vue 0.00% 81 Missing ⚠️

📢 Thoughts on this report? Let us know!

@cristianscheid cristianscheid force-pushed the feat/2433/circle-preview-avatar branch from 2a114c1 to eb90eef Compare May 4, 2026 14:16
@cristianscheid
Copy link
Copy Markdown
Member Author

cristianscheid commented May 4, 2026

@kra-mo following your suggestion:

I positioned the avatar buttons beneath the name and description in edit mode and styled them closer to the reference in this issue. The PR description has been updated with screenshots and a short video showing the current state.

Two questions I'd appreciate your input on:

  1. How should we handle the button widths?
  2. Right now, selecting an image opens the cropper and clicking "Set as team picture" saves immediately to the backend. Should it instead only save when the main edit mode "Save" button is clicked?

Besides that, I'm also aware that probably a spacing between the buttons is needed and also some margin from the main edit mode "Cancel" and "Save" buttons. Just waiting for another round of input before moving forward.

Any other thoughts are welcome :)

@cristianscheid cristianscheid force-pushed the feat/2433/circle-preview-avatar branch from eb90eef to a5892d4 Compare May 4, 2026 17:06
@kra-mo
Copy link
Copy Markdown
Member

kra-mo commented May 11, 2026

How should we handle the button widths?

The widths should definitely be homogeneous.

They should also have margins between them.

Right now, selecting an image opens the cropper and clicking "Set as team picture" saves immediately to the backend. Should it instead only save when the main edit mode "Save" button is clicked?

Since there is a "Cancel" button, the second behavior makes more sense.

@cristianscheid cristianscheid force-pushed the feat/2433/circle-preview-avatar branch 5 times, most recently from 927fa24 to a7b202b Compare May 12, 2026 11:09
@cristianscheid
Copy link
Copy Markdown
Member Author

@kra-mo thanks for the input, really appreciated! I've adopted your suggestions and updated the implementation. Also updated the main comment of this PR with new screenshots and a short video demoing the current state. Would appreciate if you could take a look at it and share your thoughts.

Signed-off-by: Cristian Scheid <cristianscheid@gmail.com>
@cristianscheid cristianscheid force-pushed the feat/2433/circle-preview-avatar branch from a7b202b to ccadcf6 Compare May 12, 2026 13:47
@cristianscheid cristianscheid self-assigned this May 13, 2026
@cristianscheid cristianscheid added enhancement New feature or request 2. developing Work in progress labels May 13, 2026
'circle.id': {
handler() {
this.fetchTeamResources()
this.loadAvatarUrl()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I start editing one team, pick a picture, then click another team in the sidebar before saving, the pending blob carries over and saves to the wrong team. Watcher should call clearPendingAvatar() (and probably reset isEditing too) when the id actually changes.

v-bind="cropperOptions" />
<div class="circle-avatar-cropper-buttons">
<NcButton @click="cancelSetAvatar">
{{ t('settings', 'Cancel') }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three (Cancel, Apply, Error cropping avatar picture) are still in the settings domain. I might be wrong but I suspect won't be picked up by the contacts gettext catalog, so they stay English regardless of locale. Should be t('contacts', ...) maybe?

async onAvatarInputChange(e) {
try {
const file = e.target.files[0]
if (!['image/png', 'image/jpeg'].includes(file.type)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You already have VALID_MIME_TYPES at the top of the file, can we use it here too? Otherwise the picker filter and this check can drift.

const canvasData = this.$refs.cropper.getCroppedCanvas()
const scaleFactor = canvasData.width > 512 ? 512 / canvasData.width : 1

this.$refs.cropper.scale(scaleFactor, scaleFactor).getCroppedCanvas().toBlob(async (blob) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cropper has getCroppedCanvas({ maxWidth: 512, maxHeight: 512 }) for exactly this, no need to mutate the cropper state. Same approach is used in ContactDetailsAvatar.vue around line 425.

</NcButton>
<input
ref="avatarInput"
type="file"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth setting accept="image/png,image/jpeg" here so the native OS dialog only shows valid files. Cheap UX improvement, and the MIME check stays as the actual gate. Not a blocker for this PR though :)

One way to do it could be in setup:

setup() {
    const avatarList = ref()
    const { width } = useElementSize(avatarList)
    return { avatarList, width, avatarAccept: VALID_MIME_TYPES.join(',') }
}

and then

<input
    ref="avatarInput"
    type="file"
    :accept="avatarAccept"
    class="hidden-visually"
    @change="onAvatarInputChange">

this.loadAvatarUrl()
} catch {
console.error('Unable to save avatar picture')
errors.push('avatar')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is untranslated, but so are the existing name and description fields. If you have time it could be good to fix them, but not blocking for PR

const picker = getFilePickerBuilder(t('contacts', 'Choose a team picture'))
.setMultiSelect(false)
.setMimeTypeFilter(VALID_MIME_TYPES)
.setType(1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik FilePickerType.Choose is exported from @nextcloud/dialogs. Reads better and won't silently break if the underlying values ever shift

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2. developing Work in progress enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support associating a "preview" picture to teams

3 participants