feat: add marble avatar (#1060)
This adds the marble avatar from [boring avatars](https://github.com/boringdesigners/boring-avatars) as an option for user avatars. Each user gets a different one (based on their id). Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1060 Co-authored-by: konrad <k@knt.li> Co-committed-by: konrad <k@knt.li>
This commit is contained in:
parent
13561f2114
commit
73ee696fc3
8 changed files with 132 additions and 11 deletions
6
go.sum
6
go.sum
|
@ -248,8 +248,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
|
|||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
|
@ -785,8 +783,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -992,8 +988,6 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211204120058-94396e421777 h1:QAkhGVjOxMa+n4mlsAWeAU+BMZmimQAaNiMu+iUi94E=
|
||||
golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
|
||||
|
|
122
pkg/modules/avatar/marble/marble.go
Normal file
122
pkg/modules/avatar/marble/marble.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package marble
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
)
|
||||
|
||||
// Provider generates a random avatar based on https://github.com/boringdesigners/boring-avatars
|
||||
type Provider struct {
|
||||
}
|
||||
|
||||
const avatarSize = 80
|
||||
|
||||
var colors = []string{
|
||||
"#A3A948",
|
||||
"#EDB92E",
|
||||
"#F85931",
|
||||
"#CE1836",
|
||||
"#009989",
|
||||
}
|
||||
|
||||
type props struct {
|
||||
Color string
|
||||
TranslateX int
|
||||
TranslateY int
|
||||
Rotate int
|
||||
Scale float64
|
||||
}
|
||||
|
||||
func getUnit(number int, rang, index int) int {
|
||||
value := number % rang
|
||||
|
||||
digit := math.Floor(math.Mod(float64(number)/math.Pow(10, float64(index)), 10))
|
||||
|
||||
if index > 0 && (math.Mod(digit, 2) == 0) {
|
||||
return -value
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func getPropsForUser(u *user.User) []*props {
|
||||
ps := []*props{}
|
||||
for i := 0; i < 3; i++ {
|
||||
f := float64(getUnit(int(u.ID)*(i+1), avatarSize/10, 0))
|
||||
ps = append(ps, &props{
|
||||
Color: colors[(int(u.ID)+i)%(len(colors)-1)],
|
||||
TranslateX: getUnit(int(u.ID)*(i+1), avatarSize/10, 1),
|
||||
TranslateY: getUnit(int(u.ID)*(i+1), avatarSize/10, 2),
|
||||
Scale: 1.2 + f/10,
|
||||
Rotate: getUnit(int(u.ID)*(i+1), 360, 1),
|
||||
})
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType string, err error) {
|
||||
|
||||
s := strconv.FormatInt(size, 10)
|
||||
avatarSizeStr := strconv.Itoa(avatarSize)
|
||||
avatarSizeHalf := strconv.Itoa(avatarSize / 2)
|
||||
|
||||
ps := getPropsForUser(u)
|
||||
|
||||
return []byte(`<svg
|
||||
viewBox="0 0 ` + avatarSizeStr + ` ` + avatarSizeStr + `"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="` + s + `"
|
||||
height="` + s + `"
|
||||
>
|
||||
<mask id="mask__marble" maskUnits="userSpaceOnUse" x="0" y="0" width="` + avatarSizeStr + `" height="` + avatarSizeStr + `">
|
||||
<rect width="` + avatarSizeStr + `" height="` + avatarSizeStr + `" rx="` + strconv.Itoa(avatarSize*2) + `" fill="white" />
|
||||
</mask>
|
||||
<g mask="url(#mask__marble)">
|
||||
<rect width="` + avatarSizeStr + `" height="` + avatarSizeStr + `" rx="2" fill="` + ps[0].Color + `" />
|
||||
<path
|
||||
filter="url(#prefix__filter0_f)"
|
||||
d="M32.414 59.35L50.376 70.5H72.5v-71H33.728L26.5 13.381l19.057 27.08L32.414 59.35z"
|
||||
fill="` + ps[1].Color + `"
|
||||
transform="translate(` + strconv.Itoa(ps[1].TranslateX) + ` ` + strconv.Itoa(ps[1].TranslateY) + `) rotate(` + strconv.Itoa(ps[1].Rotate) + ` ` + avatarSizeHalf + ` ` + avatarSizeHalf + `) scale(` + strconv.FormatFloat(ps[2].Scale, 'f', 2, 64) + `)"
|
||||
/>
|
||||
<path
|
||||
filter="url(#prefix__filter0_f)"
|
||||
style="mix-blend-mode: overlay;"
|
||||
d="M22.216 24L0 46.75l14.108 38.129L78 86l-3.081-59.276-22.378 4.005 12.972 20.186-23.35 27.395L22.215 24z"
|
||||
fill="` + ps[2].Color + `"
|
||||
transform="translate(` + strconv.Itoa(ps[2].TranslateX) + ` ` + strconv.Itoa(ps[2].TranslateY) + `) rotate(` + strconv.Itoa(ps[2].Rotate) + ` ` + avatarSizeHalf + ` ` + avatarSizeHalf + `) scale(` + strconv.FormatFloat(ps[2].Scale, 'f', 2, 64) + `)"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="prefix__filter0_f"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="7" result="effect1_foregroundBlur" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>`), "image/svg+xml", nil
|
||||
}
|
|
@ -25,6 +25,7 @@ import (
|
|||
"code.vikunja.io/api/pkg/modules/avatar/empty"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/gravatar"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/initials"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/marble"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/upload"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web/handler"
|
||||
|
@ -77,6 +78,8 @@ func GetAvatar(c echo.Context) error {
|
|||
avatarProvider = &initials.Provider{}
|
||||
case "upload":
|
||||
avatarProvider = &upload.Provider{}
|
||||
case "marble":
|
||||
avatarProvider = &marble.Provider{}
|
||||
default:
|
||||
avatarProvider = &empty.Provider{}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import (
|
|||
|
||||
// UserAvatarProvider holds the user avatar provider type
|
||||
type UserAvatarProvider struct {
|
||||
// The avatar provider. Valid types are `gravatar` (uses the user email), `upload`, `initials`, `default`.
|
||||
// The avatar provider. Valid types are `gravatar` (uses the user email), `upload`, `initials`, `marble` (generates a random avatar for each user), `default`.
|
||||
AvatarProvider string `json:"avatar_provider"`
|
||||
}
|
||||
|
||||
|
|
|
@ -8915,7 +8915,7 @@ var doc = `{
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"avatar_provider": {
|
||||
"description": "The avatar provider. Valid types are ` + "`" + `gravatar` + "`" + ` (uses the user email), ` + "`" + `upload` + "`" + `, ` + "`" + `initials` + "`" + `, ` + "`" + `default` + "`" + `.",
|
||||
"description": "The avatar provider. Valid types are ` + "`" + `gravatar` + "`" + ` (uses the user email), ` + "`" + `upload` + "`" + `, ` + "`" + `initials` + "`" + `, ` + "`" + `marble` + "`" + ` (generates a random avatar for each user), ` + "`" + `default` + "`" + `.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8899,7 +8899,7 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"avatar_provider": {
|
||||
"description": "The avatar provider. Valid types are `gravatar` (uses the user email), `upload`, `initials`, `default`.",
|
||||
"description": "The avatar provider. Valid types are `gravatar` (uses the user email), `upload`, `initials`, `marble` (generates a random avatar for each user), `default`.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1235,7 +1235,8 @@ definitions:
|
|||
properties:
|
||||
avatar_provider:
|
||||
description: The avatar provider. Valid types are `gravatar` (uses the user
|
||||
email), `upload`, `initials`, `default`.
|
||||
email), `upload`, `initials`, `marble` (generates a random avatar for each
|
||||
user), `default`.
|
||||
type: string
|
||||
type: object
|
||||
v1.UserDeletionRequestConfirm:
|
||||
|
|
|
@ -455,7 +455,8 @@ func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
|
|||
if user.AvatarProvider != "default" &&
|
||||
user.AvatarProvider != "gravatar" &&
|
||||
user.AvatarProvider != "initials" &&
|
||||
user.AvatarProvider != "upload" {
|
||||
user.AvatarProvider != "upload" &&
|
||||
user.AvatarProvider != "marble" {
|
||||
return updatedUser, &ErrInvalidAvatarProvider{AvatarProvider: user.AvatarProvider}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue