diff options
11 files changed, 91 insertions, 78 deletions
diff --git a/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts index 32f6d650d..3326a1505 100644 --- a/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts +++ b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts | |||
@@ -13,6 +13,7 @@ import { FormReactiveService } from '@app/shared/shared-forms' | |||
13 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' | 13 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' |
14 | import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models' | 14 | import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models' |
15 | import { VideoChannelEdit } from './video-channel-edit' | 15 | import { VideoChannelEdit } from './video-channel-edit' |
16 | import { shallowCopy } from '@shared/core-utils' | ||
16 | 17 | ||
17 | @Component({ | 18 | @Component({ |
18 | selector: 'my-video-channel-update', | 19 | selector: 'my-video-channel-update', |
@@ -118,6 +119,9 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI | |||
118 | this.notifier.success($localize`Avatar changed.`) | 119 | this.notifier.success($localize`Avatar changed.`) |
119 | 120 | ||
120 | this.videoChannel.updateAvatar(data.avatars) | 121 | this.videoChannel.updateAvatar(data.avatars) |
122 | |||
123 | // So my-actor-avatar component detects changes | ||
124 | this.videoChannel = shallowCopy(this.videoChannel) | ||
121 | }, | 125 | }, |
122 | 126 | ||
123 | error: (err: HttpErrorResponse) => genericUploadErrorHandler({ | 127 | error: (err: HttpErrorResponse) => genericUploadErrorHandler({ |
@@ -135,6 +139,9 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI | |||
135 | this.notifier.success($localize`Avatar deleted.`) | 139 | this.notifier.success($localize`Avatar deleted.`) |
136 | 140 | ||
137 | this.videoChannel.resetAvatar() | 141 | this.videoChannel.resetAvatar() |
142 | |||
143 | // So my-actor-avatar component detects changes | ||
144 | this.videoChannel = shallowCopy(this.videoChannel) | ||
138 | }, | 145 | }, |
139 | 146 | ||
140 | error: err => this.notifier.error(err.message) | 147 | error: err => this.notifier.error(err.message) |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts index 577f4a252..a276bb126 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts | |||
@@ -3,6 +3,7 @@ import { HttpErrorResponse } from '@angular/common/http' | |||
3 | import { AfterViewChecked, Component, OnInit } from '@angular/core' | 3 | import { AfterViewChecked, Component, OnInit } from '@angular/core' |
4 | import { AuthService, Notifier, User, UserService } from '@app/core' | 4 | import { AuthService, Notifier, User, UserService } from '@app/core' |
5 | import { genericUploadErrorHandler } from '@app/helpers' | 5 | import { genericUploadErrorHandler } from '@app/helpers' |
6 | import { shallowCopy } from '@shared/core-utils' | ||
6 | 7 | ||
7 | @Component({ | 8 | @Component({ |
8 | selector: 'my-account-settings', | 9 | selector: 'my-account-settings', |
@@ -44,6 +45,9 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked { | |||
44 | this.notifier.success($localize`Avatar changed.`) | 45 | this.notifier.success($localize`Avatar changed.`) |
45 | 46 | ||
46 | this.user.updateAccountAvatar(data.avatars) | 47 | this.user.updateAccountAvatar(data.avatars) |
48 | |||
49 | // So my-actor-avatar component detects changes | ||
50 | this.user.account = shallowCopy(this.user.account) | ||
47 | }, | 51 | }, |
48 | 52 | ||
49 | error: (err: HttpErrorResponse) => genericUploadErrorHandler({ | 53 | error: (err: HttpErrorResponse) => genericUploadErrorHandler({ |
@@ -57,10 +61,13 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked { | |||
57 | onAvatarDelete () { | 61 | onAvatarDelete () { |
58 | this.userService.deleteAvatar() | 62 | this.userService.deleteAvatar() |
59 | .subscribe({ | 63 | .subscribe({ |
60 | next: data => { | 64 | next: () => { |
61 | this.notifier.success($localize`Avatar deleted.`) | 65 | this.notifier.success($localize`Avatar deleted.`) |
62 | 66 | ||
63 | this.user.updateAccountAvatar() | 67 | this.user.updateAccountAvatar() |
68 | |||
69 | // So my-actor-avatar component detects changes | ||
70 | this.user.account = shallowCopy(this.user.account) | ||
64 | }, | 71 | }, |
65 | 72 | ||
66 | error: (err: HttpErrorResponse) => this.notifier.error(err.message) | 73 | error: (err: HttpErrorResponse) => this.notifier.error(err.message) |
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html index 6459c5ffe..a0f65a3d9 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html | |||
@@ -1,23 +1,32 @@ | |||
1 | <div class="actor" *ngIf="actor"> | 1 | <div class="actor" *ngIf="actor"> |
2 | <div class="d-flex"> | 2 | <div class="position-relative me-3"> |
3 | <my-actor-avatar [actor]="actor" [actorType]="getActorType()" [previewImage]="preview" size="100"></my-actor-avatar> | 3 | <my-actor-avatar [actor]="actor" [actorType]="getActorType()" [previewImage]="preview" size="100"></my-actor-avatar> |
4 | 4 | ||
5 | <div class="actor-img-edit-container"> | 5 | <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body"> |
6 | 6 | <my-global-icon iconName="upload"></my-global-icon> | |
7 | <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body"> | 7 | <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label> |
8 | <my-global-icon iconName="upload"></my-global-icon> | 8 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> |
9 | <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label> | 9 | </div> |
10 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> | ||
11 | </div> | ||
12 | 10 | ||
13 | <div | 11 | <div *ngIf="editable && hasAvatar()" ngbDropdown placement="right"> |
14 | *ngIf="editable && hasAvatar()" class="actor-img-edit-button" | 12 | <div class="actor-img-edit-button" ngbDropdownToggle> |
15 | #avatarPopover="ngbPopover" [ngbPopover]="avatarEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right" | ||
16 | > | ||
17 | <my-global-icon iconName="edit"></my-global-icon> | 13 | <my-global-icon iconName="edit"></my-global-icon> |
18 | <label class="visually-hidden" for="avatarMenu" i18n>Change your avatar</label> | 14 | <label class="visually-hidden" for="avatarMenu" i18n>Change your avatar</label> |
19 | </div> | 15 | </div> |
20 | 16 | ||
17 | <div ngbDropdownMenu> | ||
18 | <div class="dropdown-item c-hand dropdown-file" [ngbTooltip]="avatarFormat"> | ||
19 | <my-global-icon iconName="upload"></my-global-icon> | ||
20 | <span for="avatarfile" i18n>Upload a new avatar</span> | ||
21 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> | ||
22 | </div> | ||
23 | |||
24 | <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()"> | ||
25 | <my-global-icon iconName="delete"></my-global-icon> | ||
26 | <span i18n>Remove avatar</span> | ||
27 | </div> | ||
28 | </div> | ||
29 | |||
21 | </div> | 30 | </div> |
22 | </div> | 31 | </div> |
23 | 32 | ||
@@ -27,16 +36,3 @@ | |||
27 | <div *ngIf="displaySubscribers" i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div> | 36 | <div *ngIf="displaySubscribers" i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div> |
28 | </div> | 37 | </div> |
29 | </div> | 38 | </div> |
30 | |||
31 | <ng-template #avatarEditContent> | ||
32 | <div class="dropdown-item c-hand" [ngbTooltip]="avatarFormat" placement="right" container="body"> | ||
33 | <my-global-icon iconName="upload"></my-global-icon> | ||
34 | <span for="avatarfile" i18n>Upload a new avatar</span> | ||
35 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> | ||
36 | </div> | ||
37 | |||
38 | <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()"> | ||
39 | <my-global-icon iconName="delete"></my-global-icon> | ||
40 | <span i18n>Remove avatar</span> | ||
41 | </div> | ||
42 | </ng-template> | ||
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss index fd8cd7ffc..01e2131ba 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss | |||
@@ -5,10 +5,6 @@ | |||
5 | display: flex; | 5 | display: flex; |
6 | } | 6 | } |
7 | 7 | ||
8 | my-actor-avatar { | ||
9 | @include margin-right(15px); | ||
10 | } | ||
11 | |||
12 | .actor-info { | 8 | .actor-info { |
13 | display: inline-flex; | 9 | display: inline-flex; |
14 | flex-direction: column; | 10 | flex-direction: column; |
@@ -16,12 +12,12 @@ my-actor-avatar { | |||
16 | 12 | ||
17 | .actor-info-display-name { | 13 | .actor-info-display-name { |
18 | @include peertube-word-wrap; | 14 | @include peertube-word-wrap; |
15 | @include font-size(1.25rem); | ||
19 | 16 | ||
20 | font-size: 20px; | ||
21 | font-weight: $font-bold; | 17 | font-weight: $font-bold; |
22 | 18 | ||
23 | @media screen and (max-width: $small-view) { | 19 | @media screen and (max-width: $small-view) { |
24 | font-size: 16px; | 20 | @include font-size(18px); |
25 | } | 21 | } |
26 | } | 22 | } |
27 | 23 | ||
@@ -35,17 +31,18 @@ my-actor-avatar { | |||
35 | padding-bottom: .5rem; | 31 | padding-bottom: .5rem; |
36 | } | 32 | } |
37 | 33 | ||
38 | .actor-img-edit-container { | ||
39 | position: relative; | ||
40 | width: 0; | ||
41 | } | ||
42 | |||
43 | .actor-img-edit-button { | 34 | .actor-img-edit-button { |
44 | top: 55px; | ||
45 | right: 45px; | ||
46 | border-radius: 50%; | 35 | border-radius: 50%; |
36 | |||
37 | position: absolute; | ||
38 | bottom: 5px; | ||
39 | right: 5px; | ||
47 | } | 40 | } |
48 | 41 | ||
49 | .dropdown-item { | 42 | .dropdown-item { |
50 | @include dropdown-with-icon-item; | 43 | @include dropdown-with-icon-item; |
51 | } | 44 | } |
45 | |||
46 | .dropdown-toggle::after { | ||
47 | display: none; | ||
48 | } | ||
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts index b71a3c485..fc925083e 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier, ServerService } from '@app/core' | 2 | import { Notifier, ServerService } from '@app/core' |
3 | import { Account, VideoChannel } from '@app/shared/shared-main' | 3 | import { Account, VideoChannel } from '@app/shared/shared-main' |
4 | import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' | ||
5 | import { getBytes } from '@root-helpers/bytes' | 4 | import { getBytes } from '@root-helpers/bytes' |
6 | import { imageToDataURL } from '@root-helpers/images' | 5 | import { imageToDataURL } from '@root-helpers/images' |
7 | 6 | ||
@@ -15,7 +14,6 @@ import { imageToDataURL } from '@root-helpers/images' | |||
15 | }) | 14 | }) |
16 | export class ActorAvatarEditComponent implements OnInit { | 15 | export class ActorAvatarEditComponent implements OnInit { |
17 | @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement> | 16 | @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement> |
18 | @ViewChild('avatarPopover') avatarPopover: NgbPopover | ||
19 | 17 | ||
20 | @Input() actor: VideoChannel | Account | 18 | @Input() actor: VideoChannel | Account |
21 | @Input() editable = true | 19 | @Input() editable = true |
@@ -58,7 +56,6 @@ export class ActorAvatarEditComponent implements OnInit { | |||
58 | 56 | ||
59 | const formData = new FormData() | 57 | const formData = new FormData() |
60 | formData.append('avatarfile', avatarfile) | 58 | formData.append('avatarfile', avatarfile) |
61 | this.avatarPopover?.close() | ||
62 | this.avatarChange.emit(formData) | 59 | this.avatarChange.emit(formData) |
63 | 60 | ||
64 | if (this.previewImage) { | 61 | if (this.previewImage) { |
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html index f675371d9..d6fe37094 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html +++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html | |||
@@ -8,26 +8,25 @@ | |||
8 | <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> | 8 | <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> |
9 | </div> | 9 | </div> |
10 | 10 | ||
11 | <div | 11 | <div *ngIf="hasBanner()" ngbDropdown placement="right"> |
12 | *ngIf="hasBanner()" class="actor-img-edit-button" | 12 | <div class="actor-img-edit-button" ngbDropdownToggle> |
13 | #bannerPopover="ngbPopover" [ngbPopover]="bannerEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right" | 13 | <my-global-icon iconName="edit"></my-global-icon> |
14 | > | 14 | <label for="bannerMenu" i18n>Change your banner</label> |
15 | <my-global-icon iconName="edit"></my-global-icon> | 15 | </div> |
16 | <label for="bannerMenu" i18n>Change your banner</label> | ||
17 | </div> | ||
18 | </div> | ||
19 | </div> | ||
20 | 16 | ||
21 | <ng-template #bannerEditContent> | 17 | <div ngbDropdownMenu> |
22 | <div class="dropdown-item c-hand" [ngbTooltip]="bannerFormat" placement="right" container="body"> | 18 | <div class="dropdown-item c-hand dropdown-file" [ngbTooltip]="bannerFormat"> |
23 | <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> | 19 | <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> |
24 | </div> | 20 | </div> |
25 | 21 | ||
26 | <div class="dropdown-item c-hand" (click)="deleteBanner()" (key.enter)="deleteBanner()"> | 22 | <div class="dropdown-item c-hand" (click)="deleteBanner()" (key.enter)="deleteBanner()"> |
27 | <my-global-icon iconName="delete"></my-global-icon> | 23 | <my-global-icon iconName="delete"></my-global-icon> |
28 | <span i18n>Remove banner</span> | 24 | <span i18n>Remove banner</span> |
25 | </div> | ||
26 | </div> | ||
27 | </div> | ||
29 | </div> | 28 | </div> |
30 | </ng-template> | 29 | </div> |
31 | 30 | ||
32 | <ng-template #uploadNewBanner> | 31 | <ng-template #uploadNewBanner> |
33 | <my-global-icon iconName="upload"></my-global-icon> | 32 | <my-global-icon iconName="upload"></my-global-icon> |
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss index ec2de2528..b2c64fff7 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss +++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss | |||
@@ -16,12 +16,28 @@ | |||
16 | align-items: center; | 16 | align-items: center; |
17 | } | 17 | } |
18 | 18 | ||
19 | .actor-img-edit-button { | 19 | .dropdown { |
20 | position: absolute; | 20 | position: absolute; |
21 | |||
22 | > .actor-img-edit-button { | ||
23 | position: relative; | ||
24 | } | ||
25 | } | ||
26 | |||
27 | .actor-img-edit-button { | ||
21 | width: auto; | 28 | width: auto; |
29 | position: absolute; | ||
22 | 30 | ||
23 | label { | 31 | label { |
24 | font-weight: $font-semibold; | 32 | font-weight: $font-semibold; |
25 | margin-bottom: 0; | 33 | margin-bottom: 0; |
26 | } | 34 | } |
27 | } | 35 | } |
36 | |||
37 | .dropdown-item { | ||
38 | @include dropdown-with-icon-item; | ||
39 | } | ||
40 | |||
41 | .dropdown-toggle::after { | ||
42 | display: none; | ||
43 | } | ||
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss b/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss index b054086e4..9e4ff2654 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss +++ b/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss | |||
@@ -1,18 +1,8 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .actor ::ng-deep .popover-image-info .popover-body { | 4 | .dropdown-file { |
5 | padding: 0; | 5 | @include peertube-file; |
6 | |||
7 | .dropdown-item { | ||
8 | padding: 6px 10px; | ||
9 | border-radius: 4px; | ||
10 | |||
11 | &:first-child { | ||
12 | @include peertube-file; | ||
13 | display: block; | ||
14 | } | ||
15 | } | ||
16 | } | 6 | } |
17 | 7 | ||
18 | .actor-img-edit-button { | 8 | .actor-img-edit-button { |
@@ -22,8 +12,6 @@ | |||
22 | 12 | ||
23 | display: flex; | 13 | display: flex; |
24 | justify-content: center; | 14 | justify-content: center; |
25 | margin-top: 10px; | ||
26 | margin-bottom: 5px; | ||
27 | cursor: pointer; | 15 | cursor: pointer; |
28 | 16 | ||
29 | input { | 17 | input { |
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts index 6036123f9..a52e68a17 100644 --- a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts +++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts | |||
@@ -50,15 +50,15 @@ export class ActorAvatarComponent implements OnInit, OnChanges { | |||
50 | ngOnInit () { | 50 | ngOnInit () { |
51 | this.buildDefaultAvatarUrl() | 51 | this.buildDefaultAvatarUrl() |
52 | 52 | ||
53 | this.buildClasses() | ||
54 | this.buildAlt() | 53 | this.buildAlt() |
55 | this.buildAvatarUrl() | 54 | this.buildAvatarUrl() |
55 | this.buildClasses() | ||
56 | } | 56 | } |
57 | 57 | ||
58 | ngOnChanges () { | 58 | ngOnChanges () { |
59 | this.buildClasses() | ||
60 | this.buildAlt() | 59 | this.buildAlt() |
61 | this.buildAvatarUrl() | 60 | this.buildAvatarUrl() |
61 | this.buildClasses() | ||
62 | } | 62 | } |
63 | 63 | ||
64 | private buildClasses () { | 64 | private buildClasses () { |
@@ -114,12 +114,13 @@ export class ActorAvatarComponent implements OnInit, OnChanges { | |||
114 | 114 | ||
115 | displayImage () { | 115 | displayImage () { |
116 | if (this.actorType === 'unlogged') return true | 116 | if (this.actorType === 'unlogged') return true |
117 | if (this.previewImage) return true | ||
117 | 118 | ||
118 | return !!(this.actor && this.avatarUrl) | 119 | return !!(this.actor && this.avatarUrl) |
119 | } | 120 | } |
120 | 121 | ||
121 | displayActorInitial () { | 122 | displayActorInitial () { |
122 | return this.actor && !this.avatarUrl | 123 | return !this.displayImage() && this.actor && !this.avatarUrl |
123 | } | 124 | } |
124 | 125 | ||
125 | displayPlaceholder () { | 126 | displayPlaceholder () { |
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 3b847c75b..4d956d652 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss | |||
@@ -30,7 +30,7 @@ | |||
30 | @import 'bootstrap/scss/helpers'; | 30 | @import 'bootstrap/scss/helpers'; |
31 | @import 'bootstrap/scss/utilities/api'; | 31 | @import 'bootstrap/scss/utilities/api'; |
32 | 32 | ||
33 | :root { | 33 | body { |
34 | --bs-border-color-translucent: #{pvar(--inputBorderColor)}; | 34 | --bs-border-color-translucent: #{pvar(--inputBorderColor)}; |
35 | } | 35 | } |
36 | 36 | ||
diff --git a/shared/core-utils/common/object.ts b/shared/core-utils/common/object.ts index 2330c9403..7f1f147f4 100644 --- a/shared/core-utils/common/object.ts +++ b/shared/core-utils/common/object.ts | |||
@@ -41,9 +41,14 @@ function sortObjectComparator (key: string, order: 'asc' | 'desc') { | |||
41 | } | 41 | } |
42 | } | 42 | } |
43 | 43 | ||
44 | function shallowCopy <T> (o: T): T { | ||
45 | return Object.assign(Object.create(Object.getPrototypeOf(o)), o) | ||
46 | } | ||
47 | |||
44 | export { | 48 | export { |
45 | pick, | 49 | pick, |
46 | omit, | 50 | omit, |
47 | getKeys, | 51 | getKeys, |
52 | shallowCopy, | ||
48 | sortObjectComparator | 53 | sortObjectComparator |
49 | } | 54 | } |