proper resets, better upload dialog

This commit is contained in:
Henry Jameson 2025-08-04 23:15:26 +03:00
commit d89f564b5e
4 changed files with 199 additions and 227 deletions

View file

@ -15,7 +15,7 @@ import RichContent from 'src/components/rich_content/rich_content.jsx'
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue' import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import EmojiInput from 'src/components/emoji_input/emoji_input.vue' import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
import Modal from 'src/components/modal/modal.vue' import DialogModal from 'src/components/dialog_modal/dialog_modal.vue'
import ImageCropper from 'src/components/image_cropper/image_cropper.vue' import ImageCropper from 'src/components/image_cropper/image_cropper.vue'
import localeService from 'src/services/locale/locale.service.js' import localeService from 'src/services/locale/locale.service.js'
@ -37,8 +37,6 @@ import {
faExpandAlt, faExpandAlt,
faBirthdayCake, faBirthdayCake,
faSave, faSave,
faChevronRight,
faChevronDown,
faClockRotateLeft faClockRotateLeft
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
@ -55,8 +53,6 @@ library.add(
faTimes, faTimes,
faExpandAlt, faExpandAlt,
faBirthdayCake, faBirthdayCake,
faChevronRight,
faChevronDown,
faClockRotateLeft faClockRotateLeft
) )
@ -74,7 +70,7 @@ export default {
'hasNoteEditor' 'hasNoteEditor'
], ],
components: { components: {
Modal, DialogModal,
UserAvatar, UserAvatar,
Checkbox, Checkbox,
RemoteFollow, RemoteFollow,
@ -100,22 +96,27 @@ export default {
muteExpiryUnit: 'minutes', muteExpiryUnit: 'minutes',
// Editable stuff // Editable stuff
editImage: false,
newName: user.name_unescaped, newName: user.name_unescaped,
editingName: false, editingName: false,
editImage: false,
newAvatar: '',
newAvatarFile: null,
newBanner: '',
newBannerFile: null,
newActorType: user.actor_type,
newBio: unescape(user.description), newBio: unescape(user.description),
editingBio: false, editingBio: false,
newAvatar: null,
newAvatarFile: null,
newBanner: null,
newBannerFile: null,
newActorType: user.actor_type,
newBirthday: user.birthday, newBirthday: user.birthday,
newShowBirthday: user.show_birthday, newShowBirthday: user.show_birthday,
newCoverPhoto: user.cover_photo, newShowRole: user.show_role,
newFields: user.fields?.map(field => ({ name: field.name, value: field.value })), newFields: user.fields?.map(field => ({ name: field.name, value: field.value })),
editingFields: false, editingFields: false,
newShowRole: user.show_role,
} }
}, },
created () { created () {
@ -248,24 +249,20 @@ export default {
// Editable stuff // Editable stuff
avatarImgSrc () { avatarImgSrc () {
const src = this.newAvatar const currentUrl = this.user.profile_image_url_original || this.defaultAvatar
return (!src) ? (this.user.profile_image_url_original || this.defaultAvatar) : src const newUrl = this.newAvatar === '' ? this.defaultAvatar : this.newAvatar
return (this.newAvatar === null) ? currentUrl : newUrl
}, },
bannerImgSrc () { bannerImgSrc () {
const src = this.newBanner const currentUrl = this.user.cover_photo || this.defaultBanner
return (!src) ? (this.user.cover_photo || this.defaultBanner) : src const newUrl = this.newBanner === '' ? this.defaultBanner : this.newBanner
return (this.newBanner === null) ? currentUrl : newUrl
}, },
defaultAvatar () { defaultAvatar () {
if (this.isDefaultAvatar) { return this.$store.state.instance.server + this.$store.state.instance.defaultAvatar
return this.$store.state.instance.server + this.$store.state.instance.defaultAvatar
}
return this.user.profile_image_url
}, },
defaultBanner () { defaultBanner () {
if (this.isDefaultBanner) { return this.$store.state.instance.server + this.$store.state.instance.defaultBanner
return this.$store.state.instance.server + this.$store.state.instance.defaultBanner
}
return this.user.cover_photo
}, },
isDefaultAvatar () { isDefaultAvatar () {
const baseAvatar = this.$store.state.instance.defaultAvatar const baseAvatar = this.$store.state.instance.defaultAvatar
@ -366,24 +363,6 @@ export default {
changeBanner () { changeBanner () {
this.editImage = 'banner' this.editImage = 'banner'
}, },
resetAvatar () {
const confirmed = window.confirm(this.$t('settings.reset_avatar_confirm'))
if (confirmed) {
this.submitAvatar(undefined, '')
}
},
resetBanner () {
const confirmed = window.confirm(this.$t('settings.reset_banner_confirm'))
if (confirmed) {
this.submitBanner('')
}
},
resetBackground () {
const confirmed = window.confirm(this.$t('settings.reset_background_confirm'))
if (confirmed) {
this.submitBackground('')
}
},
submitImage ({ canvas, file }) { submitImage ({ canvas, file }) {
if (canvas) { if (canvas) {
return canvas.toBlob((data) => this.submitImage({ canvas: null, file: data })) return canvas.toBlob((data) => this.submitImage({ canvas: null, file: data }))
@ -407,6 +386,16 @@ export default {
reader.readAsDataURL(file) reader.readAsDataURL(file)
}, },
resetImage () {
if (this.editImage === 'avatar') {
this.newAvatar = ''
this.newAvatarFile = ''
} else {
this.newBanner = ''
this.newBannerFile = ''
}
this.editImage = false
},
addField () { addField () {
if (this.newFields.length < this.maxFields) { if (this.newFields.length < this.maxFields) {
this.newFields.push({ name: '', value: '' }) this.newFields.push({ name: '', value: '' })
@ -423,21 +412,24 @@ export default {
cancelImageText () { cancelImageText () {
return return
}, },
resetNews () { resetState () {
const user = this.$store.state.users.currentUser const user = this.$store.state.users.currentUser
this.newName = user.name_unescaped this.newName = user.name_unescaped
this.newAvatar = ''
this.newAvatarFile = null
this.newBanner = ''
this.newBannerFile = null
this.newActorType = user.actor_type
this.newBio = unescape(user.description) this.newBio = unescape(user.description)
this.newAvatar = null
this.newAvatarFile = null
this.newBanner = null
this.newBannerFile = null
this.newActorType = user.actor_type
this.newBirthday = user.birthday this.newBirthday = user.birthday
this.newShowBirthday = user.show_birthday this.newShowBirthday = user.show_birthday
this.newCoverPhoto = user.cover_photo
this.newFields = user.fields.map(field => ({ name: field.name, value: field.value }))
this.newShowRole = user.show_role this.newShowRole = user.show_role
this.newFields = user.fields.map(field => ({ name: field.name, value: field.value }))
}, },
updateProfile () { updateProfile () {
const params = { const params = {
@ -455,11 +447,11 @@ export default {
params.actor_type = this.actorType params.actor_type = this.actorType
} }
if (this.newAvatar) { if (this.newAvatarFile !== null) {
params.avatar = this.newAvatarFile params.avatar = this.newAvatarFile
} }
if (this.newBanner) { if (this.newBannerFile !== null) {
params.header = this.newBannerFile params.header = this.newBannerFile
} }
@ -474,7 +466,7 @@ export default {
merge(this.newFields, user.fields) merge(this.newFields, user.fields)
this.$store.commit('addNewUsers', [user]) this.$store.commit('addNewUsers', [user])
this.$store.commit('setCurrentUser', user) this.$store.commit('setCurrentUser', user)
this.resetNews() this.resetState()
}) })
.catch((error) => { .catch((error) => {
this.displayUploadError(error) this.displayUploadError(error)

View file

@ -410,7 +410,7 @@
.user-interactions { .user-interactions {
position: relative; position: relative;
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(7.5em, 20%)); grid-template-columns: repeat(auto-fit, minmax(5em, 40%));
grid-gap: 0.6em; grid-gap: 0.6em;
max-width: 98vw; max-width: 98vw;
margin-bottom: 0.6em; margin-bottom: 0.6em;
@ -568,11 +568,9 @@
object-fit: contain; object-fit: contain;
} }
.images-container { .image-container {
display: grid; display: flex;
margin: 1em; margin: 0 1em 0.5em;
grid-template-columns: 1fr 5em 1fr;
grid-template-rows: 20em;
gap: 0.5em; gap: 0.5em;
.new-image { .new-image {

View file

@ -19,10 +19,7 @@
class="user-info-avatar -link" class="user-info-avatar -link"
@click="zoomAvatar" @click="zoomAvatar"
> >
<UserAvatar <UserAvatar :user="user" />
:user="user"
:url="avatarImgSrc"
/>
<div class="user-info-avatar -link -overlay"> <div class="user-info-avatar -link -overlay">
<FAIcon <FAIcon
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
@ -36,7 +33,10 @@
:class="{ '-editable': editable }" :class="{ '-editable': editable }"
@click="changeAvatar" @click="changeAvatar"
> >
<UserAvatar :user="user" /> <UserAvatar
:user="user"
:url="avatarImgSrc"
/>
<div class="user-info-avatar -link -overlay"> <div class="user-info-avatar -link -overlay">
<FAIcon <FAIcon
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
@ -76,34 +76,6 @@
:title="$t('user_card.change_banner')" :title="$t('user_card.change_banner')"
/> />
</button> </button>
<button
v-if="editable"
:disabled="somethingToSave"
class="btn button-unstyled reset-profile-button"
@click="resetNews"
>
{{ $t('settings.reset') }}
<FAIcon
fixed-width
class="icon"
icon="clock-rotate-left"
:title="$t('user_card.edit_profile')"
/>
</button>
<button
v-if="editable"
:disabled="somethingToSave"
class="btn button-unstyled save-profile-button"
@click="updateProfile"
>
{{ $t('settings.save') }}
<FAIcon
fixed-width
class="icon"
icon="save"
:title="$t('user_card.edit_profile')"
/>
</button>
<button <button
v-else-if="!editable && !isOtherUser && user.is_local" v-else-if="!editable && !isOtherUser && user.is_local"
class="button-unstyled edit-profile-button" class="button-unstyled edit-profile-button"
@ -256,72 +228,102 @@
</div> </div>
</div> </div>
<div <div
v-if="!editable && loggedIn && isOtherUser" v-if="loggedIn"
class="user-interactions" class="user-interactions"
> >
<div class="btn-group"> <template v-if="isOtherUser">
<FollowButton <div class="btn-group">
:relationship="relationship" <FollowButton
:relationship="relationship"
:user="user"
/>
<template v-if="relationship.following">
<ProgressButton
v-if="!relationship.notifying"
class="btn button-default"
:click="subscribeUser"
:title="$t('user_card.subscribe')"
>
<FAIcon icon="bell" />
</ProgressButton>
<ProgressButton
v-else
class="btn button-default toggled"
:click="unsubscribeUser"
:title="$t('user_card.unsubscribe')"
>
<FALayers>
<FAIcon
icon="rss"
transform="left-5 shrink-6 up-3 rotate-20"
flip="horizontal"
/>
<FAIcon
icon="rss"
transform="right-5 shrink-6 up-3 rotate-20"
/>
<FAIcon icon="bell" />
</FALayers>
</ProgressButton>
</template>
</div>
<button
v-if="relationship.muting"
class="btn button-default btn-mute toggled"
:disabled="user.deactivated"
@click="unmuteUser"
>
{{ $t('user_card.muted') }}
</button>
<button
v-else
class="btn button-default btn-mute"
:disabled="user.deactivated"
@click="muteUser"
>
{{ $t('user_card.mute') }}
</button>
<button
class="btn button-default btn-mention"
:disabled="user.deactivated"
@click="mentionUser"
>
{{ $t('user_card.mention') }}
</button>
<ModerationTools
v-if="showModerationMenu"
class="moderation-menu"
:user="user" :user="user"
/> />
<template v-if="relationship.following"> </template>
<ProgressButton
v-if="!relationship.notifying"
class="btn button-default"
:click="subscribeUser"
:title="$t('user_card.subscribe')"
>
<FAIcon icon="bell" />
</ProgressButton>
<ProgressButton
v-else
class="btn button-default toggled"
:click="unsubscribeUser"
:title="$t('user_card.unsubscribe')"
>
<FALayers>
<FAIcon
icon="rss"
transform="left-5 shrink-6 up-3 rotate-20"
flip="horizontal"
/>
<FAIcon
icon="rss"
transform="right-5 shrink-6 up-3 rotate-20"
/>
<FAIcon icon="bell" />
</FALayers>
</ProgressButton>
</template>
</div>
<button <button
v-if="relationship.muting" v-if="editable"
class="btn button-default btn-mute toggled" :disabled="somethingToSave"
:disabled="user.deactivated" class="btn button-default reset-profile-button"
@click="unmuteUser" @click="resetState"
> >
{{ $t('user_card.muted') }} {{ $t('settings.reset') }}
<FAIcon
fixed-width
class="icon"
icon="clock-rotate-left"
:title="$t('user_card.edit_profile')"
/>
</button> </button>
<button <button
v-else v-if="editable"
class="btn button-default btn-mute" :disabled="somethingToSave"
:disabled="user.deactivated" class="btn button-default save-profile-button"
@click="muteUser" @click="updateProfile"
> >
{{ $t('user_card.mute') }} {{ $t('settings.save') }}
<FAIcon
fixed-width
class="icon"
icon="save"
:title="$t('user_card.edit_profile')"
/>
</button> </button>
<button
class="btn button-default btn-mention"
:disabled="user.deactivated"
@click="mentionUser"
>
{{ $t('user_card.mention') }}
</button>
<ModerationTools
v-if="showModerationMenu"
class="moderation-menu"
:user="user"
/>
</div> </div>
<div <div
v-if="!loggedIn && user.is_local" v-if="!loggedIn && user.is_local"
@ -355,7 +357,7 @@
:key="option" :key="option"
:value="option" :value="option"
> >
{{ $t('settings.actor_type_' + option) }} {{ $t('settings.actor_type_' + (option === 'Person' ? 'person_proper' : option)) }}
</option> </option>
</Select> </Select>
<div v-if="groupActorAvailable"> <div v-if="groupActorAvailable">
@ -653,85 +655,63 @@
/> />
</teleport> </teleport>
<teleport to="#modal"> <teleport to="#modal">
<Modal <DialogModal
v-if="editImage" v-if="editImage"
:is-mute="true"
class="edit-image" class="edit-image"
:class="{ '-banner': editImage === 'banner' }"
@backdrop-clicked="editImage = false"
> >
<div class="panel"> <template #header>
<div class="panel-heading"> {{ editImage === 'avatar' ? $t('settings.change_avatar') : $t('settings.change_banner') }}
<h1 class="title"> </template>
{{ editImage === 'avatar' ? $t('settings.change_avatar') : $t('settings.change_banner') }} <div class="image-container">
</h1> <image-cropper
</div> ref="cropper"
<div class="panel-body"> class="cropper"
<div class="images-container"> :aspect-ratio="editImage === 'avatar' ? 1 : 3"
<img @submit="submitImage"
:src="editImage === 'avatar' ? avatarImgSrc : bannerImgSrc" />
class="current-avatar"
/>
<FAIcon
class="separator"
:icon="editImage === 'avatar' ? 'chevron-right' : 'chevron-down'"
/>
<image-cropper
ref="cropper"
class="cropper"
:aspect-ratio="editImage === 'avatar' ? 1 : 3"
@submit="submitImage"
/>
</div>
<button
id="pick-image"
class="button-default btn"
type="button"
@click="() => this.$refs.cropper.pickImage()"
>
{{ $t('settings.upload_picture') }}
</button>
<p class="visibility-notice">
{{ $t('settings.avatar_size_instruction') }}
</p>
<button
:title="editImage === 'avatar' ? $t('settings.reset_avatar') : $t('settings.reset_banner')"
class="button-unstyled reset-button"
@click="resetImage"
>
</button>
<div class="panel-footer">
<div/>
<button
class="button-default btn"
type="button"
@click="editImage = false"
>
{{ this.$t('image_cropper.cancel') }}
</button>
<button
class="button-default btn"
type="button"
@click="this.$refs.cropper.submit(true)"
>
{{ $t('image_cropper.save') }}
</button>
<button
class="button-default btn"
type="button"
@click="this.$refs.cropper.submit(false)"
>
{{ $t('image_cropper.save_without_cropping') }}
</button>
<FAIcon
v-if="submitting"
spin
icon="circle-notch"
/>
</div>
</div>
</div> </div>
</Modal> <button
id="pick-image"
class="button-default btn"
type="button"
@click="() => this.$refs.cropper.pickImage()"
>
{{ $t('settings.upload_picture') }}
</button>
<p class="visibility-notice">
{{ editImage === 'avatar' ? $t('settings.avatar_size_instruction') : $t('settings.banner_size_instruction' )}}
</p>
<template #footer>
<button
class="button-default btn"
type="button"
@click="editImage = false"
>
{{ this.$t('image_cropper.cancel') }}
</button>
<button
:title="editImage === 'avatar' ? $t('settings.reset_avatar') : $t('settings.reset_banner')"
class="button-default btn reset-button"
@click="resetImage"
>
{{ editImage === 'avatar' ? $t('settings.reset_avatar') : $t('settings.reset_banner' )}}
</button>
<button
class="button-default btn"
type="button"
@click="this.$refs.cropper.submit(false)"
>
{{ $t('image_cropper.save_without_cropping') }}
</button>
<button
class="button-default btn"
type="button"
@click="this.$refs.cropper.submit(true)"
>
{{ $t('image_cropper.save') }}
</button>
</template>
</DialogModal>
</teleport> </teleport>
</div> </div>
</template> </template>

View file

@ -387,6 +387,7 @@
"actor_type": "This account is:", "actor_type": "This account is:",
"actor_type_description": "Marking your account as a group will make it automatically repeat statuses that mention it.", "actor_type_description": "Marking your account as a group will make it automatically repeat statuses that mention it.",
"actor_type_Person": "a normal user", "actor_type_Person": "a normal user",
"actor_type_person_proper": "a person",
"actor_type_Service": "a bot", "actor_type_Service": "a bot",
"actor_type_Group": "a group", "actor_type_Group": "a group",
"mobile_center_dialog": "Vertically center dialogs on mobile", "mobile_center_dialog": "Vertically center dialogs on mobile",
@ -568,7 +569,8 @@
"move_account_error": "Error moving account: {error}", "move_account_error": "Error moving account: {error}",
"discoverable": "Allow discovery of this account in search results and other services", "discoverable": "Allow discovery of this account in search results and other services",
"domain_mutes": "Domains", "domain_mutes": "Domains",
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels. Recommended aspect ratio is 1:1",
"banner_size_instruction": "The recommended minimum size for banner images is 450x150 pixels. Recommended aspect ratio is 3:1",
"pad_emoji": "Pad emoji with spaces when adding from picker", "pad_emoji": "Pad emoji with spaces when adding from picker",
"autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available", "autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available",
"unsaved_post_action": "When you try to close an unsaved posting form", "unsaved_post_action": "When you try to close an unsaved posting form",