diff --git a/classquiz/__init__.py b/classquiz/__init__.py
index ec12387..45d9c15 100644
--- a/classquiz/__init__.py
+++ b/classquiz/__init__.py
@@ -29,6 +29,7 @@ from classquiz.routers import (
sitemap,
remote,
community,
+ avatar,
)
from classquiz.socket_server import sio
from classquiz.helpers import meilisearch_init, telemetry_ping, bg_tasks
@@ -100,5 +101,6 @@ app.include_router(editor.router, tags=["editor"], prefix="/api/v1/editor", incl
app.include_router(eximport.router, tags=["export", "import"], prefix="/api/v1/eximport", include_in_schema=True)
app.include_router(sitemap.router, tags=["sitemap"], prefix="/api/v1/sitemap", include_in_schema=True)
app.include_router(community.router, tags=["community"], prefix="/api/v1/community", include_in_schema=True)
+app.include_router(avatar.router, tags=["avatar"], prefix="/api/v1/avatar", include_in_schema=True)
app.mount("/", ASGIApp(sio))
diff --git a/classquiz/routers/avatar.py b/classquiz/routers/avatar.py
new file mode 100644
index 0000000..26967fe
--- /dev/null
+++ b/classquiz/routers/avatar.py
@@ -0,0 +1,150 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+import gzip
+
+from fastapi import APIRouter, Response, HTTPException, Depends
+import py_avataaars_no_png as av
+from fastapi.responses import PlainTextResponse
+from classquiz.config import redis
+
+from classquiz.auth import get_current_user
+from classquiz.db.models import User
+
+router = APIRouter()
+
+
+class AvatarItemsAsList:
+ skin_color = list(av.SkinColor)
+ hair_color = list(av.HairColor)
+ facial_hair_type = list(av.FacialHairType)
+ facial_hair_color = list(av.HairColor)
+ top_type = list(av.TopType)
+ hat_color = list(av.Color)
+ mouth_type = list(av.MouthType)
+ eyebrow_type = list(av.EyebrowType)
+ nose_type = list(av.NoseType)
+ accessories_type = list(av.AccessoriesType)
+ clothe_type = list(av.ClotheType)
+ clothe_color = list(av.Color)
+ clothe_graphic_type = list(av.ClotheGraphicType)
+
+
+@router.get("/custom", response_class=PlainTextResponse)
+async def get_customized_avatar(
+ resp: Response,
+ skin_color: int | None = 0,
+ hair_color: int | None = 0,
+ facial_hair_type: int | None = 0,
+ facial_hair_color: int | None = 0,
+ top_type: int | None = 0,
+ hat_color: int | None = 0,
+ mouth_type: int | None = 0,
+ eyebrow_type: int | None = 0,
+ nose_type: int | None = 0,
+ accessories_type: int | None = 0,
+ clothe_type: int | None = 0,
+ clothe_color: int | None = 0,
+ clothe_graphic_type: int | None = 0,
+):
+ try:
+ skin_color = AvatarItemsAsList.skin_color[skin_color]
+ hair_color = AvatarItemsAsList.hat_color[hair_color]
+ facial_hair_type = AvatarItemsAsList.facial_hair_type[facial_hair_type]
+ facial_hair_color = AvatarItemsAsList.facial_hair_color[facial_hair_color]
+ top_type = AvatarItemsAsList.top_type[top_type]
+ hat_color = AvatarItemsAsList.hat_color[hat_color]
+ mouth_type = AvatarItemsAsList.mouth_type[mouth_type]
+ eyebrow_type = AvatarItemsAsList.eyebrow_type[eyebrow_type]
+ nose_type = AvatarItemsAsList.nose_type[nose_type]
+ accessories_type = AvatarItemsAsList.accessories_type[accessories_type]
+ clothe_type = AvatarItemsAsList.clothe_type[clothe_type]
+ clothe_color = AvatarItemsAsList.clothe_color[clothe_color]
+ clothe_graphic_type = AvatarItemsAsList.clothe_graphic_type[clothe_graphic_type]
+ except IndexError:
+ raise HTTPException(status_code=400, detail="One parameter-value doesn't exist.")
+ resp.headers.append("Content-Type", "image/svg+xml")
+ avatar = av.PyAvataaar(
+ style=av.AvatarStyle.TRANSPARENT,
+ skin_color=skin_color,
+ hair_color=hair_color,
+ facial_hair_type=facial_hair_type,
+ facial_hair_color=facial_hair_color,
+ top_type=top_type,
+ hat_color=hat_color,
+ mouth_type=mouth_type,
+ eyebrow_type=eyebrow_type,
+ nose_type=nose_type,
+ accessories_type=accessories_type,
+ clothe_type=clothe_type,
+ clothe_color=clothe_color,
+ clothe_graphic_type=clothe_graphic_type,
+ ).render_svg()
+ # print(f"skin_color: {len(AvatarItemsAsList.skin_color)},")
+ # print(f"hair_color: {len(AvatarItemsAsList.hair_color)},")
+ # print(f"facial_hair_type: {len(AvatarItemsAsList.facial_hair_type)},")
+ # print(f"facial_hair_color: {len(AvatarItemsAsList.facial_hair_color)},")
+ # print(f"top_type: {len(AvatarItemsAsList.top_type)},")
+ # print(f"hat_color: {len(AvatarItemsAsList.hat_color)},")
+ # print(f"mouth_type: {len(AvatarItemsAsList.mouth_type)},")
+ # print(f"eyebrow_type: {len(AvatarItemsAsList.eyebrow_type)},")
+ # print(f"nose_type: {len(AvatarItemsAsList.nose_type)},")
+ # print(f"accessories_type: {len(AvatarItemsAsList.accessories_type)},")
+ # print(f"clothe_type: {len(AvatarItemsAsList.clothe_type)},")
+ # print(f"clothe_color: {len(AvatarItemsAsList.clothe_color)},")
+ # print(f"clothe_graphic_type: {len(AvatarItemsAsList.clothe_graphic_type)},")
+ return avatar
+
+
+@router.post("/save")
+async def save_avatar(
+ skin_color: int | None = 0,
+ hair_color: int | None = 0,
+ facial_hair_type: int | None = 0,
+ facial_hair_color: int | None = 0,
+ top_type: int | None = 0,
+ hat_color: int | None = 0,
+ mouth_type: int | None = 0,
+ eyebrow_type: int | None = 0,
+ nose_type: int | None = 0,
+ accessories_type: int | None = 0,
+ clothe_type: int | None = 0,
+ clothe_color: int | None = 0,
+ clothe_graphic_type: int | None = 0,
+ user: User = Depends(get_current_user),
+):
+ try:
+ skin_color = AvatarItemsAsList.skin_color[skin_color]
+ hair_color = AvatarItemsAsList.hat_color[hair_color]
+ facial_hair_type = AvatarItemsAsList.facial_hair_type[facial_hair_type]
+ facial_hair_color = AvatarItemsAsList.facial_hair_color[facial_hair_color]
+ top_type = AvatarItemsAsList.top_type[top_type]
+ hat_color = AvatarItemsAsList.hat_color[hat_color]
+ mouth_type = AvatarItemsAsList.mouth_type[mouth_type]
+ eyebrow_type = AvatarItemsAsList.eyebrow_type[eyebrow_type]
+ nose_type = AvatarItemsAsList.nose_type[nose_type]
+ accessories_type = AvatarItemsAsList.accessories_type[accessories_type]
+ clothe_type = AvatarItemsAsList.clothe_type[clothe_type]
+ clothe_color = AvatarItemsAsList.clothe_color[clothe_color]
+ clothe_graphic_type = AvatarItemsAsList.clothe_graphic_type[clothe_graphic_type]
+ except IndexError:
+ raise HTTPException(status_code=400, detail="One parameter-value doesn't exist.")
+ avatar = av.PyAvataaar(
+ style=av.AvatarStyle.TRANSPARENT,
+ skin_color=skin_color,
+ hair_color=hair_color,
+ facial_hair_type=facial_hair_type,
+ facial_hair_color=facial_hair_color,
+ top_type=top_type,
+ hat_color=hat_color,
+ mouth_type=mouth_type,
+ eyebrow_type=eyebrow_type,
+ nose_type=nose_type,
+ accessories_type=accessories_type,
+ clothe_type=clothe_type,
+ clothe_color=clothe_color,
+ clothe_graphic_type=clothe_graphic_type,
+ ).render_svg()
+ user.avatar = gzip.compress(str.encode(avatar))
+ await redis.delete(user.email)
+ await user.update()
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 3bdf7e0..015ca9d 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -1007,13 +1007,6 @@ packages:
engines: { node: '>=6.0.0' }
dev: true
- /@neodrag/svelte/1.2.4:
- resolution:
- {
- integrity: sha512-oGOxAgjtLbHyCJIWv35cvuHpgip9Kzn0ANrCidZb3rXV29w3AWhS7VtKAQfRinzFbmfK0Jeic7aSYdGmtKFKqw==
- }
- dev: true
-
/@nodelib/fs.scandir/2.1.5:
resolution:
{
@@ -4929,13 +4922,6 @@ packages:
sander: 0.5.1
dev: true
- /sortablejs/1.15.0:
- resolution:
- {
- integrity: sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==
- }
- dev: true
-
/source-map-js/1.0.2:
resolution:
{
diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json
index 79628c0..44a64bd 100644
--- a/frontend/src/lib/i18n/locales/en.json
+++ b/frontend/src/lib/i18n/locales/en.json
@@ -236,5 +236,21 @@
},
"uploader": {
"add_image": "Add image"
+ },
+ "avatar_settings": {
+ "skin_color": "Skin color",
+ "top_type": "Top",
+ "hair_color": "Hair color",
+ "facial_hair_type": "Facial hair",
+ "facial_hair_color": "Facial hair color",
+ "mouth_type": "Mouth",
+ "eyebrow_type": "Eyebrow",
+ "accessories_type": "Glasses",
+ "hat_color": "Hat color",
+ "clothe_type": "Clothes",
+ "clothe_color": "Clothing color",
+ "clothe_graphic_type": "Graphic",
+ "thats_you": "That's You!",
+ "start_over": "Start over"
}
}
diff --git a/frontend/src/routes/account/settings/+page.svelte b/frontend/src/routes/account/settings/+page.svelte
index d11dba0..f8cce05 100644
--- a/frontend/src/routes/account/settings/+page.svelte
+++ b/frontend/src/routes/account/settings/+page.svelte
@@ -163,11 +163,16 @@