Skip to content
This repository was archived by the owner on Aug 21, 2024. It is now read-only.

Commit 4d6ba78

Browse files
authored
search by owner in avatars table (#9073)
* search by owner in avatars table * feat: resolve user and sort user name on client * chore: add comment for Type.Any * fix: sort users by name in server
1 parent 8b4d14a commit 4d6ba78

File tree

5 files changed

+78
-57
lines changed

5 files changed

+78
-57
lines changed

β€Žpackages/client-core/src/admin/common/variables/avatar.tsβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Ethereal Engine. All Rights Reserved.
2626
import { AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema'
2727

2828
export interface AvatarColumn {
29-
id: 'select' | 'id' | 'name' | 'owner' | 'thumbnail' | 'action'
29+
id: 'select' | 'id' | 'name' | 'user' | 'thumbnail' | 'action'
3030
label: string | React.ReactElement
3131
minWidth?: number
3232
align?: 'right'
@@ -35,7 +35,7 @@ export interface AvatarColumn {
3535
export const avatarColumns: AvatarColumn[] = [
3636
{ id: 'id', label: 'Id', minWidth: 65 },
3737
{ id: 'name', label: 'Name', minWidth: 65 },
38-
{ id: 'owner', label: 'Owner', minWidth: 65 },
38+
{ id: 'user', label: 'Owner', minWidth: 65 },
3939
{
4040
id: 'thumbnail',
4141
label: 'Thumbnail',

β€Žpackages/client-core/src/admin/components/Avatars/AvatarTable.tsxβ€Ž

Lines changed: 48 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import Checkbox from '@etherealengine/ui/src/primitives/mui/Checkbox'
3434

3535
import { useFind, useMutation } from '@etherealengine/engine/src/common/functions/FeathersHooks'
3636
import TableComponent from '../../common/Table'
37-
import { AvatarColumn, AvatarData, avatarColumns } from '../../common/variables/avatar'
37+
import { AvatarColumn, avatarColumns } from '../../common/variables/avatar'
3838
import styles from '../../styles/admin.module.scss'
3939
import AvatarDrawer, { AvatarDrawerMode } from './AvatarDrawer'
4040

@@ -83,56 +83,54 @@ const AvatarTable = ({ className, search, selectedAvatarIds, setSelectedAvatarId
8383
}
8484
}
8585

86-
const createData = (el: AvatarType): AvatarData => {
87-
return {
88-
el,
89-
select: (
90-
<>
91-
<Checkbox
92-
className={styles.checkbox}
93-
checked={selectedAvatarIds.has(el.id)}
94-
onChange={() => {
95-
toggleSelection(el.id)
96-
}}
97-
/>
98-
</>
99-
),
100-
id: el.id,
101-
name: el.name as string,
102-
owner: el.userId,
103-
thumbnail: (
104-
<img
105-
style={{ maxHeight: '50px' }}
106-
crossOrigin="anonymous"
107-
src={el.thumbnailResource?.url + '?' + new Date().getTime()}
108-
alt=""
86+
const createData = (el: AvatarType): any => ({
87+
el,
88+
select: (
89+
<>
90+
<Checkbox
91+
className={styles.checkbox}
92+
checked={selectedAvatarIds.has(el.id)}
93+
onChange={() => {
94+
toggleSelection(el.id)
95+
}}
10996
/>
110-
),
111-
action: (
112-
<>
113-
<a
114-
className={styles.actionStyle}
115-
onClick={() => {
116-
avatarData.set(el)
117-
openAvatarDrawer.set(true)
118-
}}
119-
>
120-
<span className={styles.spanWhite}>{t('admin:components.common.view')}</span>
121-
</a>
122-
<a
123-
className={styles.actionStyle}
124-
onClick={() => {
125-
avatarId.set(el.id)
126-
avatarName.set(el.name)
127-
openConfirm.set(true)
128-
}}
129-
>
130-
<span className={styles.spanDange}>{t('admin:components.common.delete')}</span>
131-
</a>
132-
</>
133-
)
134-
}
135-
}
97+
</>
98+
),
99+
id: el.id,
100+
name: el.name as string,
101+
user: el.user?.name || '',
102+
thumbnail: (
103+
<img
104+
style={{ maxHeight: '50px' }}
105+
crossOrigin="anonymous"
106+
src={el.thumbnailResource?.url + '?' + new Date().getTime()}
107+
alt=""
108+
/>
109+
),
110+
action: (
111+
<>
112+
<a
113+
className={styles.actionStyle}
114+
onClick={() => {
115+
avatarData.set(el)
116+
openAvatarDrawer.set(true)
117+
}}
118+
>
119+
<span className={styles.spanWhite}>{t('admin:components.common.view')}</span>
120+
</a>
121+
<a
122+
className={styles.actionStyle}
123+
onClick={() => {
124+
avatarId.set(el.id)
125+
avatarName.set(el.name)
126+
openConfirm.set(true)
127+
}}
128+
>
129+
<span className={styles.spanDange}>{t('admin:components.common.delete')}</span>
130+
</a>
131+
</>
132+
)
133+
})
136134

137135
const submitRemoveAvatar = async () => {
138136
adminAvatarRemove(avatarId.value)

β€Žpackages/engine/src/schemas/user/avatar.schema.tsβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const avatarSchema = Type.Object(
5555
format: 'uuid'
5656
}),
5757
project: Type.String(),
58+
user: Type.Optional(Type.Any()), // avoid circular reference to `userSchema` which utilizes current `avatarSchema`
5859
modelResource: Type.Optional(Type.Ref(staticResourceSchema)),
5960
thumbnailResource: Type.Optional(Type.Ref(staticResourceSchema)),
6061
createdAt: Type.String({ format: 'date-time' }),
@@ -64,7 +65,7 @@ export const avatarSchema = Type.Object(
6465
)
6566
export type AvatarType = Static<typeof avatarSchema>
6667

67-
export type AvatarDatabaseType = Omit<AvatarType, 'modelResource' | 'thumbnailResource'>
68+
export type AvatarDatabaseType = Omit<AvatarType, 'user' | 'modelResource' | 'thumbnailResource'>
6869

6970
// Schema for creating new entries
7071
// export const avatarDataSchema = Type.Pick(

β€Žpackages/server-core/src/user/avatar/avatar.hooks.tsβ€Ž

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,25 @@ const ensureUserAccessibleAvatars = async (context: HookContext<AvatarService>)
9696
isPublic: true
9797
}
9898
}
99+
}
100+
101+
const sortByUserName = async (context: HookContext<AvatarService>) => {
102+
if (!context.params.query || !context.params.query.$sort?.['user']) return
103+
104+
const userSort = context.params.query.$sort['user']
105+
delete context.params.query.$sort['user']
106+
107+
if (context.params.query.name) {
108+
context.params.query[`${avatarPath}.name`] = context.params.query.name
109+
delete context.params.query.name
110+
}
111+
112+
const query = context.service.createQuery(context.params)
99113

100-
return context
114+
query.leftJoin(userPath, `${userPath}.id`, `${avatarPath}.userId`)
115+
query.orderBy(`${userPath}.name`, userSort === 1 ? 'asc' : 'desc')
116+
117+
context.params.knex = query
101118
}
102119

103120
/**
@@ -119,8 +136,6 @@ const removeAvatarResources = async (context: HookContext<AvatarService>) => {
119136
} catch (err) {
120137
logger.error(err)
121138
}
122-
123-
return context
124139
}
125140

126141
/**
@@ -164,7 +179,8 @@ export default {
164179
all: [() => schemaHooks.validateQuery(avatarQueryValidator), schemaHooks.resolveQuery(avatarQueryResolver)],
165180
find: [
166181
iffElse(isAction('admin'), verifyScope('admin', 'admin'), ensureUserAccessibleAvatars),
167-
discardQuery('action')
182+
discardQuery('action'),
183+
sortByUserName
168184
],
169185
get: [],
170186
create: [

β€Žpackages/server-core/src/user/avatar/avatar.resolvers.tsβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { staticResourcePath } from '@etherealengine/engine/src/schemas/media/sta
3131
import { AvatarDatabaseType, AvatarQuery, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema'
3232
import type { HookContext } from '@etherealengine/server-core/declarations'
3333

34+
import { userPath } from '@etherealengine/engine/src/schemas/user/user.schema'
3435
import { fromDateTimeSql, getDateTimeSql } from '../../util/datetime-sql'
3536

3637
export const avatarResolver = resolve<AvatarType, HookContext>({
@@ -39,6 +40,11 @@ export const avatarResolver = resolve<AvatarType, HookContext>({
3940
})
4041

4142
export const avatarExternalResolver = resolve<AvatarType, HookContext>({
43+
user: virtual(async (avatar, context) => {
44+
if (avatar.userId) {
45+
return context.app.service(userPath).get(avatar.userId)
46+
}
47+
}),
4248
modelResource: virtual(async (avatar, context) => {
4349
if (context.event !== 'removed' && avatar.modelResourceId)
4450
return context.app.service(staticResourcePath).get(avatar.modelResourceId)

0 commit comments

Comments
Β (0)