From bcf5d9d6d764627de815b4c56f9793da2b0d8b3f Mon Sep 17 00:00:00 2001 From: laodaming <11058467+laudamine@user.noreply.gitee.com> Date: Tue, 19 Sep 2023 16:01:23 +0800 Subject: [PATCH 1/7] 11 --- utils/websocket_data/render_data.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/websocket_data/render_data.go b/utils/websocket_data/render_data.go index 8ff17311..0eea4eb8 100644 --- a/utils/websocket_data/render_data.go +++ b/utils/websocket_data/render_data.go @@ -17,7 +17,7 @@ type RenderImageReqMsg struct { type RenderData struct { TemplateTag string `json:"template_tag"` //模板标签(必须) TemplateTagColor TemplateTagColor `json:"template_tag_color"` //模板标签组合颜色(必须) - TemplateTagGroups interface{} `json:"template_tag_groups"` //模板标签分组信息(必须) + TemplateTagGroups interface{} `json:"template_tag_groups"` //模板标签分组信息数组(必须) Logo string `json:"logo"` //log资源地址(必须) ProductId int64 `json:"product_id"` //产品id(必须) Website string `json:"website"` //网站(可选) From aa9b972deda3f14e6d24b05e6aba36cf41b61b37 Mon Sep 17 00:00:00 2001 From: laodaming <11058467+laudamine@user.noreply.gitee.com> Date: Tue, 19 Sep 2023 16:03:11 +0800 Subject: [PATCH 2/7] 11 --- .../internal/logic/ws_render_image.go | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/server/websocket/internal/logic/ws_render_image.go b/server/websocket/internal/logic/ws_render_image.go index 5b6789be..d9e8e702 100644 --- a/server/websocket/internal/logic/ws_render_image.go +++ b/server/websocket/internal/logic/ws_render_image.go @@ -195,16 +195,17 @@ func (w *wsConnectItem) renderImage(data []byte) { }*/ //获取刀版图 combineReq := repositories.LogoCombineReq{ - UserId: renderImageData.RenderData.UserId, - GuestId: renderImageData.RenderData.GuestId, - ProductTemplateV2Info: productTemplate, - TemplateTag: renderImageData.RenderData.TemplateTag, - Website: renderImageData.RenderData.Website, - Slogan: renderImageData.RenderData.Slogan, - Address: renderImageData.RenderData.Address, - Phone: renderImageData.RenderData.Phone, - Qrcode: renderImageData.RenderData.Qrcode, - LogoUrl: renderImageData.RenderData.Logo, + UserId: renderImageData.RenderData.UserId, + GuestId: renderImageData.RenderData.GuestId, + ProductTemplateV2Info: productTemplate, + ProductTemplateTagGroups: renderImageData.RenderData.TemplateTagGroups, + TemplateTag: renderImageData.RenderData.TemplateTag, + Website: renderImageData.RenderData.Website, + Slogan: renderImageData.RenderData.Slogan, + Address: renderImageData.RenderData.Address, + Phone: renderImageData.RenderData.Phone, + Qrcode: renderImageData.RenderData.Qrcode, + LogoUrl: renderImageData.RenderData.Logo, TemplateTagColor: repositories.TemplateTagColor{ Color: renderImageData.RenderData.TemplateTagColor.Color, Index: renderImageData.RenderData.TemplateTagColor.SelectedIndex, From b41d6ffda54e30f479e53e9c6a5d891d79453a56 Mon Sep 17 00:00:00 2001 From: laodaming <11058467+laudamine@user.noreply.gitee.com> Date: Tue, 19 Sep 2023 16:10:17 +0800 Subject: [PATCH 3/7] 11 --- server/websocket/internal/logic/ws_render_image.go | 4 ++-- utils/websocket_data/render_data.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/websocket/internal/logic/ws_render_image.go b/server/websocket/internal/logic/ws_render_image.go index d9e8e702..096fd3b2 100644 --- a/server/websocket/internal/logic/ws_render_image.go +++ b/server/websocket/internal/logic/ws_render_image.go @@ -109,7 +109,7 @@ func (w *wsConnectItem) renderImage(data []byte) { w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "请传入模板标签选择的颜色", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) return } - if renderImageData.RenderData.TemplateTagColor.SelectedIndex >= lenColor || renderImageData.RenderData.TemplateTagColor.SelectedIndex < 0 { + if renderImageData.RenderData.TemplateTagColor.SelectedColorIndex >= lenColor || renderImageData.RenderData.TemplateTagColor.SelectedColorIndex < 0 { w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "选择的模板标签颜色索引越界", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) return } @@ -208,7 +208,7 @@ func (w *wsConnectItem) renderImage(data []byte) { LogoUrl: renderImageData.RenderData.Logo, TemplateTagColor: repositories.TemplateTagColor{ Color: renderImageData.RenderData.TemplateTagColor.Color, - Index: renderImageData.RenderData.TemplateTagColor.SelectedIndex, + Index: renderImageData.RenderData.TemplateTagColor.SelectedColorIndex, }, } res, err := w.logic.svcCtx.Repositories.ImageHandle.LogoCombine(w.logic.ctx, &combineReq) diff --git a/utils/websocket_data/render_data.go b/utils/websocket_data/render_data.go index 0eea4eb8..c65b2eda 100644 --- a/utils/websocket_data/render_data.go +++ b/utils/websocket_data/render_data.go @@ -30,8 +30,8 @@ type RenderData struct { GuestId int64 `json:"guest_id"` //游客id(websocket连接建立再赋值) } type TemplateTagColor struct { - Color [][]string `json:"color"` //颜色组合 - SelectedIndex int `json:"selected_index"` //主色的下标索引 + Color [][]string `json:"color"` //颜色组合 + SelectedColorIndex int `json:"selected_color_index"` //主色的下标索引 } // websocket发送渲染完的数据 From 8ba604b3b938bf2f29b147dd85c03c47133b3602 Mon Sep 17 00:00:00 2001 From: laodaming <11058467+laudamine@user.noreply.gitee.com> Date: Tue, 19 Sep 2023 16:28:20 +0800 Subject: [PATCH 4/7] 11 --- server/websocket/internal/logic/ws_render_image.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/websocket/internal/logic/ws_render_image.go b/server/websocket/internal/logic/ws_render_image.go index 096fd3b2..dc914316 100644 --- a/server/websocket/internal/logic/ws_render_image.go +++ b/server/websocket/internal/logic/ws_render_image.go @@ -11,7 +11,6 @@ import ( "fusenapi/service/repositories" "fusenapi/utils/curl" "fusenapi/utils/hash" - "fusenapi/utils/template_switch_info" "fusenapi/utils/websocket_data" "github.com/zeromicro/go-zero/core/logx" "gorm.io/gorm" From 0dc1413988da18bb0e2a8f0065cdece30a03e27e Mon Sep 17 00:00:00 2001 From: laodaming <11058467+laudamine@user.noreply.gitee.com> Date: Tue, 19 Sep 2023 16:49:57 +0800 Subject: [PATCH 5/7] 11 --- .../internal/logic/datatransferlogic.go | 2 - .../internal/logic/ws_render_image.go | 186 ------------------ 2 files changed, 188 deletions(-) diff --git a/server/websocket/internal/logic/datatransferlogic.go b/server/websocket/internal/logic/datatransferlogic.go index 65a3121c..ca5d3bb9 100644 --- a/server/websocket/internal/logic/datatransferlogic.go +++ b/server/websocket/internal/logic/datatransferlogic.go @@ -194,8 +194,6 @@ func (l *DataTransferLogic) setConnPool(conn *websocket.Conn, userInfo *auth.Use userId: userInfo.UserId, guestId: userInfo.GuestId, extendRenderProperty: extendRenderProperty{ - //renderImageTask: make(map[string]*renderTask), - //renderImageTaskCtlChan: make(chan renderImageControlChanItem, renderImageTaskCtlChanLen), renderChan: make(chan []byte, renderChanLen), renderConsumeTickTime: 1, //默认1纳秒,后面需要根据不同用户不同触发速度 }, diff --git a/server/websocket/internal/logic/ws_render_image.go b/server/websocket/internal/logic/ws_render_image.go index dc914316..2ee96af2 100644 --- a/server/websocket/internal/logic/ws_render_image.go +++ b/server/websocket/internal/logic/ws_render_image.go @@ -19,8 +19,6 @@ import ( ) var ( - //每个websocket连接渲染任务调度队列长度默认值(添加任务/删除任务/修改任务属性)缓冲队列长度(该队列用于避免map并发读写冲突) - renderImageTaskCtlChanLen = 100 //每个websocket渲染任务缓冲队列长度默认值 renderChanLen = 500 ) @@ -31,29 +29,10 @@ type renderProcessor struct { // 云渲染属性 type extendRenderProperty struct { - //renderImageTask map[string]*renderTask //需要渲染的图片任务 key是taskId val 是renderId - //renderImageTaskCtlChan chan renderImageControlChanItem //渲染任务新增/回调结果移除任务/更新渲染耗时属性的控制通道(由于任务map无法读写并发) renderChan chan []byte //渲染消息入口的缓冲队列 renderConsumeTickTime time.Duration //消费渲染消息时钟间隔(纳秒),用于后期控制不同类型用户渲染速度限制 } -// 渲染任务新增移除的控制通道的数据 -/*type renderImageControlChanItem struct { - option int // 0删除 1添加 2修改耗时属性 - taskId string //map的key(必须传) - renderId string // map的val(增加任务时候传) - renderNotifyImageUrl string //渲染回调数据(删除任务时候传) - taskProperty renderTask //渲染任务的属性 -}*/ - -// 渲染任务属性 -/*type renderTask struct { - renderId string //渲染id(新增任务传) - unityRenderBeginTime int64 //发送给unity时间 - unityRenderEndTime int64 //unity回调结果时间 - uploadUnityRenderImageTakesTime int64 //上传unity渲染结果图时间 -}*/ - // 处理分发到这里的数据 func (r *renderProcessor) allocationMessage(w *wsConnectItem, data []byte) { //logx.Info("收到渲染任务消息:", string(data)) @@ -128,21 +107,6 @@ func (w *wsConnectItem) renderImage(data []byte) { w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "该产品不可定制", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) return } - //获取用户需要渲染logo - /*logoInfo, err := w.logic.svcCtx.Repositories.ImageHandle.LogoInfo(w.logic.ctx, &repositories.LogoInfoReq{ - UserId: w.userId, - GuestId: w.guestId, - }) - if err != nil { - w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "获取用户logo素材错误", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) - logx.Error(err) - return - } - if logoInfo == nil || logoInfo.LogoUrl == nil { - w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "用户logo素材url是空的", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) - return - } - renderImageData.RenderData.Logo = *logoInfo.LogoUrl*/ //用户id赋值 renderImageData.RenderData.UserId = w.userId renderImageData.RenderData.GuestId = w.guestId @@ -253,9 +217,6 @@ func (w *wsConnectItem) renderImage(data []byte) { }) return } - //########################################### - //把需要渲染的图片任务加进去 - //w.createRenderTask(taskId, renderImageData.RenderId) //组装数据 if err = w.assembleRenderDataToUnity(taskId, combineImage, renderImageData, productTemplate, model3dInfo, element, productSize); err != nil { logx.Error("组装数据失败:", err) @@ -447,15 +408,12 @@ func (w *wsConnectItem) assembleRenderDataToUnity(taskId string, combineImage st "render_data": sendData, } postDataBytes, _ := json.Marshal(postData) - //unityRenderBeginTime := time.Now().UTC().UnixMilli() _, err = curl.ApiCall(url, "POST", header, bytes.NewReader(postDataBytes), time.Second*10) if err != nil { w.renderErrResponse(info.RenderId, info.RenderData.TemplateTag, taskId, "请求unity接口失败", info.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3dInfo.Id, productSize.Id, *productTemplate.ElementModelId) logx.Error("failed to send data to unity") return err } - //记录发送到unity时间 - //w.modifyRenderTaskProperty(taskId, renderTask{unityRenderBeginTime: unityRenderBeginTime}) //发送运行阶段消息 w.sendRenderDataToUnityStepResponseMessage(info.RenderId) logx.Info("发送到unity成功,刀版图:", combineImage /*, " 请求unity的数据:", string(postDataBytes)*/) @@ -500,75 +458,6 @@ func (w *wsConnectItem) sendRenderResultData(data websocket_data.RenderImageRspM w.sendToOutChan(w.respondDataFormat(constants.WEBSOCKET_RENDER_IMAGE, data)) } -/*// 增加渲染任务 -func (w *wsConnectItem) createRenderTask(taskId, renderId string) { - if taskId == "" { - logx.Error("task_id不能为空") - return - } - if renderId == "" { - logx.Error("render_id不能为空") - return - } - data := renderImageControlChanItem{ - option: 1, - taskId: taskId, - renderId: renderId, - } - select { - case <-w.closeChan: //关闭 - return - case w.extendRenderProperty.renderImageTaskCtlChan <- data: - return - case <-time.After(time.Second * 3): - return - } -} - -// 渲染回调处理并删除渲染任务 -func (w *wsConnectItem) deleteRenderTask(taskId, renderId, renderNotifyImageUrl string) { - if taskId == "" { - logx.Error("task_id不能为空") - return - } - data := renderImageControlChanItem{ - option: 0, - taskId: taskId, - renderId: renderId, - renderNotifyImageUrl: renderNotifyImageUrl, - } - select { - case <-w.closeChan: //关闭 - return - case w.extendRenderProperty.renderImageTaskCtlChan <- data: - return - case <-time.After(time.Second * 3): - return - } -} - -// 修改任务属性(只有耗时属性可以更新) -func (w *wsConnectItem) modifyRenderTaskProperty(taskId string, property renderTask) { - if taskId == "" { - logx.Error("task_id不能为空") - return - } - //强制设为修改任务属性 - data := renderImageControlChanItem{ - option: 2, - taskId: taskId, - taskProperty: property, - } - select { - case <-w.closeChan: //关闭 - return - case w.extendRenderProperty.renderImageTaskCtlChan <- data: - return - case <-time.After(time.Second * 3): - return - } -}*/ - // 组装渲染任务id func (w *wsConnectItem) genRenderTaskId(combineImage string, renderImageData websocket_data.RenderImageReqMsg, model3dInfo *gmodel.FsProductModel3d, productTemplate *gmodel.FsProductTemplateV2, element *gmodel.FsProductTemplateElement) string { //生成任务id(需要把user_id,guest_id设为0) @@ -595,78 +484,3 @@ func (w *wsConnectItem) genRenderTaskId(combineImage string, renderImageData web } return hash.JsonHashKey(hashMap) } - -// 处理渲染任务的增加/删除/修改耗时属性(任务map不能读写并发,所以放在chan里面串行执行) -/*func (w *wsConnectItem) operationRenderTask() { - defer func() { - if err := recover(); err != nil { - logx.Error("operation render task panic:", err) - } - }() - for { - select { - case <-w.closeChan: - return - case data := <-w.extendRenderProperty.renderImageTaskCtlChan: - switch data.option { - case 0: //渲染结果回调,删除任务 - taskData, ok := w.extendRenderProperty.renderImageTask[data.taskId] - if !ok { - //发送到出口 - w.sendRenderResultData(websocket_data.RenderImageRspMsg{ - RenderId: data.renderId, //没有找到任务渲染id则用传进来的 - Image: data.renderNotifyImageUrl, - RenderProcessTime: websocket_data.RenderProcessTime{ - UnityRenderTakesTime: "unknown", - UploadUnityRenderImageTakesTime: "unknown", - }, - }) - continue - } - //删除任务 - delete(w.extendRenderProperty.renderImageTask, data.taskId) - //存在任务,则发送渲染结果给前端 - UnityRenderTakesTime := "cache" - uploadUnityRenderImageTakesTime := "cache" - //unity渲染时间 - if taskData.unityRenderBeginTime > 0 && taskData.unityRenderEndTime > 0 { - UnityRenderTakesTime = fmt.Sprintf("%dms", taskData.unityRenderEndTime-taskData.unityRenderBeginTime) - } - //上传unity渲染图耗时 - if taskData.uploadUnityRenderImageTakesTime > 0 { - uploadUnityRenderImageTakesTime = fmt.Sprintf("%dms", taskData.uploadUnityRenderImageTakesTime) - } - //发送到出口 - w.sendRenderResultData(websocket_data.RenderImageRspMsg{ - RenderId: taskData.renderId, - Image: data.renderNotifyImageUrl, - RenderProcessTime: websocket_data.RenderProcessTime{ - UnityRenderTakesTime: UnityRenderTakesTime, - UploadUnityRenderImageTakesTime: uploadUnityRenderImageTakesTime, - }, - }) - case 1: //新增任务 - w.extendRenderProperty.renderImageTask[data.taskId] = &renderTask{ - renderId: data.renderId, - } - case 2: //修改任务属性 - taskData, ok := w.extendRenderProperty.renderImageTask[data.taskId] - if !ok { - continue - } - //上传渲染结果图耗时 - if data.taskProperty.uploadUnityRenderImageTakesTime != 0 { - taskData.uploadUnityRenderImageTakesTime = data.taskProperty.uploadUnityRenderImageTakesTime - } - //发送unity时间 - if data.taskProperty.unityRenderBeginTime != 0 { - taskData.unityRenderBeginTime = data.taskProperty.unityRenderBeginTime - } - //收到unity返回的时间 - if data.taskProperty.unityRenderEndTime != 0 { - taskData.unityRenderEndTime = data.taskProperty.unityRenderEndTime - } - } - } - } -}*/ From 40e04c70b86fc366d94b9b0488bfbc1ad10d8deb Mon Sep 17 00:00:00 2001 From: laodaming <11058467+laudamine@user.noreply.gitee.com> Date: Tue, 19 Sep 2023 17:08:47 +0800 Subject: [PATCH 6/7] 11 --- server/websocket/internal/logic/ws_render_image.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/websocket/internal/logic/ws_render_image.go b/server/websocket/internal/logic/ws_render_image.go index 2ee96af2..f8484ecd 100644 --- a/server/websocket/internal/logic/ws_render_image.go +++ b/server/websocket/internal/logic/ws_render_image.go @@ -266,8 +266,8 @@ func (w *wsConnectItem) getProductRelateionInfoWithSizeId(renderImageData *webso return nil, nil, nil, errors.New("模板未开启云渲染") } if productTemplate.TemplateInfo == nil || *productTemplate.TemplateInfo == "" { - w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "渲染模板的json设计信息是空的", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3d.Id, productSize.Id, 0) - return nil, nil, nil, errors.New("渲染模板的json设计信息是空的") + w.renderErrResponse(renderImageData.RenderId, renderImageData.RenderData.TemplateTag, "", "渲染模板的设计信息是空的", renderImageData.RenderData.ProductId, w.userId, w.guestId, productTemplate.Id, model3d.Id, productSize.Id, 0) + return nil, nil, nil, errors.New("渲染模板的设计信息是空的") } return } From 3f4d808fda6482f930fc98fcf392890d3ecbff19 Mon Sep 17 00:00:00 2001 From: laodaming <11058467+laudamine@user.noreply.gitee.com> Date: Tue, 19 Sep 2023 17:24:47 +0800 Subject: [PATCH 7/7] 11 --- .../logic/getproducttemplatetagslogic.go | 10 ++++------ .../internal/types/types.go | 20 ++++++++----------- server_api/product-template-tag.api | 19 ++++++++---------- 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go b/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go index cd43c5af..51b0ef36 100644 --- a/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go +++ b/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go @@ -124,19 +124,17 @@ func (l *GetProductTemplateTagsLogic) GetProductTemplateTags(req *types.GetProdu } list := make([]types.GetProductTemplateTagsRsp, 0, len(productTemplateTags)) for _, templateInfo := range productTemplateTags { - colors := make([]types.ColorsItem, 0, 10) + colors := make([][]string, 0, 10) SelectedColorIndex := 0 isDefaultTemplateTag := false - for _, colorsSet := range mapTemplateTag[*templateInfo.TemplateTag] { + if colorsSet, ok := mapTemplateTag[*templateInfo.TemplateTag]; ok { if selectIndex, ok := mapSelectColor[*templateInfo.TemplateTag]; ok { isDefaultTemplateTag = true SelectedColorIndex = selectIndex } - colors = append(colors, types.ColorsItem{ - Color: colorsSet, - }) + colors = colorsSet } - var templateTagGroups interface{} + var templateTagGroups []interface{} if templateInfo.Groups != nil && *templateInfo.Groups != "" { if err = json.Unmarshal([]byte(*templateInfo.Groups), &templateTagGroups); err != nil { logx.Error(err) diff --git a/server/product-template-tag/internal/types/types.go b/server/product-template-tag/internal/types/types.go index b24c9f6b..295cc1ee 100644 --- a/server/product-template-tag/internal/types/types.go +++ b/server/product-template-tag/internal/types/types.go @@ -10,18 +10,14 @@ type GetProductTemplateTagsReq struct { } type GetProductTemplateTagsRsp struct { - Id int64 `json:"id"` - TemplateTag string `json:"template_tag"` - IsDefaultTemplateTag bool `json:"is_default_template_tag"` - TemplateTagGroups interface{} `json:"template_tag_groups"` - Cover string `json:"cover"` - CoverMetadata interface{} `json:"cover_metadata"` - Colors []ColorsItem `json:"colors"` - SelectedColorIndex int `json:"selected_color_index"` -} - -type ColorsItem struct { - Color []string `json:"color"` + Id int64 `json:"id"` + TemplateTag string `json:"template_tag"` + IsDefaultTemplateTag bool `json:"is_default_template_tag"` + TemplateTagGroups interface{} `json:"template_tag_groups"` + Cover string `json:"cover"` + CoverMetadata interface{} `json:"cover_metadata"` + Colors [][]string `json:"colors"` + SelectedColorIndex int `json:"selected_color_index"` } type Request struct { diff --git a/server_api/product-template-tag.api b/server_api/product-template-tag.api index 2c290067..74778ccf 100644 --- a/server_api/product-template-tag.api +++ b/server_api/product-template-tag.api @@ -20,15 +20,12 @@ type GetProductTemplateTagsReq { Limit int `form:"limit"` } type GetProductTemplateTagsRsp { - Id int64 `json:"id"` - TemplateTag string `json:"template_tag"` - IsDefaultTemplateTag bool `json:"is_default_template_tag"` - TemplateTagGroups interface{} `json:"template_tag_groups"` - Cover string `json:"cover"` - CoverMetadata interface{} `json:"cover_metadata"` - Colors []ColorsItem `json:"colors"` - SelectedColorIndex int `json:"selected_color_index"` -} -type ColorsItem { - Color []string `json:"color"` + Id int64 `json:"id"` + TemplateTag string `json:"template_tag"` + IsDefaultTemplateTag bool `json:"is_default_template_tag"` + TemplateTagGroups interface{} `json:"template_tag_groups"` + Cover string `json:"cover"` + CoverMetadata interface{} `json:"cover_metadata"` + Colors [][]string `json:"colors"` + SelectedColorIndex int `json:"selected_color_index"` } \ No newline at end of file