diff --git a/server/e2e/gql_storytelling_test.go b/server/e2e/gql_storytelling_test.go index a4d01a1a70..e9bc5c4632 100644 --- a/server/e2e/gql_storytelling_test.go +++ b/server/e2e/gql_storytelling_test.go @@ -1048,7 +1048,7 @@ func TestStoryPublishing(t *testing.T) { _, err = buf.ReadFrom(rc) assert.NoError(t, err) - pub := regexp.MustCompile(fmt.Sprintf(`{"schemaVersion":1,"id":"%s","publishedAt":".*","property":{"tiles":\[{"id":".*"}]},"plugins":{},"layers":null,"widgets":\[],"widgetAlignSystem":null,"tags":\[],"clusters":\[],"story":{"id":"%s","property":{},"pages":\[{"id":"%s","property":{},"title":"test","blocks":\[{"id":"%s","property":{"default":{"text":"test value"},"panel":{"padding":{"top":2,"bottom":3,"left":0,"right":1}}},"plugins":null,"extensionId":"%s","pluginId":"%s"}],"swipeable":true,"swipeableLayers":\[],"layers":\[]}],"position":"left","bgColor":""},"nlsLayers":null,"layerStyles":null,"coreSupport":true,"enableGa":false,"trackingId":""}`, sID, storyID, pageID, blockID, extensionId, pluginId)) + pub := regexp.MustCompile(fmt.Sprintf(`{"schemaVersion":1,"id":"%s","publishedAt":".*","property":{"tiles":\[{"id":".*"}]},"plugins":{},"layers":null,"widgets":\[],"widgetAlignSystem":null,"tags":\[],"clusters":\[],"story":{"id":"%s","title":"","property":{},"pages":\[{"id":"%s","property":{},"title":"test","blocks":\[{"id":"%s","property":{"default":{"text":"test value"},"panel":{"padding":{"top":2,"bottom":3,"left":0,"right":1}}},"plugins":null,"extensionId":"%s","pluginId":"%s"}],"swipeable":true,"swipeableLayers":\[],"layers":\[]}],"position":"left","bgColor":""},"nlsLayers":null,"layerStyles":null,"coreSupport":true,"enableGa":false,"trackingId":""}`, sID, storyID, pageID, blockID, extensionId, pluginId)) assert.Regexp(t, pub, buf.String()) resString := e.GET("/p/test-alias/data.json"). diff --git a/server/gql/project.graphql b/server/gql/project.graphql index 27a1cb723f..13a5aa6ef5 100644 --- a/server/gql/project.graphql +++ b/server/gql/project.graphql @@ -104,6 +104,7 @@ input ExportProjectInput { } input ImportProjectInput { + teamId: ID! file: Upload! } diff --git a/server/internal/adapter/gql/generated.go b/server/internal/adapter/gql/generated.go index 75320de377..dd08f723ed 100644 --- a/server/internal/adapter/gql/generated.go +++ b/server/internal/adapter/gql/generated.go @@ -9621,6 +9621,7 @@ input ExportProjectInput { } input ImportProjectInput { + teamId: ID! file: Upload! } @@ -9662,7 +9663,7 @@ extend type Query { pagination: Pagination keyword: String sort: ProjectSort - ): ProjectConnection! + ): ProjectConnection! # not included deleted projects checkProjectAlias(alias: String!): ProjectAliasAvailability! starredProjects(teamId: ID!): ProjectConnection! deletedProjects(teamId: ID!): ProjectConnection! @@ -60553,13 +60554,20 @@ func (ec *executionContext) unmarshalInputImportProjectInput(ctx context.Context asMap[k] = v } - fieldsInOrder := [...]string{"file"} + fieldsInOrder := [...]string{"teamId", "file"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { + case "teamId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("teamId")) + data, err := ec.unmarshalNID2githubᚗcomᚋreearthᚋreearthᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐID(ctx, v) + if err != nil { + return it, err + } + it.TeamID = data case "file": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("file")) data, err := ec.unmarshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx, v) diff --git a/server/internal/adapter/gql/gqlmodel/convert_nlslayer.go b/server/internal/adapter/gql/gqlmodel/convert_nlslayer.go index 443c134b4b..ab2a76412a 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_nlslayer.go +++ b/server/internal/adapter/gql/gqlmodel/convert_nlslayer.go @@ -77,6 +77,9 @@ func ToNLSLayer(l nlslayer.NLSLayer, parent *id.NLSLayerID) NLSLayer { func ToNLSLayers(layers nlslayer.NLSLayerList, parent *id.NLSLayerID) []NLSLayer { return util.Map(layers, func(l *nlslayer.NLSLayer) NLSLayer { + if l == nil { + return nil + } return ToNLSLayer(*l, parent) }) } diff --git a/server/internal/adapter/gql/gqlmodel/convert_plugin.go b/server/internal/adapter/gql/gqlmodel/convert_plugin.go index 632a40e4e3..2afea407ef 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_plugin.go +++ b/server/internal/adapter/gql/gqlmodel/convert_plugin.go @@ -2,6 +2,7 @@ package gqlmodel import ( "encoding/json" + "strings" "github.com/reearth/reearth/server/pkg/plugin" "github.com/reearth/reearthx/util" @@ -123,6 +124,14 @@ func ToPluginWidgetZoneType(t plugin.WidgetZoneType) WidgetZoneType { case plugin.WidgetZoneOuter: return WidgetZoneTypeOuter } + + t2 := plugin.WidgetZoneType(strings.ToLower(string(t))) + switch t2 { + case plugin.WidgetZoneInner: + return WidgetZoneTypeInner + case plugin.WidgetZoneOuter: + return WidgetZoneTypeOuter + } return "" } @@ -135,6 +144,16 @@ func ToPluginWidgetSectionType(t plugin.WidgetSectionType) WidgetSectionType { case plugin.WidgetSectionRight: return WidgetSectionTypeRight } + + t2 := plugin.WidgetSectionType(strings.ToLower(string(t))) + switch t2 { + case plugin.WidgetSectionLeft: + return WidgetSectionTypeLeft + case plugin.WidgetSectionCenter: + return WidgetSectionTypeCenter + case plugin.WidgetSectionRight: + return WidgetSectionTypeRight + } return "" } @@ -147,5 +166,15 @@ func ToPluginWidgetAreaType(t plugin.WidgetAreaType) WidgetAreaType { case plugin.WidgetAreaBottom: return WidgetAreaTypeBottom } + + t2 := plugin.WidgetAreaType(strings.ToLower(string(t))) + switch t2 { + case plugin.WidgetAreaTop: + return WidgetAreaTypeTop + case plugin.WidgetAreaMiddle: + return WidgetAreaTypeMiddle + case plugin.WidgetAreaBottom: + return WidgetAreaTypeBottom + } return "" } diff --git a/server/internal/adapter/gql/gqlmodel/convert_property.go b/server/internal/adapter/gql/gqlmodel/convert_property.go index 29096bceb7..edb7984b94 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_property.go +++ b/server/internal/adapter/gql/gqlmodel/convert_property.go @@ -7,6 +7,7 @@ import ( "github.com/reearth/reearth/server/pkg/property" "github.com/reearth/reearth/server/pkg/value" "github.com/reearth/reearthx/util" + "github.com/samber/lo" ) func ToPropertyValue(v *property.Value) *interface{} { @@ -242,6 +243,12 @@ func ToPropertySchema(propertySchema *property.Schema) *PropertySchema { } } +func ToPropertySchemas(ps []*property.Schema) []*PropertySchema { + return lo.Map(ps, func(s *property.Schema, _ int) *PropertySchema { + return ToPropertySchema(s) + }) +} + func ToPropertyLinkableFields(sid id.PropertySchemaID, l property.LinkableFields) *PropertyLinkableFields { var latlng, url *id.PropertyFieldID if l.LatLng != nil { @@ -327,6 +334,47 @@ func ToPropertySchemaFieldUI(ui *property.SchemaFieldUI) *PropertySchemaFieldUI return nil } +func FromPropertySchemaFieldUI(ui *string) *property.SchemaFieldUI { + if ui == nil { + return nil + } + + var ui2 property.SchemaFieldUI + switch *ui { + case PropertySchemaFieldUIMultiline.String(): + ui2 = property.SchemaFieldUIMultiline + case PropertySchemaFieldUISelection.String(): + ui2 = property.SchemaFieldUISelection + case PropertySchemaFieldUIColor.String(): + ui2 = property.SchemaFieldUIColor + case PropertySchemaFieldUIRange.String(): + ui2 = property.SchemaFieldUIRange + case PropertySchemaFieldUISlider.String(): + ui2 = property.SchemaFieldUISlider + case PropertySchemaFieldUIImage.String(): + ui2 = property.SchemaFieldUIImage + case PropertySchemaFieldUIVideo.String(): + ui2 = property.SchemaFieldUIVideo + case PropertySchemaFieldUIFile.String(): + ui2 = property.SchemaFieldUIFile + case PropertySchemaFieldUILayer.String(): + ui2 = property.SchemaFieldUILayer + case PropertySchemaFieldUICameraPose.String(): + ui2 = property.SchemaFieldUICameraPose + case PropertySchemaFieldUIPadding.String(): + ui2 = property.SchemaFieldUIPadding + case PropertySchemaFieldUIMargin.String(): + ui2 = property.SchemaFieldUIMargin + case PropertySchemaFieldUIDatetime.String(): + ui2 = property.SchemaFieldUIDateTime + } + + if ui2 != "" { + return &ui2 + } + return nil +} + func ToMergedPropertyFromMetadata(m *property.MergedMetadata) *MergedProperty { if m == nil { return nil diff --git a/server/internal/adapter/gql/gqlmodel/convert_value.go b/server/internal/adapter/gql/gqlmodel/convert_value.go index 643f6c13d7..be763929cb 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_value.go +++ b/server/internal/adapter/gql/gqlmodel/convert_value.go @@ -4,6 +4,7 @@ import ( "net/url" "strings" + "github.com/reearth/reearth/server/pkg/property" "github.com/reearth/reearth/server/pkg/value" "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -206,3 +207,32 @@ func ToValueType(t value.Type) ValueType { func FromValueType(t ValueType) value.Type { return value.Type(strings.ToLower(string(t))) } + +func ToPropertyValueType(t string) property.ValueType { + switch t { + case ValueTypeBool.String(): + return property.ValueTypeBool + case ValueTypeNumber.String(): + return property.ValueTypeNumber + case ValueTypeString.String(): + return property.ValueTypeString + case ValueTypeRef.String(): + return property.ValueTypeRef + case ValueTypeURL.String(): + return property.ValueTypeURL + case ValueTypeLatlng.String(): + return property.ValueTypeLatLng + case ValueTypeLatlngheight.String(): + return property.ValueTypeLatLngHeight + case ValueTypeCoordinates.String(): + return property.ValueTypeCoordinates + case ValueTypePolygon.String(): + return property.ValueTypePolygon + case ValueTypeRect.String(): + return property.ValueTypeRect + case ValueTypeArray.String(): + return property.ValueTypeArray + default: + return property.ValueTypeUnknown + } +} diff --git a/server/internal/adapter/gql/gqlmodel/models_gen.go b/server/internal/adapter/gql/gqlmodel/models_gen.go index dcdc8ba0fc..a33f7a3b31 100644 --- a/server/internal/adapter/gql/gqlmodel/models_gen.go +++ b/server/internal/adapter/gql/gqlmodel/models_gen.go @@ -615,7 +615,8 @@ type ImportLayerPayload struct { } type ImportProjectInput struct { - File graphql.Upload `json:"file"` + TeamID ID `json:"teamId"` + File graphql.Upload `json:"file"` } type ImportProjectPayload struct { diff --git a/server/internal/adapter/gql/resolver_mutation_project.go b/server/internal/adapter/gql/resolver_mutation_project.go index 508f5623cf..0354e00d11 100644 --- a/server/internal/adapter/gql/resolver_mutation_project.go +++ b/server/internal/adapter/gql/resolver_mutation_project.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "os" "strings" @@ -15,6 +16,7 @@ import ( "github.com/reearth/reearth/server/internal/usecase/interfaces" "github.com/reearth/reearth/server/pkg/file" "github.com/reearth/reearth/server/pkg/id" + "github.com/reearth/reearth/server/pkg/scene" "github.com/reearth/reearth/server/pkg/visualizer" "github.com/reearth/reearthx/account/accountdomain" "github.com/spf13/afero" @@ -162,11 +164,12 @@ func (r *mutationResolver) ExportProject(ctx context.Context, input gqlmodel.Exp } data["project"] = gqlmodel.ToProject(prj) - plgs, err := usecases(ctx).Plugin.ExportPlugins(ctx, sce, zipWriter) + plgs, schemas, err := usecases(ctx).Plugin.ExportPlugins(ctx, sce, zipWriter) if err != nil { return nil, err } data["plugins"] = gqlmodel.ToPlugins(plgs) + data["schemas"] = gqlmodel.ToPropertySchemas(schemas) err = usecases(ctx).Project.UploadExportProjectZip(ctx, zipWriter, zipFile, data, prj) if err != nil { @@ -178,86 +181,118 @@ func (r *mutationResolver) ExportProject(ctx context.Context, input gqlmodel.Exp }, nil } +func replaceOldSceneID(data []byte, sce *scene.Scene) (string, []byte, error) { + var jsonData map[string]interface{} + if err := json.Unmarshal(data, &jsonData); err != nil { + return "", nil, err + } + + oldSceneData, _ := jsonData["scene"].(map[string]interface{}) + oldSceneID := oldSceneData["id"].(string) + return oldSceneID, bytes.Replace(data, []byte(oldSceneID), []byte(sce.ID().String()), -1), nil +} + +func unmarshalProject(data []byte) (map[string]interface{}, error) { + var jsonData map[string]interface{} + if err := json.Unmarshal(data, &jsonData); err != nil { + return nil, err + } + + projectData, _ := jsonData["project"].(map[string]interface{}) + return projectData, nil +} + +func unmarshalPluginsScene(data []byte) ([]interface{}, map[string]interface{}, []interface{}, error) { + var jsonData map[string]interface{} + if err := json.Unmarshal(data, &jsonData); err != nil { + return nil, nil, nil, err + } + + pluginsData, _ := jsonData["plugins"].([]interface{}) + sceneData, _ := jsonData["scene"].(map[string]interface{}) + schemaData, _ := jsonData["schemas"].([]interface{}) + return pluginsData, sceneData, schemaData, nil +} + func (r *mutationResolver) ImportProject(ctx context.Context, input gqlmodel.ImportProjectInput) (*gqlmodel.ImportProjectPayload, error) { - data, assets, plugins, err := file.UncompressExportZip(input.File.File) + tempData, assets, plugins, err := file.UncompressExportZip(input.File.File) if err != nil { - return nil, err + return nil, errors.New("Fail UncompressExportZip :" + err.Error()) } // Assets file import - changedFileName := make(map[string]string) for fileName, file := range assets { parts1 := strings.Split(fileName, "/") beforeName := parts1[0] url, _, err := usecases(ctx).Asset.UploadAssetFile(ctx, beforeName, file) if err != nil { - return nil, err + return nil, errors.New("Fail UploadAssetFile :" + err.Error()) } parts2 := strings.Split(url.Path, "/") afterName := parts2[len(parts2)-1] - changedFileName[beforeName] = afterName + tempData = bytes.Replace(tempData, []byte(beforeName), []byte(afterName), -1) } + projectData, _ := unmarshalProject(tempData) + prj, tx, err := usecases(ctx).Project.ImportProject(ctx, string(input.TeamID), projectData) + if err != nil { + return nil, errors.New("Fail ImportProject :" + err.Error()) + } + defer func() { + if err2 := tx.End(ctx); err == nil && err2 != nil { + err = err2 + } + }() + + // need to create a Scene firsts + sce, err := usecases(ctx).Scene.Create(ctx, prj.ID(), getOperator(ctx)) + oldSceneID, tempData, _ := replaceOldSceneID(tempData, sce) + // Plugin file import for fileName, file := range plugins { + parts := strings.Split(fileName, "/") - pid, err := id.PluginIDFrom(parts[0]) + oldPID := parts[0] + fileName := parts[1] + + newPID := strings.Replace(oldPID, oldSceneID, sce.ID().String(), 1) + pid, err := id.PluginIDFrom(newPID) if err != nil { return nil, err } - if err := usecases(ctx).Plugin.ImporPluginFile(ctx, pid, parts[1], file); err != nil { - return nil, err + if err := usecases(ctx).Plugin.ImporPluginFile(ctx, pid, fileName, file); err != nil { + return nil, errors.New("Fail ImporPluginFile :" + err.Error()) } } - for beforeName, afterName := range changedFileName { - data = bytes.Replace(data, []byte(beforeName), []byte(afterName), -1) - } + pluginsData, sceneData, schemaData, _ := unmarshalPluginsScene(tempData) - var jsonData map[string]interface{} - if err := json.Unmarshal(data, &jsonData); err != nil { - return nil, err - } - - projectData, _ := jsonData["project"].(map[string]interface{}) - prj, tx, err := usecases(ctx).Project.ImportProject(ctx, projectData) + plgs, pss, err := usecases(ctx).Plugin.ImportPlugins(ctx, sce, pluginsData, schemaData) if err != nil { - return nil, err - } - defer func() { - if err2 := tx.End(ctx); err == nil && err2 != nil { - err = err2 - } - }() - - pluginsData, _ := jsonData["plugins"].([]interface{}) - plgs, err := usecases(ctx).Plugin.ImportPlugins(ctx, pluginsData) - if err != nil { - return nil, err + return nil, errors.New("Fail ImportPlugins :" + err.Error()) } - sceneData, _ := jsonData["scene"].(map[string]interface{}) - sce, err := usecases(ctx).Scene.ImportScene(ctx, prj, plgs, sceneData) + sce, err = usecases(ctx).Scene.ImportScene(ctx, sce, prj, plgs, sceneData) if err != nil { - return nil, err + return nil, errors.New("Fail ImportScene :" + err.Error()) } - nlayers, err := usecases(ctx).NLSLayer.ImportNLSLayers(ctx, sceneData) + nlayers, replaceNLSLayerIDs, err := usecases(ctx).NLSLayer.ImportNLSLayers(ctx, sce.ID(), sceneData) if err != nil { - return nil, err + return nil, errors.New("Fail ImportNLSLayers :" + err.Error()) } - styleList, err := usecases(ctx).Style.ImportStyles(ctx, sceneData) + styleList, err := usecases(ctx).Style.ImportStyles(ctx, sce.ID(), sceneData) if err != nil { - return nil, err + return nil, errors.New("Fail ImportStyles :" + err.Error()) } - st, err := usecases(ctx).StoryTelling.ImportStory(ctx, sceneData) + st, err := usecases(ctx).StoryTelling.ImportStory(ctx, sce.ID(), sceneData, replaceNLSLayerIDs) if err != nil { - return nil, err + return nil, errors.New("Fail ImportStory :" + err.Error()) } tx.Commit() @@ -266,6 +301,7 @@ func (r *mutationResolver) ImportProject(ctx context.Context, input gqlmodel.Imp ProjectData: map[string]any{ "project": gqlmodel.ToProject(prj), "plugins": gqlmodel.ToPlugins(plgs), + "schema": gqlmodel.ToPropertySchemas(pss), "scene": gqlmodel.ToScene(sce), "nlsLayer": gqlmodel.ToNLSLayers(nlayers, nil), "style": gqlmodel.ToStyles(styleList), diff --git a/server/internal/app/auth_server.go b/server/internal/app/auth_server.go index 923ee24e99..5d6c4587cb 100644 --- a/server/internal/app/auth_server.go +++ b/server/internal/app/auth_server.go @@ -43,7 +43,7 @@ type authServerUser struct { func (r *authServerUser) Sub(ctx context.Context, email, password, authRequestID string) (string, error) { u, err := r.User.FindByNameOrEmail(ctx, email) if err != nil { - if errors.Is(rerror.ErrNotFound, err) { + if errors.Is(err, rerror.ErrNotFound) { return "", ErrInvalidEmailORPassword } return "", err diff --git a/server/internal/app/graphql.go b/server/internal/app/graphql.go index 84fa8ac828..b0fb2d74cc 100644 --- a/server/internal/app/graphql.go +++ b/server/internal/app/graphql.go @@ -66,7 +66,7 @@ func GraphqlAPI(conf config.GraphQLConfig, dev bool) echo.HandlerFunc { // show more detailed error messgage in debug mode func(ctx context.Context, e error) *gqlerror.Error { if dev { - return gqlerror.ErrorPathf(graphql.GetFieldContext(ctx).Path(), e.Error()) + return gqlerror.ErrorPathf(graphql.GetFieldContext(ctx).Path(), "%s", e.Error()) } return graphql.DefaultErrorPresenter(ctx, e) }, diff --git a/server/internal/app/public.go b/server/internal/app/public.go index 6a97bb2c8d..e878025415 100644 --- a/server/internal/app/public.go +++ b/server/internal/app/public.go @@ -192,8 +192,10 @@ func PublishedIndexMiddleware(pattern string, useParam, errorIfNotFound bool) ec } } +type keyType struct{} + func PublishedAuthMiddleware() echo.MiddlewareFunc { - key := struct{}{} + key := keyType{} return middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{ Validator: func(user string, password string, c echo.Context) (bool, error) { md, ok := c.Request().Context().Value(key).(interfaces.ProjectPublishedMetadata) diff --git a/server/internal/usecase/interactor/nlslayer.go b/server/internal/usecase/interactor/nlslayer.go index 657008c50a..489a70b92b 100644 --- a/server/internal/usecase/interactor/nlslayer.go +++ b/server/internal/usecase/interactor/nlslayer.go @@ -13,6 +13,7 @@ import ( "github.com/reearth/reearth/server/pkg/nlslayer/nlslayerops" "github.com/reearth/reearth/server/pkg/plugin" "github.com/reearth/reearth/server/pkg/property" + "github.com/reearth/reearth/server/pkg/scene" "github.com/reearth/reearth/server/pkg/scene/builder" "github.com/reearth/reearthx/account/accountusecase/accountrepo" "github.com/reearth/reearthx/idx" @@ -833,52 +834,124 @@ func (i *NLSLayer) DeleteGeoJSONFeature(ctx context.Context, inp interfaces.Dele return inp.FeatureID, nil } -func (i *NLSLayer) ImportNLSLayers(ctx context.Context, sceneData map[string]interface{}) (nlslayer.NLSLayerList, error) { +func (i *NLSLayer) ImportNLSLayers(ctx context.Context, sceneID idx.ID[id.Scene], sceneData map[string]interface{}) (nlslayer.NLSLayerList, map[string]idx.ID[id.NLSLayer], error) { sceneJSON, err := builder.ParseSceneJSON(ctx, sceneData) if err != nil { - return nil, err - } - sceneID, err := id.SceneIDFrom(sceneJSON.ID) - if err != nil { - return nil, err + return nil, nil, err } + readableFilter := repo.SceneFilter{Readable: scene.IDList{sceneID}} + writableFilter := repo.SceneFilter{Writable: scene.IDList{sceneID}} + nlayerIDs := idx.List[id.NLSLayer]{} - nlayers := []nlslayer.NLSLayer{} + replaceNLSLayerIDs := make(map[string]idx.ID[id.NLSLayer]) for _, nlsLayerJSON := range sceneJSON.NLSLayers { - nlsLayerID, err := id.NLSLayerIDFrom(nlsLayerJSON.ID) - if err != nil { - return nil, err - } - nlayerIDs = append(nlayerIDs, nlsLayerID) - nlayer, err := nlslayer.New(). - ID(nlsLayerID). + newNLSLayerID := id.NewNLSLayerID() + nlayerIDs = append(nlayerIDs, newNLSLayerID) + replaceNLSLayerIDs[nlsLayerJSON.ID] = newNLSLayerID + + nlBuilder := nlslayer.New(). + ID(newNLSLayerID). Simple(). Scene(sceneID). Title(nlsLayerJSON.Title). LayerType(nlslayer.LayerType(nlsLayerJSON.LayerType)). Config((*nlslayer.Config)(nlsLayerJSON.Config)). IsVisible(nlsLayerJSON.IsVisible). - IsSketch(nlsLayerJSON.IsSketch). - Build() - if err != nil { - return nil, err + IsSketch(nlsLayerJSON.IsSketch) + + // Infobox -------- + if nlsLayerJSON.Infobox != nil { + schema := builtin.GetPropertySchema(builtin.PropertySchemaIDBetaInfobox) + prop, err := property.New().NewID().Schema(schema.ID()).Scene(sceneID).Build() + if err != nil { + return nil, nil, err + } + prop, err = builder.AddItemFromPropertyJSON(prop, schema, nlsLayerJSON.Infobox.Property) + if err != nil { + return nil, nil, err + } + // Save property + if err = i.propertyRepo.Filtered(writableFilter).Save(ctx, prop); err != nil { + return nil, nil, err + } + + blocks := make([]*nlslayer.InfoboxBlock, 0) + if nlsLayerJSON.Infobox != nil { + for _, b := range nlsLayerJSON.Infobox.Blocks { + schemaB := builtin.GetPropertySchema(builtin.PropertySchemaIDBetaInfobox) + propB, err := property.New().NewID().Schema(schemaB.ID()).Scene(sceneID).Build() + if err != nil { + return nil, nil, err + } + propB, err = builder.AddItemFromPropertyJSON(propB, schemaB, b.Property) + if err != nil { + return nil, nil, err + } + // Save property + if err = i.propertyRepo.Filtered(writableFilter).Save(ctx, propB); err != nil { + return nil, nil, err + } + extensionID := id.PluginExtensionID(b.ExtensionId) + pluginID, _ := id.PluginIDFrom(b.PluginId) + ibf, err := nlslayer.NewInfoboxBlock(). + NewID(). + Plugin(pluginID). + Extension(extensionID). + Property(propB.ID()). + Build() + if err != nil { + return nil, nil, err + } + blocks = append(blocks, ibf) + } + } + nlBuilder = nlBuilder.Infobox(nlslayer.NewInfobox(blocks, prop.ID())) } - nlayers = append(nlayers, nlayer) - } - nlsLayerList := make(nlslayer.NLSLayerList, len(nlayers)) - for i, layer := range nlayers { - nlsLayerList[i] = &layer - } + // SketchInfo -------- + if nlsLayerJSON.SketchInfo != nil { + i := nlsLayerJSON.SketchInfo + feature := make([]nlslayer.Feature, 0) + for _, v := range i.FeatureCollection.Features { + var geometry nlslayer.Geometry + for _, g := range v.Geometry { + if geometryMap, ok := g.(map[string]any); ok { + geometry, err = nlslayer.NewGeometryFromMap(geometryMap) + if err != nil { + return nil, nil, err + } + } + } + f, err := nlslayer.NewFeatureWithNewId(v.Type, geometry) + if err != nil { + return nil, nil, err + } + feature = append(feature, *f) + } + featureCollection := nlslayer.NewFeatureCollection( + i.FeatureCollection.Type, + feature, + ) + sketchInfo := nlslayer.NewSketchInfo( + i.PropertySchema, + featureCollection, + ) + nlBuilder = nlBuilder.Sketch(sketchInfo) + } - if err := i.nlslayerRepo.SaveAll(ctx, nlsLayerList); err != nil { - return nil, err + nlayer, err := nlBuilder.Build() + if err != nil { + return nil, nil, err + } + if err := i.nlslayerRepo.Filtered(writableFilter).Save(ctx, nlayer); err != nil { + return nil, nil, err + } } - nlayer, err := i.nlslayerRepo.FindByIDs(ctx, nlayerIDs) + nlayer, err := i.nlslayerRepo.Filtered(readableFilter).FindByIDs(ctx, nlayerIDs) if err != nil { - return nil, err + return nil, nil, err } - return nlayer, nil + return nlayer, replaceNLSLayerIDs, nil } diff --git a/server/internal/usecase/interactor/nlslayer_test.go b/server/internal/usecase/interactor/nlslayer_test.go index f620dc214f..629945f339 100644 --- a/server/internal/usecase/interactor/nlslayer_test.go +++ b/server/internal/usecase/interactor/nlslayer_test.go @@ -261,6 +261,7 @@ func TestDeleteGeoJSONFeature(t *testing.T) { assert.Equal(t, 0, len(featureCollection.Features())) } +// go test -v -run TestImportNLSLayers ./internal/usecase/interactor/... func TestImportNLSLayers(t *testing.T) { ctx := context.Background() @@ -300,7 +301,7 @@ func TestImportNLSLayers(t *testing.T) { assert.NoError(t, err) // invoke the target function - result, err := ifl.ImportNLSLayers(ctx, sceneData) + result, _, err := ifl.ImportNLSLayers(ctx, scene.ID(), sceneData) assert.NoError(t, err) assert.NotNil(t, result) @@ -311,10 +312,8 @@ func TestImportNLSLayers(t *testing.T) { actual := string(resultJSON) // expected - var expectedMap []map[string]interface{} - err = json.Unmarshal([]byte(fmt.Sprintf(`[ - { - "id": "01j7g9gwj6qbv286pcwwmwq5ds", + exp := fmt.Sprintf(`[{ + "id": "%s", "layerType": "simple", "sceneId": "%s", "config": { @@ -329,9 +328,10 @@ func TestImportNLSLayers(t *testing.T) { }, "title": "japan_architecture (2).csv", "visible": true, - "isSketch": false - } -]`, scene.ID())), &expectedMap) + "isSketch": false + }]`, result.IDs().LayerAt(0), scene.ID()) + var expectedMap []map[string]interface{} + err = json.Unmarshal([]byte(exp), &expectedMap) assert.NoError(t, err) expectedJSON, err := json.Marshal(expectedMap) assert.NoError(t, err) diff --git a/server/internal/usecase/interactor/plugin.go b/server/internal/usecase/interactor/plugin.go index 884fbb340d..df315405cf 100644 --- a/server/internal/usecase/interactor/plugin.go +++ b/server/internal/usecase/interactor/plugin.go @@ -3,6 +3,7 @@ package interactor import ( "archive/zip" "context" + "errors" "fmt" "io" "net/http" @@ -17,6 +18,7 @@ import ( "github.com/reearth/reearth/server/pkg/i18n" "github.com/reearth/reearth/server/pkg/id" "github.com/reearth/reearth/server/pkg/plugin" + "github.com/reearth/reearth/server/pkg/property" "github.com/reearth/reearth/server/pkg/scene" "github.com/reearth/reearthx/usecasex" ) @@ -59,7 +61,7 @@ func (i *Plugin) Fetch(ctx context.Context, ids []id.PluginID, operator *usecase return i.pluginRepo.FindByIDs(ctx, ids) } -func (i *Plugin) ExportPlugins(ctx context.Context, sce *scene.Scene, zipWriter *zip.Writer) ([]*plugin.Plugin, error) { +func (i *Plugin) ExportPlugins(ctx context.Context, sce *scene.Scene, zipWriter *zip.Writer) ([]*plugin.Plugin, []*property.Schema, error) { pluginIDs := sce.PluginIds() var filteredPluginIDs []id.PluginID @@ -72,48 +74,234 @@ func (i *Plugin) ExportPlugins(ctx context.Context, sce *scene.Scene, zipWriter plgs, err := i.pluginRepo.FindByIDs(ctx, filteredPluginIDs) if err != nil { - return nil, err + return nil, nil, err } + schemas := make([]*property.Schema, 0) for _, plg := range plgs { for _, extension := range plg.Extensions() { extensionFileName := fmt.Sprintf("%s.js", extension.ID().String()) zipEntryPath := fmt.Sprintf("plugins/%s/%s", plg.ID().String(), extensionFileName) zipEntry, err := zipWriter.Create(zipEntryPath) if err != nil { - return nil, err + return nil, nil, err } + // get plugin file stream, err := i.file.ReadPluginFile(ctx, plg.ID(), extensionFileName) if err != nil { if stream != nil { _ = stream.Close() } - return nil, err + return nil, nil, err } _, err = io.Copy(zipEntry, stream) if err != nil { _ = stream.Close() - return nil, err + return nil, nil, err } if err := stream.Close(); err != nil { - return nil, err + return nil, nil, err + } + + // get property schem + schema, err := i.propertySchemaRepo.FindByID(ctx, extension.Schema()) + if err != nil { + return nil, nil, err + } + schemas = append(schemas, schema) + } + } + + return plgs, schemas, nil +} + +func parsePropertySchemaField(fieldMap map[string]interface{}) *property.SchemaField { + + // SchemaFieldChoice ------------- + chs := make([]property.SchemaFieldChoice, 0) + if choices, ok := fieldMap["choices"].([]interface{}); ok { + + for _, choice := range choices { + choiceMap := choice.(map[string]interface{}) + + chBuilder := property.NewSchemaFieldChoice() + if v, ok := choiceMap["key"].(string); ok { + chBuilder = chBuilder.Key(v) + } + if v, ok := choiceMap["title"].(string); ok { + chBuilder = chBuilder.Title(i18n.StringFrom(v)) + } + if v, ok := choiceMap["icon"].(string); ok { + chBuilder = chBuilder.Icon(v) + } + + chs = append(chs, *chBuilder.MustBuild()) + } + } + fieldId := fieldMap["fieldId"].(string) + fid := id.PropertyFieldIDFromRef(&fieldId) + fiBuilder := property.NewSchemaField().ID(*fid) + if len(chs) > 0 { + fiBuilder = fiBuilder.Choices(chs) + } + + if v, ok := fieldMap["type"].(string); ok { + t := gqlmodel.ToPropertyValueType(v) + fiBuilder = fiBuilder.Type(t) + if dv, ok := fieldMap["defaultValue"]; ok { + fiBuilder = fiBuilder.DefaultValue(property.ValueType(t).ValueFrom(dv)) + } + } + if v, ok := fieldMap["title"].(string); ok { + fiBuilder = fiBuilder.Title(i18n.StringFrom(v)) + } + if v, ok := fieldMap["description"].(string); ok { + fiBuilder = fiBuilder.Description(i18n.StringFrom(v)) + } + if v, ok := fieldMap["prefix"].(string); ok { + fiBuilder = fiBuilder.Prefix(v) + } + if v, ok := fieldMap["suffix"].(string); ok { + fiBuilder = fiBuilder.Suffix(v) + } + if v, ok := fieldMap["ui"].(string); ok { + ui := gqlmodel.FromPropertySchemaFieldUI(&v) + fiBuilder = fiBuilder.UI(*ui) + } + if v, ok := fieldMap["min"].(float64); ok { + fiBuilder = fiBuilder.Min(v) + } + if v, ok := fieldMap["max"].(float64); ok { + fiBuilder = fiBuilder.Max(v) + } + if v, ok := fieldMap["isAvailableIf"].(map[string]interface{}); ok { + fiBuilder = fiBuilder.IsAvailableIf(parseIsAvailableIf(v)) + } + return fiBuilder.MustBuild() +} + +func parseIsAvailableIf(conditionMap map[string]interface{}) *property.Condition { + fid := string(conditionMap["fieldId"].(string)) + f := id.PropertyFieldIDFromRef(&fid) + t := gqlmodel.ToPropertyValueType(conditionMap["type"].(string)) + v := property.ValueType(t).ValueFrom(conditionMap["value"]) + return &property.Condition{ + Field: *f, + Value: v, + } +} + +func parseSchemaFieldPointer(linkableFieldsMap map[string]interface{}) *property.SchemaFieldPointer { + sg := linkableFieldsMap["schemaGroupId"].(string) + f := linkableFieldsMap["fieldId"].(string) + return &property.SchemaFieldPointer{ + SchemaGroup: property.SchemaGroupID(sg), + Field: property.FieldID(f), + } +} + +func parsePropertySchema(psid id.PropertySchemaID, schemaMap map[string]interface{}) (*property.Schema, error) { + + groups := schemaMap["groups"].([]interface{}) + + // SchemaGroup ------------- + sgl := make([]*property.SchemaGroup, 0) + for _, group := range groups { + groupMap := group.(map[string]interface{}) + + // SchemaField ------------- + fil := make([]*property.SchemaField, 0) + if fields, ok := groupMap["fields"].([]interface{}); ok { + for _, field := range fields { + fieldMap := field.(map[string]interface{}) + fi := parsePropertySchemaField(fieldMap) + fil = append(fil, fi) } } + + gid := groupMap["schemaGroupId"].(string) + psgid := id.PropertySchemaGroupIDFromRef(&gid) + sgBuilder := property.NewSchemaGroup().ID(*psgid) + + sgBuilder = sgBuilder.Fields(fil) + + if v, ok := groupMap["isList"].(bool); ok { + sgBuilder = sgBuilder.IsList(v) + } + if v, ok := groupMap["isAvailableIf"].(map[string]interface{}); ok { + sgBuilder = sgBuilder.IsAvailableIf(parseIsAvailableIf(v)) + } + if v, ok := groupMap["title"].(string); ok { + sgBuilder = sgBuilder.Title(i18n.StringFrom(v)) + } + if v, ok := groupMap["collection"].(string); ok { + sgBuilder = sgBuilder.Collection(i18n.StringFrom(v)) + } + if v, ok := groupMap["representativeFieldId"].(string); ok { + rfid := id.PropertyFieldIDFromRef(&v) + sgBuilder = sgBuilder.RepresentativeField(rfid) + } + + sg := sgBuilder.MustBuild() + sgl = append(sgl, sg) + } - return plgs, nil + // LinkableFields ------------- + linkableFields := schemaMap["linkableFields"].(map[string]interface{}) + + linkableBuilder := property.NewLinkableFields() + if v, ok := linkableFields["LatLng"].(map[string]interface{}); ok { + linkableBuilder = linkableBuilder.LatLng(parseSchemaFieldPointer(v)) + } + if v, ok := linkableFields["URL"].(map[string]interface{}); ok { + linkableBuilder = linkableBuilder.URL(parseSchemaFieldPointer(v)) + } + lf := linkableBuilder.MustBuild() + + // Schema ------------- + ps := property.NewSchema(). + ID(psid). + Groups(property.NewSchemaGroupList(sgl)). + LinkableFields(*lf). + MustBuild() + return ps, nil + +} + +func parseWidgetLayout(model *jsonmodel.WidgetLayout) *plugin.WidgetLayout { + if model == nil { + return nil + } + location := plugin.WidgetLocation{ + Zone: plugin.WidgetZoneType(model.DefaultLocation.Zone), + Section: plugin.WidgetSectionType(model.DefaultLocation.Section), + Area: plugin.WidgetAreaType(model.DefaultLocation.Area), + } + wl := plugin.NewWidgetLayout( + model.Extendable.Horizontally, + model.Extendable.Vertically, + model.Extended, + model.Floating, + &location, + ) + return &wl } -func (i *Plugin) ImportPlugins(ctx context.Context, pluginsData []interface{}) ([]*plugin.Plugin, error) { +func (i *Plugin) ImportPlugins(ctx context.Context, sce *scene.Scene, pluginsData []interface{}, schemasData []interface{}) ([]*plugin.Plugin, property.SchemaList, error) { var pluginsJSON = jsonmodel.ToPluginsFromJSON(pluginsData) + readableFilter := repo.SceneFilter{Readable: scene.IDList{sce.ID()}} + writableFilter := repo.SceneFilter{Writable: scene.IDList{sce.ID()}} + + var propertySchemaIDs []id.PropertySchemaID var pluginIDs []id.PluginID for _, pluginJSON := range pluginsJSON { pid, err := jsonmodel.ToPluginID(pluginJSON.ID) if err != nil { - return nil, err + return nil, nil, err } pluginIDs = append(pluginIDs, pid) @@ -121,7 +309,7 @@ func (i *Plugin) ImportPlugins(ctx context.Context, pluginsData []interface{}) ( for _, pluginJSONextension := range pluginJSON.Extensions { psid, err := jsonmodel.ToPropertySchemaID(pluginJSONextension.PropertySchemaID) if err != nil { - return nil, err + return nil, nil, err } extension, err := plugin.NewExtension(). ID(id.PluginExtensionID(pluginJSONextension.ExtensionID)). @@ -130,13 +318,30 @@ func (i *Plugin) ImportPlugins(ctx context.Context, pluginsData []interface{}) ( Description(i18n.StringFrom(pluginJSONextension.Description)). Icon(pluginJSONextension.Icon). SingleOnly(*pluginJSONextension.SingleOnly). + WidgetLayout(parseWidgetLayout(pluginJSONextension.WidgetLayout)). Schema(psid). Build() if err != nil { - return nil, err + return nil, nil, err } extensions = append(extensions, extension) + + // Save propertySchema + for _, schema := range schemasData { + schemaMap, _ := schema.(map[string]interface{}) + if schemaMap["id"].(string) == psid.String() { + ps, err := parsePropertySchema(psid, schemaMap) + if err != nil { + return nil, nil, err + } + if err := i.propertySchemaRepo.Filtered(writableFilter).Save(ctx, ps); err != nil { + return nil, nil, errors.New("Save propertySchema :" + err.Error()) + } + propertySchemaIDs = append(propertySchemaIDs, ps.ID()) + } + } } + p, err := plugin.New(). ID(pid). Name(i18n.StringFrom(pluginJSON.Name)). @@ -146,20 +351,26 @@ func (i *Plugin) ImportPlugins(ctx context.Context, pluginsData []interface{}) ( Extensions(extensions). Build() if err != nil { - return nil, err + return nil, nil, err } if !p.ID().System() { - if err := i.pluginRepo.Save(ctx, p); err != nil { - return nil, err + // Save plugin + if err := i.pluginRepo.Filtered(writableFilter).Save(ctx, p); err != nil { + return nil, nil, errors.New("Save plugin :" + err.Error()) } } } - plgs, err := i.pluginRepo.FindByIDs(ctx, pluginIDs) + + plgs, err := i.pluginRepo.Filtered(readableFilter).FindByIDs(ctx, pluginIDs) + if err != nil { + return nil, nil, err + } + pss, err := i.propertySchemaRepo.Filtered(readableFilter).FindByIDs(ctx, propertySchemaIDs) if err != nil { - return nil, err + return nil, nil, err } - return plgs, nil + return plgs, pss, nil } func (i *Plugin) ImporPluginFile(ctx context.Context, pid id.PluginID, name string, zipFile *zip.File) error { diff --git a/server/internal/usecase/interactor/plugin_test.go b/server/internal/usecase/interactor/plugin_test.go index ad94e56afb..4a62b6f7bd 100644 --- a/server/internal/usecase/interactor/plugin_test.go +++ b/server/internal/usecase/interactor/plugin_test.go @@ -1,175 +1,2207 @@ package interactor import ( + // "context" + // "encoding/json" + "bytes" "context" "encoding/json" "testing" - "github.com/reearth/reearth/server/internal/adapter/gql/gqlmodel" "github.com/reearth/reearth/server/internal/infrastructure/fs" "github.com/reearth/reearth/server/internal/infrastructure/memory" "github.com/reearth/reearth/server/internal/usecase/gateway" + "github.com/reearth/reearth/server/pkg/id" + "github.com/reearth/reearth/server/pkg/policy" + "github.com/reearth/reearth/server/pkg/project" + "github.com/reearth/reearth/server/pkg/scene" + "github.com/reearth/reearthx/account/accountdomain" + "github.com/reearth/reearthx/account/accountdomain/workspace" "github.com/samber/lo" "github.com/spf13/afero" "github.com/stretchr/testify/assert" ) +// go test -v -run TestImportPlugins ./internal/usecase/interactor/... + func TestImportPlugins(t *testing.T) { ctx := context.Background() db := memory.New() + ws := workspace.New().NewID().Policy(policy.ID("policy").Ref()).MustBuild() + prj, _ := project.New().NewID().Workspace(ws.ID()).Build() + _ = db.Project.Save(ctx, prj) + sce, _ := scene.New().NewID().Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).RootLayer(id.NewLayerID()).Build() + _ = db.Scene.Save(ctx, sce) + is := NewPlugin(db, &gateway.Container{ File: lo.Must(fs.NewFile(afero.NewMemMapFs(), "https://example.com")), }) + pluginsJson := bytes.Replace(pluginsJson, []byte("01jaxywcjw38sv2v28e4hcbha8"), []byte(sce.ID().String()), -1) + var pluginsData []interface{} - err := json.Unmarshal([]byte(`[ - { - "id": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0", - "sceneId": "01j7g9ddv4sbf8tgt5c6xxj5xc", - "name": "Plugin Communication Demo for Beta", - "version": "1.0.0", - "description": "", - "author": "", - "repositoryUrl": "", - "extensions": [ - { - "extensionId": "widgetcomm", - "pluginId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0", - "type": "WIDGET", - "name": "Communication Demo Widget", - "description": "", - "icon": "", - "singleOnly": false, - "propertySchemaId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm", - "allTranslatedName": { - "en": "Communication Demo Widget" - }, - "translatedName": "", - "translatedDescription": "" - }, - { - "extensionId": "storyblockcomm", - "pluginId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0", - "type": "StoryBlock", - "name": "Communication Demo Story Block", - "description": "", - "icon": "", - "singleOnly": false, - "propertySchemaId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm", - "allTranslatedName": { - "en": "Communication Demo Story Block" - }, - "translatedName": "", - "translatedDescription": "" - }, - { - "extensionId": "infoboxblockcomm", - "pluginId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0", - "type": "InfoboxBlock", - "name": "Communication Demo Infobox Block", - "description": "", - "icon": "", - "singleOnly": false, - "propertySchemaId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm", - "allTranslatedName": { - "en": "Communication Demo Infobox Block" - }, - "translatedName": "", - "translatedDescription": "" - } - ], - "allTranslatedName": { - "en": "Plugin Communication Demo for Beta" - }, - "translatedName": "", - "translatedDescription": "" - } - ]`), &pluginsData) + err := json.Unmarshal(pluginsJson, &pluginsData) assert.NoError(t, err) - // invoke the target function - result, err := is.ImportPlugins(ctx, pluginsData) + var schemasData []interface{} + err = json.Unmarshal(schemasJson, &schemasData) assert.NoError(t, err) - assert.NotNil(t, result) - // actual - temp := gqlmodel.ToPlugins(result) - resultJSON, err := json.Marshal(temp) + // invoke the target function + result, result2, err := is.ImportPlugins(ctx, sce, pluginsData, schemasData) assert.NoError(t, err) - actual := string(resultJSON) + assert.NotNil(t, result) + assert.NotNil(t, result2) +} - // expected - var expectedMap []map[string]interface{} - err = json.Unmarshal([]byte(`[ +var pluginsJson = []byte(` +[ { - "id": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0", - "sceneId": "01j7g9ddv4sbf8tgt5c6xxj5xc", - "name": "Plugin Communication Demo for Beta", - "version": "1.0.0", - "description": "", - "author": "", - "repositoryUrl": "", - "extensions": [ - { - "extensionId": "widgetcomm", - "pluginId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0", - "type": "WIDGET", - "name": "Communication Demo Widget", - "description": "", - "icon": "", - "singleOnly": false, - "propertySchemaId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm", - "allTranslatedName": { - "en": "Communication Demo Widget" - }, - "translatedName": "", - "translatedDescription": "" + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0", + "sceneId": "01jaxywcjw38sv2v28e4hcbha8", + "name": "Plugin Communication Demo", + "version": "1.0.0", + "description": "", + "author": "", + "repositoryUrl": "", + "extensions": [ + { + "extensionId": "widgetcomm", + "pluginId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0", + "type": "WIDGET", + "name": "Communication Demo Widget", + "description": "", + "icon": "", + "singleOnly": false, + "propertySchemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0/widgetcomm", + "allTranslatedName": { + "en": "Communication Demo Widget" + }, + "translatedName": "", + "translatedDescription": "" + }, + { + "extensionId": "blockcomm", + "pluginId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0", + "type": "BLOCK", + "name": "Communication Demo Block", + "description": "", + "icon": "", + "singleOnly": false, + "propertySchemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0/blockcomm", + "allTranslatedName": { + "en": "Communication Demo Block" + }, + "translatedName": "", + "translatedDescription": "" + } + ], + "allTranslatedName": { + "en": "Plugin Communication Demo" }, - { - "extensionId": "storyblockcomm", - "pluginId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0", - "type": "StoryBlock", - "name": "Communication Demo Story Block", - "description": "", - "icon": "", - "singleOnly": false, - "propertySchemaId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm", - "allTranslatedName": { - "en": "Communication Demo Story Block" - }, - "translatedName": "", - "translatedDescription": "" + "translatedName": "", + "translatedDescription": "" + }, + { + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0", + "sceneId": "01jaxywcjw38sv2v28e4hcbha8", + "name": "Plugin Communication Demo for Beta", + "version": "1.0.0", + "description": "", + "author": "", + "repositoryUrl": "", + "extensions": [ + { + "extensionId": "widgetcomm", + "pluginId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0", + "type": "WIDGET", + "name": "Communication Demo Widget", + "description": "", + "icon": "", + "singleOnly": false, + "propertySchemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm", + "allTranslatedName": { + "en": "Communication Demo Widget" + }, + "translatedName": "", + "translatedDescription": "" + }, + { + "extensionId": "storyblockcomm", + "pluginId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0", + "type": "StoryBlock", + "name": "Communication Demo Story Block", + "description": "", + "icon": "", + "singleOnly": false, + "propertySchemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm", + "allTranslatedName": { + "en": "Communication Demo Story Block" + }, + "translatedName": "", + "translatedDescription": "" + }, + { + "extensionId": "infoboxblockcomm", + "pluginId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0", + "type": "InfoboxBlock", + "name": "Communication Demo Infobox Block", + "description": "", + "icon": "", + "singleOnly": false, + "propertySchemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm", + "allTranslatedName": { + "en": "Communication Demo Infobox Block" + }, + "translatedName": "", + "translatedDescription": "" + } + ], + "allTranslatedName": { + "en": "Plugin Communication Demo for Beta" }, - { - "extensionId": "infoboxblockcomm", - "pluginId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0", - "type": "InfoboxBlock", - "name": "Communication Demo Infobox Block", - "description": "", - "icon": "", - "singleOnly": false, - "propertySchemaId": "01j7g9ddv4sbf8tgt5c6xxj5xc~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm", - "allTranslatedName": { - "en": "Communication Demo Infobox Block" - }, - "translatedName": "", - "translatedDescription": "" - } - ], - "allTranslatedName": { - "en": "Plugin Communication Demo for Beta" - }, - "translatedName": "", - "translatedDescription": "" + "translatedName": "", + "translatedDescription": "" + }, + { + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-visualizer-plugin-welcome-page~1.0.0", + "sceneId": "01jaxywcjw38sv2v28e4hcbha8", + "name": "Visualizer Welcome Page Plugin", + "version": "1.0.0", + "description": "", + "author": "", + "repositoryUrl": "", + "extensions": [ + { + "extensionId": "welcome", + "pluginId": "01jaxywcjw38sv2v28e4hcbha8~reearth-visualizer-plugin-welcome-page~1.0.0", + "type": "WIDGET", + "name": "Welcome Page", + "description": "", + "icon": "", + "singleOnly": true, + "widgetLayout": { + "extendable": { + "vertically": false, + "horizontally": false + }, + "extended": false, + "floating": false, + "defaultLocation": { + "zone": "INNER", + "section": "CENTER", + "area": "TOP" + } + }, + "propertySchemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-visualizer-plugin-welcome-page~1.0.0/welcome", + "allTranslatedName": { + "en": "Welcome Page" + }, + "translatedName": "", + "translatedDescription": "" + } + ], + "allTranslatedName": { + "en": "Visualizer Welcome Page Plugin" + }, + "translatedName": "", + "translatedDescription": "" } - ]`), &expectedMap) - assert.NoError(t, err) - expectedJSON, err := json.Marshal(expectedMap) - assert.NoError(t, err) - expected := string(expectedJSON) - - // comparison check - assert.JSONEq(t, expected, actual) +] +`) -} +var schemasJson = []byte(` +[ + { + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0/widgetcomm", + "groups": [], + "linkableFields": { + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0/widgetcomm" + } + }, + { + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0/blockcomm", + "groups": [], + "linkableFields": { + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo~1.0.0/blockcomm" + } + }, + { + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm", + "groups": [ + { + "schemaGroupId": "page_settings", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm", + "fields": [ + { + "fieldId": "page_type", + "type": "STRING", + "title": "Page type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "welcome", + "title": "Welcome Page", + "allTranslatedTitle": { + "en": "Welcome Page" + }, + "translatedTitle": "" + }, + { + "key": "markdown", + "title": "MD Content Page", + "allTranslatedTitle": { + "en": "MD Content Page" + }, + "translatedTitle": "" + }, + { + "key": "tutorial", + "title": "Tutorial Page", + "allTranslatedTitle": { + "en": "Tutorial Page" + }, + "translatedTitle": "" + }, + { + "key": "agreement", + "title": "Agreement Page", + "allTranslatedTitle": { + "en": "Agreement Page" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Page type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_title", + "type": "STRING", + "title": "Title", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Title" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_description", + "type": "STRING", + "title": "Description", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Description" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_video_url", + "type": "URL", + "title": "Video url", + "description": "", + "defaultValue": null, + "ui": "FILE", + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Video url" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "markdown_content", + "type": "STRING", + "title": "MD Content", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "markdown" + }, + "allTranslatedTitle": { + "en": "MD Content" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": true, + "title": "Page Settings", + "allTranslatedTitle": { + "en": "Page Settings" + }, + "representativeFieldId": "page_type", + "representativeField": { + "fieldId": "page_type", + "type": "STRING", + "title": "Page type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "welcome", + "title": "Welcome Page", + "allTranslatedTitle": { + "en": "Welcome Page" + }, + "translatedTitle": "" + }, + { + "key": "markdown", + "title": "MD Content Page", + "allTranslatedTitle": { + "en": "MD Content Page" + }, + "translatedTitle": "" + }, + { + "key": "tutorial", + "title": "Tutorial Page", + "allTranslatedTitle": { + "en": "Tutorial Page" + }, + "translatedTitle": "" + }, + { + "key": "agreement", + "title": "Agreement Page", + "allTranslatedTitle": { + "en": "Agreement Page" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Page type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "appearance", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm", + "fields": [ + { + "fieldId": "primary_color", + "type": "STRING", + "title": "Primary color", + "description": "", + "defaultValue": null, + "ui": "COLOR", + "allTranslatedTitle": { + "en": "Primary color" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": false, + "title": "Appearance", + "allTranslatedTitle": { + "en": "Appearance" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "available_if_test", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm", + "fields": [ + { + "fieldId": "test_type", + "type": "STRING", + "title": "Test type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "type_a", + "title": "A", + "allTranslatedTitle": { + "en": "A" + }, + "translatedTitle": "" + }, + { + "key": "type_b", + "title": "B", + "allTranslatedTitle": { + "en": "B" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Test type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "test_value_a", + "type": "STRING", + "title": "Value A", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "test_type", + "type": "STRING", + "value": "type_a" + }, + "allTranslatedTitle": { + "en": "Value A" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "test_value_b", + "type": "STRING", + "title": "Value B", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "test_type", + "type": "STRING", + "value": "type_b" + }, + "allTranslatedTitle": { + "en": "Value B" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": false, + "title": "Avaliable If Test", + "allTranslatedTitle": { + "en": "Avaliable If Test" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "tiles", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm", + "fields": [ + { + "fieldId": "tile_type", + "type": "STRING", + "title": "Tile type", + "description": "", + "defaultValue": "default", + "choices": [ + { + "key": "default", + "title": "Default", + "allTranslatedTitle": { + "en": "Default" + }, + "translatedTitle": "" + }, + { + "key": "default_label", + "title": "Labelled", + "allTranslatedTitle": { + "en": "Labelled" + }, + "translatedTitle": "" + }, + { + "key": "default_road", + "title": "Road Map", + "allTranslatedTitle": { + "en": "Road Map" + }, + "translatedTitle": "" + }, + { + "key": "stamen_watercolor", + "title": "Stamen Watercolor", + "allTranslatedTitle": { + "en": "Stamen Watercolor" + }, + "translatedTitle": "" + }, + { + "key": "stamen_toner", + "title": "Stamen Toner", + "allTranslatedTitle": { + "en": "Stamen Toner" + }, + "translatedTitle": "" + }, + { + "key": "open_street_map", + "title": "OpenStreetMap", + "allTranslatedTitle": { + "en": "OpenStreetMap" + }, + "translatedTitle": "" + }, + { + "key": "esri_world_topo", + "title": "ESRI Topography", + "allTranslatedTitle": { + "en": "ESRI Topography" + }, + "translatedTitle": "" + }, + { + "key": "black_marble", + "title": "Earth at night", + "allTranslatedTitle": { + "en": "Earth at night" + }, + "translatedTitle": "" + }, + { + "key": "japan_gsi_standard", + "title": "Japan GSI Standard Map", + "allTranslatedTitle": { + "en": "Japan GSI Standard Map" + }, + "translatedTitle": "" + }, + { + "key": "url", + "title": "URL", + "allTranslatedTitle": { + "en": "URL" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Tile type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_url", + "type": "STRING", + "title": "Tile map URL", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "tile_type", + "type": "STRING", + "value": "url" + }, + "allTranslatedTitle": { + "en": "Tile map URL" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_minLevel", + "type": "NUMBER", + "title": "Minimum zoom level", + "description": "", + "defaultValue": null, + "min": 0, + "max": 30, + "allTranslatedTitle": { + "en": "Minimum zoom level" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_maxLevel", + "type": "NUMBER", + "title": "Maximum zoom level", + "description": "", + "defaultValue": null, + "min": 0, + "max": 30, + "allTranslatedTitle": { + "en": "Maximum zoom level" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_opacity", + "type": "NUMBER", + "title": "Opacity", + "description": "Change the opacity of the selected tile map. Min: 0 Max: 1", + "defaultValue": 1, + "ui": "SLIDER", + "min": 0, + "max": 1, + "allTranslatedTitle": { + "en": "Opacity" + }, + "allTranslatedDescription": { + "en": "Change the opacity of the selected tile map. Min: 0 Max: 1" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": true, + "title": "Tiles", + "allTranslatedTitle": { + "en": "Tiles" + }, + "representativeFieldId": "tile_type", + "representativeField": { + "fieldId": "tile_type", + "type": "STRING", + "title": "Tile type", + "description": "", + "defaultValue": "default", + "choices": [ + { + "key": "default", + "title": "Default", + "allTranslatedTitle": { + "en": "Default" + }, + "translatedTitle": "" + }, + { + "key": "default_label", + "title": "Labelled", + "allTranslatedTitle": { + "en": "Labelled" + }, + "translatedTitle": "" + }, + { + "key": "default_road", + "title": "Road Map", + "allTranslatedTitle": { + "en": "Road Map" + }, + "translatedTitle": "" + }, + { + "key": "stamen_watercolor", + "title": "Stamen Watercolor", + "allTranslatedTitle": { + "en": "Stamen Watercolor" + }, + "translatedTitle": "" + }, + { + "key": "stamen_toner", + "title": "Stamen Toner", + "allTranslatedTitle": { + "en": "Stamen Toner" + }, + "translatedTitle": "" + }, + { + "key": "open_street_map", + "title": "OpenStreetMap", + "allTranslatedTitle": { + "en": "OpenStreetMap" + }, + "translatedTitle": "" + }, + { + "key": "esri_world_topo", + "title": "ESRI Topography", + "allTranslatedTitle": { + "en": "ESRI Topography" + }, + "translatedTitle": "" + }, + { + "key": "black_marble", + "title": "Earth at night", + "allTranslatedTitle": { + "en": "Earth at night" + }, + "translatedTitle": "" + }, + { + "key": "japan_gsi_standard", + "title": "Japan GSI Standard Map", + "allTranslatedTitle": { + "en": "Japan GSI Standard Map" + }, + "translatedTitle": "" + }, + { + "key": "url", + "title": "URL", + "allTranslatedTitle": { + "en": "URL" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Tile type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + "translatedTitle": "" + } + ], + "linkableFields": { + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/widgetcomm" + } + }, + { + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm", + "groups": [ + { + "schemaGroupId": "page_settings", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm", + "fields": [ + { + "fieldId": "page_type", + "type": "STRING", + "title": "Page type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "welcome", + "title": "Welcome Page", + "allTranslatedTitle": { + "en": "Welcome Page" + }, + "translatedTitle": "" + }, + { + "key": "markdown", + "title": "MD Content Page", + "allTranslatedTitle": { + "en": "MD Content Page" + }, + "translatedTitle": "" + }, + { + "key": "tutorial", + "title": "Tutorial Page", + "allTranslatedTitle": { + "en": "Tutorial Page" + }, + "translatedTitle": "" + }, + { + "key": "agreement", + "title": "Agreement Page", + "allTranslatedTitle": { + "en": "Agreement Page" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Page type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_title", + "type": "STRING", + "title": "Title", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Title" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_description", + "type": "STRING", + "title": "Description", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Description" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_video_url", + "type": "URL", + "title": "Video url", + "description": "", + "defaultValue": null, + "ui": "FILE", + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Video url" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "markdown_content", + "type": "STRING", + "title": "MD Content", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "markdown" + }, + "allTranslatedTitle": { + "en": "MD Content" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": true, + "title": "Page Settings", + "allTranslatedTitle": { + "en": "Page Settings" + }, + "representativeFieldId": "page_type", + "representativeField": { + "fieldId": "page_type", + "type": "STRING", + "title": "Page type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "welcome", + "title": "Welcome Page", + "allTranslatedTitle": { + "en": "Welcome Page" + }, + "translatedTitle": "" + }, + { + "key": "markdown", + "title": "MD Content Page", + "allTranslatedTitle": { + "en": "MD Content Page" + }, + "translatedTitle": "" + }, + { + "key": "tutorial", + "title": "Tutorial Page", + "allTranslatedTitle": { + "en": "Tutorial Page" + }, + "translatedTitle": "" + }, + { + "key": "agreement", + "title": "Agreement Page", + "allTranslatedTitle": { + "en": "Agreement Page" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Page type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "appearance", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm", + "fields": [ + { + "fieldId": "primary_color", + "type": "STRING", + "title": "Primary color", + "description": "", + "defaultValue": null, + "ui": "COLOR", + "allTranslatedTitle": { + "en": "Primary color" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": false, + "title": "Appearance", + "allTranslatedTitle": { + "en": "Appearance" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "available_if_test", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm", + "fields": [ + { + "fieldId": "test_type", + "type": "STRING", + "title": "Test type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "type_a", + "title": "A", + "allTranslatedTitle": { + "en": "A" + }, + "translatedTitle": "" + }, + { + "key": "type_b", + "title": "B", + "allTranslatedTitle": { + "en": "B" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Test type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "test_value_a", + "type": "STRING", + "title": "Value A", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "test_type", + "type": "STRING", + "value": "type_a" + }, + "allTranslatedTitle": { + "en": "Value A" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "test_value_b", + "type": "STRING", + "title": "Value B", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "test_type", + "type": "STRING", + "value": "type_b" + }, + "allTranslatedTitle": { + "en": "Value B" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": false, + "title": "Avaliable If Test", + "allTranslatedTitle": { + "en": "Avaliable If Test" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "tiles", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm", + "fields": [ + { + "fieldId": "tile_type", + "type": "STRING", + "title": "Tile type", + "description": "", + "defaultValue": "default", + "choices": [ + { + "key": "default", + "title": "Default", + "allTranslatedTitle": { + "en": "Default" + }, + "translatedTitle": "" + }, + { + "key": "default_label", + "title": "Labelled", + "allTranslatedTitle": { + "en": "Labelled" + }, + "translatedTitle": "" + }, + { + "key": "default_road", + "title": "Road Map", + "allTranslatedTitle": { + "en": "Road Map" + }, + "translatedTitle": "" + }, + { + "key": "stamen_watercolor", + "title": "Stamen Watercolor", + "allTranslatedTitle": { + "en": "Stamen Watercolor" + }, + "translatedTitle": "" + }, + { + "key": "stamen_toner", + "title": "Stamen Toner", + "allTranslatedTitle": { + "en": "Stamen Toner" + }, + "translatedTitle": "" + }, + { + "key": "open_street_map", + "title": "OpenStreetMap", + "allTranslatedTitle": { + "en": "OpenStreetMap" + }, + "translatedTitle": "" + }, + { + "key": "esri_world_topo", + "title": "ESRI Topography", + "allTranslatedTitle": { + "en": "ESRI Topography" + }, + "translatedTitle": "" + }, + { + "key": "black_marble", + "title": "Earth at night", + "allTranslatedTitle": { + "en": "Earth at night" + }, + "translatedTitle": "" + }, + { + "key": "japan_gsi_standard", + "title": "Japan GSI Standard Map", + "allTranslatedTitle": { + "en": "Japan GSI Standard Map" + }, + "translatedTitle": "" + }, + { + "key": "url", + "title": "URL", + "allTranslatedTitle": { + "en": "URL" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Tile type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_url", + "type": "STRING", + "title": "Tile map URL", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "tile_type", + "type": "STRING", + "value": "url" + }, + "allTranslatedTitle": { + "en": "Tile map URL" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_minLevel", + "type": "NUMBER", + "title": "Minimum zoom level", + "description": "", + "defaultValue": null, + "min": 0, + "max": 30, + "allTranslatedTitle": { + "en": "Minimum zoom level" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_maxLevel", + "type": "NUMBER", + "title": "Maximum zoom level", + "description": "", + "defaultValue": null, + "min": 0, + "max": 30, + "allTranslatedTitle": { + "en": "Maximum zoom level" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_opacity", + "type": "NUMBER", + "title": "Opacity", + "description": "Change the opacity of the selected tile map. Min: 0 Max: 1", + "defaultValue": 1, + "ui": "SLIDER", + "min": 0, + "max": 1, + "allTranslatedTitle": { + "en": "Opacity" + }, + "allTranslatedDescription": { + "en": "Change the opacity of the selected tile map. Min: 0 Max: 1" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": true, + "title": "Tiles", + "allTranslatedTitle": { + "en": "Tiles" + }, + "representativeFieldId": "tile_type", + "representativeField": { + "fieldId": "tile_type", + "type": "STRING", + "title": "Tile type", + "description": "", + "defaultValue": "default", + "choices": [ + { + "key": "default", + "title": "Default", + "allTranslatedTitle": { + "en": "Default" + }, + "translatedTitle": "" + }, + { + "key": "default_label", + "title": "Labelled", + "allTranslatedTitle": { + "en": "Labelled" + }, + "translatedTitle": "" + }, + { + "key": "default_road", + "title": "Road Map", + "allTranslatedTitle": { + "en": "Road Map" + }, + "translatedTitle": "" + }, + { + "key": "stamen_watercolor", + "title": "Stamen Watercolor", + "allTranslatedTitle": { + "en": "Stamen Watercolor" + }, + "translatedTitle": "" + }, + { + "key": "stamen_toner", + "title": "Stamen Toner", + "allTranslatedTitle": { + "en": "Stamen Toner" + }, + "translatedTitle": "" + }, + { + "key": "open_street_map", + "title": "OpenStreetMap", + "allTranslatedTitle": { + "en": "OpenStreetMap" + }, + "translatedTitle": "" + }, + { + "key": "esri_world_topo", + "title": "ESRI Topography", + "allTranslatedTitle": { + "en": "ESRI Topography" + }, + "translatedTitle": "" + }, + { + "key": "black_marble", + "title": "Earth at night", + "allTranslatedTitle": { + "en": "Earth at night" + }, + "translatedTitle": "" + }, + { + "key": "japan_gsi_standard", + "title": "Japan GSI Standard Map", + "allTranslatedTitle": { + "en": "Japan GSI Standard Map" + }, + "translatedTitle": "" + }, + { + "key": "url", + "title": "URL", + "allTranslatedTitle": { + "en": "URL" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Tile type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + "translatedTitle": "" + } + ], + "linkableFields": { + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/storyblockcomm" + } + }, + { + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm", + "groups": [ + { + "schemaGroupId": "page_settings", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm", + "fields": [ + { + "fieldId": "page_type", + "type": "STRING", + "title": "Page type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "welcome", + "title": "Welcome Page", + "allTranslatedTitle": { + "en": "Welcome Page" + }, + "translatedTitle": "" + }, + { + "key": "markdown", + "title": "MD Content Page", + "allTranslatedTitle": { + "en": "MD Content Page" + }, + "translatedTitle": "" + }, + { + "key": "tutorial", + "title": "Tutorial Page", + "allTranslatedTitle": { + "en": "Tutorial Page" + }, + "translatedTitle": "" + }, + { + "key": "agreement", + "title": "Agreement Page", + "allTranslatedTitle": { + "en": "Agreement Page" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Page type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_title", + "type": "STRING", + "title": "Title", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Title" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_description", + "type": "STRING", + "title": "Description", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Description" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "welcome_video_url", + "type": "URL", + "title": "Video url", + "description": "", + "defaultValue": null, + "ui": "FILE", + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome" + }, + "allTranslatedTitle": { + "en": "Video url" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "markdown_content", + "type": "STRING", + "title": "MD Content", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "markdown" + }, + "allTranslatedTitle": { + "en": "MD Content" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": true, + "title": "Page Settings", + "allTranslatedTitle": { + "en": "Page Settings" + }, + "representativeFieldId": "page_type", + "representativeField": { + "fieldId": "page_type", + "type": "STRING", + "title": "Page type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "welcome", + "title": "Welcome Page", + "allTranslatedTitle": { + "en": "Welcome Page" + }, + "translatedTitle": "" + }, + { + "key": "markdown", + "title": "MD Content Page", + "allTranslatedTitle": { + "en": "MD Content Page" + }, + "translatedTitle": "" + }, + { + "key": "tutorial", + "title": "Tutorial Page", + "allTranslatedTitle": { + "en": "Tutorial Page" + }, + "translatedTitle": "" + }, + { + "key": "agreement", + "title": "Agreement Page", + "allTranslatedTitle": { + "en": "Agreement Page" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Page type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "appearance", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm", + "fields": [ + { + "fieldId": "primary_color", + "type": "STRING", + "title": "Primary color", + "description": "", + "defaultValue": null, + "ui": "COLOR", + "allTranslatedTitle": { + "en": "Primary color" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": false, + "title": "Appearance", + "allTranslatedTitle": { + "en": "Appearance" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "available_if_test", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm", + "fields": [ + { + "fieldId": "test_type", + "type": "STRING", + "title": "Test type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "type_a", + "title": "A", + "allTranslatedTitle": { + "en": "A" + }, + "translatedTitle": "" + }, + { + "key": "type_b", + "title": "B", + "allTranslatedTitle": { + "en": "B" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Test type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "test_value_a", + "type": "STRING", + "title": "Value A", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "test_type", + "type": "STRING", + "value": "type_a" + }, + "allTranslatedTitle": { + "en": "Value A" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "test_value_b", + "type": "STRING", + "title": "Value B", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "test_type", + "type": "STRING", + "value": "type_b" + }, + "allTranslatedTitle": { + "en": "Value B" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": false, + "title": "Avaliable If Test", + "allTranslatedTitle": { + "en": "Avaliable If Test" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "tiles", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm", + "fields": [ + { + "fieldId": "tile_type", + "type": "STRING", + "title": "Tile type", + "description": "", + "defaultValue": "default", + "choices": [ + { + "key": "default", + "title": "Default", + "allTranslatedTitle": { + "en": "Default" + }, + "translatedTitle": "" + }, + { + "key": "default_label", + "title": "Labelled", + "allTranslatedTitle": { + "en": "Labelled" + }, + "translatedTitle": "" + }, + { + "key": "default_road", + "title": "Road Map", + "allTranslatedTitle": { + "en": "Road Map" + }, + "translatedTitle": "" + }, + { + "key": "stamen_watercolor", + "title": "Stamen Watercolor", + "allTranslatedTitle": { + "en": "Stamen Watercolor" + }, + "translatedTitle": "" + }, + { + "key": "stamen_toner", + "title": "Stamen Toner", + "allTranslatedTitle": { + "en": "Stamen Toner" + }, + "translatedTitle": "" + }, + { + "key": "open_street_map", + "title": "OpenStreetMap", + "allTranslatedTitle": { + "en": "OpenStreetMap" + }, + "translatedTitle": "" + }, + { + "key": "esri_world_topo", + "title": "ESRI Topography", + "allTranslatedTitle": { + "en": "ESRI Topography" + }, + "translatedTitle": "" + }, + { + "key": "black_marble", + "title": "Earth at night", + "allTranslatedTitle": { + "en": "Earth at night" + }, + "translatedTitle": "" + }, + { + "key": "japan_gsi_standard", + "title": "Japan GSI Standard Map", + "allTranslatedTitle": { + "en": "Japan GSI Standard Map" + }, + "translatedTitle": "" + }, + { + "key": "url", + "title": "URL", + "allTranslatedTitle": { + "en": "URL" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Tile type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_url", + "type": "STRING", + "title": "Tile map URL", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "tile_type", + "type": "STRING", + "value": "url" + }, + "allTranslatedTitle": { + "en": "Tile map URL" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_minLevel", + "type": "NUMBER", + "title": "Minimum zoom level", + "description": "", + "defaultValue": null, + "min": 0, + "max": 30, + "allTranslatedTitle": { + "en": "Minimum zoom level" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_maxLevel", + "type": "NUMBER", + "title": "Maximum zoom level", + "description": "", + "defaultValue": null, + "min": 0, + "max": 30, + "allTranslatedTitle": { + "en": "Maximum zoom level" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tile_opacity", + "type": "NUMBER", + "title": "Opacity", + "description": "Change the opacity of the selected tile map. Min: 0 Max: 1", + "defaultValue": 1, + "ui": "SLIDER", + "min": 0, + "max": 1, + "allTranslatedTitle": { + "en": "Opacity" + }, + "allTranslatedDescription": { + "en": "Change the opacity of the selected tile map. Min: 0 Max: 1" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": true, + "title": "Tiles", + "allTranslatedTitle": { + "en": "Tiles" + }, + "representativeFieldId": "tile_type", + "representativeField": { + "fieldId": "tile_type", + "type": "STRING", + "title": "Tile type", + "description": "", + "defaultValue": "default", + "choices": [ + { + "key": "default", + "title": "Default", + "allTranslatedTitle": { + "en": "Default" + }, + "translatedTitle": "" + }, + { + "key": "default_label", + "title": "Labelled", + "allTranslatedTitle": { + "en": "Labelled" + }, + "translatedTitle": "" + }, + { + "key": "default_road", + "title": "Road Map", + "allTranslatedTitle": { + "en": "Road Map" + }, + "translatedTitle": "" + }, + { + "key": "stamen_watercolor", + "title": "Stamen Watercolor", + "allTranslatedTitle": { + "en": "Stamen Watercolor" + }, + "translatedTitle": "" + }, + { + "key": "stamen_toner", + "title": "Stamen Toner", + "allTranslatedTitle": { + "en": "Stamen Toner" + }, + "translatedTitle": "" + }, + { + "key": "open_street_map", + "title": "OpenStreetMap", + "allTranslatedTitle": { + "en": "OpenStreetMap" + }, + "translatedTitle": "" + }, + { + "key": "esri_world_topo", + "title": "ESRI Topography", + "allTranslatedTitle": { + "en": "ESRI Topography" + }, + "translatedTitle": "" + }, + { + "key": "black_marble", + "title": "Earth at night", + "allTranslatedTitle": { + "en": "Earth at night" + }, + "translatedTitle": "" + }, + { + "key": "japan_gsi_standard", + "title": "Japan GSI Standard Map", + "allTranslatedTitle": { + "en": "Japan GSI Standard Map" + }, + "translatedTitle": "" + }, + { + "key": "url", + "title": "URL", + "allTranslatedTitle": { + "en": "URL" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Tile type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + "translatedTitle": "" + } + ], + "linkableFields": { + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-plugin-communication-demo-beta~1.0.0/infoboxblockcomm" + } + }, + { + "id": "01jaxywcjw38sv2v28e4hcbha8~reearth-visualizer-plugin-welcome-page~1.0.0/welcome", + "groups": [ + { + "schemaGroupId": "page_setting", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-visualizer-plugin-welcome-page~1.0.0/welcome", + "fields": [ + { + "fieldId": "page_type", + "type": "STRING", + "title": "Page Type", + "description": "", + "defaultValue": "welcome_page", + "choices": [ + { + "key": "welcome_page", + "title": "Welcome Page", + "allTranslatedTitle": { + "en": "Welcome Page" + }, + "translatedTitle": "" + }, + { + "key": "md_page", + "title": "MD Content Page", + "allTranslatedTitle": { + "en": "MD Content Page" + }, + "translatedTitle": "" + }, + { + "key": "tutorial_page", + "title": "Tutorial Page", + "allTranslatedTitle": { + "en": "Tutorial Page" + }, + "translatedTitle": "" + }, + { + "key": "agreement_page", + "title": "Agreement Page", + "allTranslatedTitle": { + "en": "Agreement Page" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Page Type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "page_title", + "type": "STRING", + "title": "Welcome Page Title", + "description": "", + "defaultValue": null, + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome_page" + }, + "allTranslatedTitle": { + "en": "Welcome Page Title" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "page_description", + "type": "STRING", + "title": "Welcome Page Description", + "description": "", + "defaultValue": null, + "ui": "MULTILINE", + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome_page" + }, + "allTranslatedTitle": { + "en": "Welcome Page Description" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "media_type", + "type": "STRING", + "title": "Welcome Page Media Type", + "description": "", + "defaultValue": null, + "choices": [ + { + "key": "image_type", + "title": "Image", + "allTranslatedTitle": { + "en": "Image" + }, + "translatedTitle": "" + }, + { + "key": "video_type", + "title": "Video", + "allTranslatedTitle": { + "en": "Video" + }, + "translatedTitle": "" + } + ], + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "welcome_page" + }, + "allTranslatedTitle": { + "en": "Welcome Page Media Type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "media_url", + "type": "URL", + "title": "Welcome Page Media Image", + "description": "", + "defaultValue": null, + "ui": "IMAGE", + "isAvailableIf": { + "fieldId": "media_type", + "type": "STRING", + "value": "image_type" + }, + "allTranslatedTitle": { + "en": "Welcome Page Media Image" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "video_url", + "type": "URL", + "title": "Welcome Page Media Video", + "description": "", + "defaultValue": null, + "ui": "VIDEO", + "isAvailableIf": { + "fieldId": "media_type", + "type": "STRING", + "value": "video_type" + }, + "allTranslatedTitle": { + "en": "Welcome Page Media Video" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "thumbnail_video_url", + "type": "URL", + "title": "Welcome Page Media Video Thumbnail", + "description": "", + "defaultValue": null, + "ui": "IMAGE", + "isAvailableIf": { + "fieldId": "media_type", + "type": "STRING", + "value": "video_type" + }, + "allTranslatedTitle": { + "en": "Welcome Page Media Video Thumbnail" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "tutorial_page_image_url", + "type": "URL", + "title": "Tutorial Page Image", + "description": "", + "defaultValue": null, + "ui": "IMAGE", + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "tutorial_page" + }, + "allTranslatedTitle": { + "en": "Tutorial Page Image" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "md_content", + "type": "STRING", + "title": "MD Page Content", + "description": "For markdown content", + "defaultValue": null, + "ui": "MULTILINE", + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "md_page" + }, + "allTranslatedTitle": { + "en": "MD Page Content" + }, + "allTranslatedDescription": { + "en": "For markdown content" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "agree_content", + "type": "STRING", + "title": "Agreement", + "description": "Markdown content for agreement page", + "defaultValue": null, + "ui": "MULTILINE", + "isAvailableIf": { + "fieldId": "page_type", + "type": "STRING", + "value": "agreement_page" + }, + "allTranslatedTitle": { + "en": "Agreement" + }, + "allTranslatedDescription": { + "en": "Markdown content for agreement page" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": true, + "title": "Page", + "allTranslatedTitle": { + "en": "Page" + }, + "representativeFieldId": "page_type", + "representativeField": { + "fieldId": "page_type", + "type": "STRING", + "title": "Page Type", + "description": "", + "defaultValue": "welcome_page", + "choices": [ + { + "key": "welcome_page", + "title": "Welcome Page", + "allTranslatedTitle": { + "en": "Welcome Page" + }, + "translatedTitle": "" + }, + { + "key": "md_page", + "title": "MD Content Page", + "allTranslatedTitle": { + "en": "MD Content Page" + }, + "translatedTitle": "" + }, + { + "key": "tutorial_page", + "title": "Tutorial Page", + "allTranslatedTitle": { + "en": "Tutorial Page" + }, + "translatedTitle": "" + }, + { + "key": "agreement_page", + "title": "Agreement Page", + "allTranslatedTitle": { + "en": "Agreement Page" + }, + "translatedTitle": "" + } + ], + "allTranslatedTitle": { + "en": "Page Type" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + "translatedTitle": "" + }, + { + "schemaGroupId": "appearance", + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-visualizer-plugin-welcome-page~1.0.0/welcome", + "fields": [ + { + "fieldId": "primary_color", + "type": "STRING", + "title": "Primary Color", + "description": "", + "defaultValue": "#0085BE", + "ui": "COLOR", + "allTranslatedTitle": { + "en": "Primary Color" + }, + "translatedTitle": "", + "translatedDescription": "" + }, + { + "fieldId": "bg_color", + "type": "STRING", + "title": "Background Color", + "description": "", + "defaultValue": "#000", + "ui": "COLOR", + "allTranslatedTitle": { + "en": "Background Color" + }, + "translatedTitle": "", + "translatedDescription": "" + } + ], + "isList": false, + "title": "Appearance", + "allTranslatedTitle": { + "en": "Appearance" + }, + "translatedTitle": "" + } + ], + "linkableFields": { + "schemaId": "01jaxywcjw38sv2v28e4hcbha8~reearth-visualizer-plugin-welcome-page~1.0.0/welcome" + } + } +] +`) diff --git a/server/internal/usecase/interactor/project.go b/server/internal/usecase/interactor/project.go index 4363382a7c..e0347645a5 100644 --- a/server/internal/usecase/interactor/project.go +++ b/server/internal/usecase/interactor/project.go @@ -379,7 +379,7 @@ func (i *Project) Publish(ctx context.Context, params interfaces.PublishProjectP newAlias := prevAlias if params.Alias != nil { - if prj2, err := i.projectRepo.FindByPublicName(ctx, *params.Alias); err != nil && !errors.Is(rerror.ErrNotFound, err) { + if prj2, err := i.projectRepo.FindByPublicName(ctx, *params.Alias); err != nil && !errors.Is(err, rerror.ErrNotFound) { return nil, err } else if prj2 != nil && prj.ID() != prj2.ID() { return nil, interfaces.ErrProjectAliasAlreadyUsed @@ -511,6 +511,10 @@ func (i *Project) ExportProject(ctx context.Context, projectID id.ProjectID, zip if err != nil { return nil, err } + if prj.IsDeleted() { + fmt.Printf("Error Deleted project: %v\n", prj.ID()) + return nil, errors.New("This project is deleted") + } // project image if prj.ImageURL() != nil { @@ -570,7 +574,7 @@ func (i *Project) UploadExportProjectZip(ctx context.Context, zipWriter *zip.Wri return nil } -func (i *Project) ImportProject(ctx context.Context, projectData map[string]interface{}) (*project.Project, usecasex.Tx, error) { +func (i *Project) ImportProject(ctx context.Context, teamID string, projectData map[string]interface{}) (*project.Project, usecasex.Tx, error) { tx, err := i.transaction.Begin(ctx) if err != nil { @@ -579,17 +583,13 @@ func (i *Project) ImportProject(ctx context.Context, projectData map[string]inte var p = jsonmodel.ToProjectFromJSON(projectData) - projectID, err := id.ProjectIDFrom(string(p.ID)) - if err != nil { - return nil, nil, err - } - workspaceID, err := accountdomain.WorkspaceIDFrom(string(p.TeamID)) + workspaceID, err := accountdomain.WorkspaceIDFrom(teamID) if err != nil { return nil, nil, err } prjBuilder := project.New(). - ID(projectID). + ID(project.NewID()). Workspace(workspaceID). IsArchived(p.IsArchived). IsBasicAuthActive(p.IsBasicAuthActive). diff --git a/server/internal/usecase/interactor/project_test.go b/server/internal/usecase/interactor/project_test.go index b0ba3be499..7579f07b05 100644 --- a/server/internal/usecase/interactor/project_test.go +++ b/server/internal/usecase/interactor/project_test.go @@ -3,6 +3,7 @@ package interactor import ( "context" "encoding/json" + "fmt" "net/url" "testing" @@ -143,6 +144,8 @@ func TestProject_Create(t *testing.T) { assert.Nil(t, got) } +// go test -v -run TestImportProject ./internal/usecase/interactor/... + func TestImportProject(t *testing.T) { ctx := context.Background() @@ -152,6 +155,7 @@ func TestImportProject(t *testing.T) { File: lo.Must(fs.NewFile(afero.NewMemMapFs(), "https://example.com")), }) + teamID := "01j7g9ddttkpnt3esk8h4w7xhv" var projectData map[string]interface{} err := json.Unmarshal([]byte(`{ "id": "01j7g9ddttkpnt3esk8h4w7xhv", @@ -192,7 +196,7 @@ func TestImportProject(t *testing.T) { assert.NoError(t, err) // invoke the target function - result, _, err := ifp.ImportProject(ctx, projectData) + result, _, err := ifp.ImportProject(ctx, teamID, projectData) assert.NoError(t, err) assert.NotNil(t, result) @@ -213,9 +217,8 @@ func TestImportProject(t *testing.T) { actual := string(actualByte) // expected - var expectedMap map[string]interface{} - err = json.Unmarshal([]byte(`{ - "id": "01j7g9ddttkpnt3esk8h4w7xhv", + exp := fmt.Sprintf(`{ + "id": "%s", "isArchived": false, "isBasicAuthActive": false, "isDeleted": false, @@ -241,14 +244,17 @@ func TestImportProject(t *testing.T) { "Fragment": "", "RawFragment": "" }, - "teamId": "01j7g99pb1q1vf684af39bajw5", + "teamId": "%s", "visualizer": "cesium", "publishmentStatus": "PRIVATE", "coreSupport": true, "enableGa": false, "trackingId": "", "starred": false - }`), &expectedMap) + }`, result.ID().String(), teamID) + + var expectedMap map[string]interface{} + err = json.Unmarshal([]byte(exp), &expectedMap) assert.NoError(t, err) expectedJSON, err := json.Marshal(expectedMap) diff --git a/server/internal/usecase/interactor/scene.go b/server/internal/usecase/interactor/scene.go index e6a4c420f3..26772957f7 100644 --- a/server/internal/usecase/interactor/scene.go +++ b/server/internal/usecase/interactor/scene.go @@ -209,7 +209,7 @@ func (i *Scene) AddWidget(ctx context.Context, sid id.SceneID, pid id.PluginID, return nil, nil, err } - extension, err := i.getWidgePlugin(ctx, pid, eid) + extension, err := i.getWidgePlugin(ctx, pid, eid, nil) if err != nil { return nil, nil, err } @@ -306,7 +306,7 @@ func (i *Scene) UpdateWidget(ctx context.Context, param interfaces.UpdateWidgetP } _, location := scene.Widgets().Alignment().Find(param.WidgetID) - extension, err := i.getWidgePlugin(ctx, widget.Plugin(), widget.Extension()) + extension, err := i.getWidgePlugin(ctx, widget.Plugin(), widget.Extension(), nil) if err != nil { return nil, nil, err } @@ -721,45 +721,32 @@ func (i *Scene) ExportScene(ctx context.Context, prj *project.Project, zipWriter return sce, res, nil } -func (i *Scene) ImportScene(ctx context.Context, prj *project.Project, plgs []*plugin.Plugin, sceneData map[string]interface{}) (*scene.Scene, error) { +func (i *Scene) ImportScene(ctx context.Context, sce *scene.Scene, prj *project.Project, plgs []*plugin.Plugin, sceneData map[string]interface{}) (*scene.Scene, error) { sceneJSON, err := builder.ParseSceneJSON(ctx, sceneData) if err != nil { return nil, err } - sceneID, err := id.SceneIDFrom(sceneJSON.ID) - if err != nil { - return nil, err - } - plugins := scene.NewPlugins([]*scene.Plugin{ - scene.NewPlugin(id.OfficialPluginID, nil), - }) - for _, plg := range plgs { - if plg.ID().String() != "reearth" { - plugins.Add(scene.NewPlugin(plg.ID(), nil)) - } - } + readableFilter := repo.SceneFilter{Readable: scene.IDList{sce.ID()}} + writableFilter := repo.SceneFilter{Writable: scene.IDList{sce.ID()}} widgets := []*scene.Widget{} + replaceWidgetIDs := make(map[string]idx.ID[id.Widget]) for _, widgetJSON := range sceneJSON.Widgets { - widgetID, err := id.WidgetIDFrom(widgetJSON.ID) - if err != nil { - return nil, err - } pluginID, err := id.PluginIDFrom(widgetJSON.PluginID) if err != nil { return nil, err } extensionID := id.PluginExtensionID(widgetJSON.ExtensionID) - extension, err := i.getWidgePlugin(ctx, pluginID, extensionID) + extension, err := i.getWidgePlugin(ctx, pluginID, extensionID, &readableFilter) if err != nil { return nil, err } - prop, err := property.New().NewID().Schema(extension.Schema()).Scene(sceneID).Build() + prop, err := property.New().NewID().Schema(extension.Schema()).Scene(sce.ID()).Build() if err != nil { return nil, err } - ps, err := i.propertySchemaRepo.FindByID(ctx, extension.Schema()) + ps, err := i.propertySchemaRepo.Filtered(readableFilter).FindByID(ctx, extension.Schema()) if err != nil { return nil, err } @@ -768,29 +755,30 @@ func (i *Scene) ImportScene(ctx context.Context, prj *project.Project, plgs []*p return nil, err } // Save property - if err = i.propertyRepo.Save(ctx, prop); err != nil { + if err = i.propertyRepo.Filtered(writableFilter).Save(ctx, prop); err != nil { return nil, err } - widget, err := scene.NewWidget(widgetID, pluginID, extensionID, prop.ID(), widgetJSON.Enabled, widgetJSON.Extended) + + newWidgetID := id.NewWidgetID() + replaceWidgetIDs[widgetJSON.ID] = newWidgetID + widget, err := scene.NewWidget(newWidgetID, pluginID, extensionID, prop.ID(), widgetJSON.Enabled, widgetJSON.Extended) if err != nil { return nil, err } widgets = append(widgets, widget) } + clusters := []*scene.Cluster{} for _, clusterJson := range sceneJSON.Clusters { - clusterID, err := id.ClusterIDFrom(clusterJson.ID) - if err != nil { - return nil, err - } - property, err := property.New().NewID().Schema(id.MustPropertySchemaID("reearth/cluster")).Scene(sceneID).Build() + property, err := property.New().NewID().Schema(id.MustPropertySchemaID("reearth/cluster")).Scene(sce.ID()).Build() if err != nil { return nil, err } - if err = i.propertyRepo.Save(ctx, property); err != nil { + // Save property + if err = i.propertyRepo.Filtered(writableFilter).Save(ctx, property); err != nil { return nil, err } - cluster, err := scene.NewCluster(clusterID, clusterJson.Name, property.ID()) + cluster, err := scene.NewCluster(id.NewClusterID(), clusterJson.Name, property.ID()) if err != nil { return nil, err } @@ -802,7 +790,7 @@ func (i *Scene) ImportScene(ctx context.Context, prj *project.Project, plgs []*p viz = visualizer.VisualizerCesiumBeta } schema := builtin.GetPropertySchemaByVisualizer(viz) - prop, err := property.New().NewID().Schema(schema.ID()).Scene(sceneID).Build() + prop, err := property.New().NewID().Schema(schema.ID()).Scene(sce.ID()).Build() if err != nil { return nil, err } @@ -810,22 +798,25 @@ func (i *Scene) ImportScene(ctx context.Context, prj *project.Project, plgs []*p if err != nil { return nil, err } - rootLayer, err := layer.NewGroup().NewID().Scene(sceneID).Root(true).Build() - if err != nil { - return nil, err - } - if err = i.propertyRepo.Filtered(repo.SceneFilter{Writable: scene.IDList{sceneID}}).Save(ctx, prop); err != nil { + // Save property + if err = i.propertyRepo.Filtered(writableFilter).Save(ctx, prop); err != nil { return nil, err } - if err = i.layerRepo.Filtered(repo.SceneFilter{Writable: scene.IDList{sceneID}}).Save(ctx, rootLayer); err != nil { - return nil, err + + plugins := sce.Plugins() + for _, plg := range plgs { + if plg.ID().String() != "reearth" { + plugins.Add(scene.NewPlugin(plg.ID(), nil)) + } } - scene, err := scene.New(). - ID(sceneID). + + alignSystem := builder.ParserWidgetAlignSystem(sceneJSON.WidgetAlignSystem, replaceWidgetIDs) + s2, err := scene.New(). + ID(sce.ID()). Project(prj.ID()). Workspace(prj.Workspace()). - RootLayer(rootLayer.ID()). - Widgets(scene.NewWidgets(widgets, builder.ParserWidgetAlignSystem(sceneJSON.WidgetAlignSystem))). + RootLayer(sce.RootLayer()). + Widgets(scene.NewWidgets(widgets, alignSystem)). UpdatedAt(time.Now()). Property(prop.ID()). Clusters(clusterList). @@ -834,18 +825,19 @@ func (i *Scene) ImportScene(ctx context.Context, prj *project.Project, plgs []*p if err != nil { return nil, err } - if err := i.sceneRepo.Save(ctx, scene); err != nil { + + // Save scene (update) + if err := i.sceneRepo.Save(ctx, s2); err != nil { return nil, err } if err := updateProjectUpdatedAt(ctx, prj, i.projectRepo); err != nil { return nil, err } - // operator.AddNewScene(prj.Workspace(), sceneID) - scene, err = i.sceneRepo.FindByID(ctx, sceneID) + s3, err := i.sceneRepo.FindByID(ctx, sce.ID()) if err != nil { return nil, err } - return scene, nil + return s3, nil } func injectExtensionsToScene(s *scene.Scene, ext []plugin.ID) { @@ -854,8 +846,14 @@ func injectExtensionsToScene(s *scene.Scene, ext []plugin.ID) { }) } -func (i *Scene) getWidgePlugin(ctx context.Context, pid id.PluginID, eid id.PluginExtensionID) (*plugin.Extension, error) { - pr, err := i.pluginRepo.FindByID(ctx, pid) +func (i *Scene) getWidgePlugin(ctx context.Context, pid id.PluginID, eid id.PluginExtensionID, readableFilter *repo.SceneFilter) (*plugin.Extension, error) { + var pr *plugin.Plugin + var err error + if readableFilter == nil { + pr, err = i.pluginRepo.FindByID(ctx, pid) + } else { + pr, err = i.pluginRepo.Filtered(*readableFilter).FindByID(ctx, pid) + } if err != nil { if errors.Is(err, rerror.ErrNotFound) { return nil, interfaces.ErrPluginNotFound diff --git a/server/internal/usecase/interactor/scene_plugin.go b/server/internal/usecase/interactor/scene_plugin.go index 196ee7c884..992428e53b 100644 --- a/server/internal/usecase/interactor/scene_plugin.go +++ b/server/internal/usecase/interactor/scene_plugin.go @@ -42,7 +42,7 @@ func (i *Scene) InstallPlugin(ctx context.Context, sid id.SceneID, pid id.Plugin plugin, err := i.pluginCommon().GetOrDownloadPlugin(ctx, pid) if err != nil { - if errors.Is(rerror.ErrNotFound, err) { + if errors.Is(err, rerror.ErrNotFound) { return nil, nil, interfaces.ErrPluginNotFound } return nil, nil, err @@ -107,7 +107,7 @@ func (i *Scene) UninstallPlugin(ctx context.Context, sid id.SceneID, pid id.Plug pl, err := i.pluginRepo.FindByID(ctx, pid) if err != nil { - if errors.Is(rerror.ErrNotFound, err) { + if errors.Is(err, rerror.ErrNotFound) { return nil, interfaces.ErrPluginNotFound } return nil, err diff --git a/server/internal/usecase/interactor/scene_test.go b/server/internal/usecase/interactor/scene_test.go index 31eb8ee993..78ec0215d6 100644 --- a/server/internal/usecase/interactor/scene_test.go +++ b/server/internal/usecase/interactor/scene_test.go @@ -10,15 +10,19 @@ import ( "github.com/reearth/reearth/server/internal/infrastructure/fs" "github.com/reearth/reearth/server/internal/infrastructure/memory" "github.com/reearth/reearth/server/internal/usecase/gateway" + "github.com/reearth/reearth/server/pkg/id" "github.com/reearth/reearth/server/pkg/plugin" "github.com/reearth/reearth/server/pkg/policy" "github.com/reearth/reearth/server/pkg/project" + "github.com/reearth/reearth/server/pkg/scene" + "github.com/reearth/reearthx/account/accountdomain" "github.com/reearth/reearthx/account/accountdomain/workspace" "github.com/samber/lo" "github.com/spf13/afero" "github.com/stretchr/testify/assert" ) +// go test -v -run TestImportScene ./internal/usecase/interactor/... func TestImportScene(t *testing.T) { ctx := context.Background() @@ -31,6 +35,8 @@ func TestImportScene(t *testing.T) { ws := workspace.New().NewID().Policy(policy.ID("policy").Ref()).MustBuild() prj, _ := project.New().NewID().Workspace(ws.ID()).Build() _ = db.Project.Save(ctx, prj) + sce, _ := scene.New().NewID().Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).RootLayer(id.NewLayerID()).Build() + _ = db.Scene.Save(ctx, sce) var sceneData map[string]interface{} err := json.Unmarshal([]byte(`{ @@ -141,7 +147,7 @@ func TestImportScene(t *testing.T) { assert.NoError(t, err) // invoke the target function - result, err := ifs.ImportScene(ctx, prj, []*plugin.Plugin{}, sceneData) + result, err := ifs.ImportScene(ctx, sce, prj, []*plugin.Plugin{}, sceneData) assert.NoError(t, err) assert.NotNil(t, result) @@ -158,30 +164,42 @@ func TestImportScene(t *testing.T) { delete(resultMap, "propertyId") delete(resultMap, "updatedAt") delete(resultMap, "createdAt") + if widgets, ok := resultMap["widgets"].([]interface{}); ok { for _, widget := range widgets { if widgetMap, ok := widget.(map[string]interface{}); ok { + delete(widgetMap, "id") // id is skip delete(widgetMap, "propertyId") } } } + if widgetAlignSystem, ok := resultMap["widgetAlignSystem"].(map[string]interface{}); ok { + if outer, ok := widgetAlignSystem["outer"].(map[string]interface{}); ok { + if left, ok := outer["left"].(map[string]interface{}); ok { + if top, ok := left["top"].(map[string]interface{}); ok { + delete(top, "widgetIds") + } + } + if right, ok := outer["right"].(map[string]interface{}); ok { + if top, ok := right["top"].(map[string]interface{}); ok { + delete(top, "widgetIds") + } + } + } + } + resultJSON, err = json.Marshal(resultMap) assert.NoError(t, err) actual := string(resultJSON) // expected - var expectedMap map[string]interface{} - err = json.Unmarshal([]byte(fmt.Sprintf(`{ + exp := fmt.Sprintf(`{ "clusters": [], "datasetSchemas": null, - "id": "01j7g9ddv4sbf8tgt5c6xxj5xc", + "id": "%s", "newLayers": null, - "plugins": [ - { - "pluginId": "reearth" - } - ], + "plugins": [], "projectId": "%s", "stories": null, "styles": null, @@ -279,11 +297,7 @@ func TestImportScene(t *testing.T) { "left": 0, "right": 0, "top": 0 - }, - "widgetIds": [ - "01j7g9h4f1k93vspn3gdtz67az", - "01j7g9jr89rjq1egrb1hhcd8jy" - ] + } } }, "right": { @@ -305,10 +319,7 @@ func TestImportScene(t *testing.T) { "left": 0, "right": 0, "top": 0 - }, - "widgetIds": [ - "01j7g9jckefd0zxyy34bbygmhy" - ] + } } } } @@ -318,18 +329,18 @@ func TestImportScene(t *testing.T) { "enabled": false, "extended": false, "extensionId": "button", - "id": "01j7g9h4f1k93vspn3gdtz67az", "pluginId": "reearth" }, { "enabled": false, "extended": false, "extensionId": "navigator", - "id": "01j7g9jckefd0zxyy34bbygmhy", "pluginId": "reearth" } ] -}`, prj.ID(), prj.Workspace())), &expectedMap) + }`, result.ID(), prj.ID(), prj.Workspace()) + var expectedMap map[string]interface{} + err = json.Unmarshal([]byte(exp), &expectedMap) assert.NoError(t, err) expectedJSON, err := json.Marshal(expectedMap) assert.NoError(t, err) diff --git a/server/internal/usecase/interactor/storytelling.go b/server/internal/usecase/interactor/storytelling.go index 6f8e16b7f3..508f21b91e 100644 --- a/server/internal/usecase/interactor/storytelling.go +++ b/server/internal/usecase/interactor/storytelling.go @@ -14,10 +14,12 @@ import ( "github.com/reearth/reearth/server/pkg/id" "github.com/reearth/reearth/server/pkg/plugin" "github.com/reearth/reearth/server/pkg/property" + "github.com/reearth/reearth/server/pkg/scene" scene2 "github.com/reearth/reearth/server/pkg/scene" "github.com/reearth/reearth/server/pkg/scene/builder" "github.com/reearth/reearth/server/pkg/storytelling" "github.com/reearth/reearthx/account/accountusecase/accountrepo" + "github.com/reearth/reearthx/idx" "github.com/reearth/reearthx/rerror" "github.com/reearth/reearthx/usecasex" "github.com/samber/lo" @@ -334,7 +336,7 @@ func (i *Storytelling) Publish(ctx context.Context, inp interfaces.PublishStoryI newAlias := prevAlias if inp.Alias != nil && *inp.Alias != prevAlias { - if publishedStory, err := i.storytellingRepo.FindByPublicName(ctx, *inp.Alias); err != nil && !errors.Is(rerror.ErrNotFound, err) { + if publishedStory, err := i.storytellingRepo.FindByPublicName(ctx, *inp.Alias); err != nil && !errors.Is(err, rerror.ErrNotFound) { return nil, err } else if publishedStory != nil && story.Id() != publishedStory.Id() { return nil, interfaces.ErrProjectAliasAlreadyUsed @@ -997,26 +999,22 @@ func (i *Storytelling) MoveBlock(ctx context.Context, inp interfaces.MoveBlockPa return story, page, &inp.BlockID, inp.Index, nil } -func (i *Storytelling) ImportStory(ctx context.Context, sceneData map[string]interface{}) (*storytelling.Story, error) { +func (i *Storytelling) ImportStory(ctx context.Context, sceneID idx.ID[id.Scene], sceneData map[string]interface{}, replaceNLSLayerIDs map[string]idx.ID[id.NLSLayer]) (*storytelling.Story, error) { sceneJSON, err := builder.ParseSceneJSON(ctx, sceneData) if err != nil { return nil, err } - sceneID, err := id.SceneIDFrom(sceneJSON.ID) - if err != nil { - return nil, err - } storyJSON := sceneJSON.Story + readableFilter := repo.SceneFilter{Readable: scene.IDList{sceneID}} + writableFilter := repo.SceneFilter{Writable: scene.IDList{sceneID}} + pages := []*storytelling.Page{} for _, pageJSON := range storyJSON.Pages { blocks := []*storytelling.Block{} for _, blockJSON := range pageJSON.Blocks { - blockID, err := id.BlockIDFrom(blockJSON.ID) - if err != nil { - return nil, err - } + pluginID, err := id.PluginIDFrom(blockJSON.PluginId) if err != nil { return nil, err @@ -1042,11 +1040,11 @@ func (i *Storytelling) ImportStory(ctx context.Context, sceneData map[string]int return nil, err } // Save property - if err = i.propertyRepo.Save(ctx, prop); err != nil { + if err = i.propertyRepo.Filtered(writableFilter).Save(ctx, prop); err != nil { return nil, err } block, err := storytelling.NewBlock(). - ID(blockID). + ID(id.NewBlockID()). Property(prop.ID()). Plugin(pluginID). Extension(extensionID). @@ -1057,10 +1055,6 @@ func (i *Storytelling) ImportStory(ctx context.Context, sceneData map[string]int blocks = append(blocks, block) } - pageID, err := id.PageIDFrom(pageJSON.ID) - if err != nil { - return nil, err - } schema := builtin.GetPropertySchema(builtin.PropertySchemaIDStoryPage) prop, err := property.New().NewID().Schema(schema.ID()).Scene(sceneID).Build() if err != nil { @@ -1075,15 +1069,20 @@ func (i *Storytelling) ImportStory(ctx context.Context, sceneData map[string]int return nil, err } // Save property - if err = i.propertyRepo.Save(ctx, prop); err != nil { + if err = i.propertyRepo.Filtered(writableFilter).Save(ctx, prop); err != nil { return nil, err } + var nlslayerIDs []idx.ID[id.NLSLayer] + for _, oldId := range pageJSON.Layers { + nlslayerIDs = append(nlslayerIDs, replaceNLSLayerIDs[oldId]) + } page, err := storytelling.NewPage(). - ID(pageID). + ID(id.NewPageID()). Property(prop.ID()). Title(pageJSON.Title). Swipeable(pageJSON.Swipeable). Blocks(blocks). + Layers(nlslayerIDs). Build() if err != nil { return nil, err @@ -1091,8 +1090,6 @@ func (i *Storytelling) ImportStory(ctx context.Context, sceneData map[string]int pages = append(pages, page) } - storyID, _ := id.StoryIDFrom(storyJSON.ID) - schema := builtin.GetPropertySchema(builtin.PropertySchemaIDStory) prop, err := property.New().NewID().Schema(schema.ID()).Scene(sceneID).Build() if err != nil { @@ -1107,11 +1104,12 @@ func (i *Storytelling) ImportStory(ctx context.Context, sceneData map[string]int return nil, err } // Save property - if err = i.propertyRepo.Save(ctx, prop); err != nil { + if err = i.propertyRepo.Filtered(writableFilter).Save(ctx, prop); err != nil { return nil, err } story, err := storytelling.NewStory(). - ID(storyID). + ID(id.NewStoryID()). + Title(storyJSON.Title). Property(prop.ID()). Scene(sceneID). PanelPosition(storytelling.Position(storyJSON.PanelPosition)). @@ -1121,10 +1119,10 @@ func (i *Storytelling) ImportStory(ctx context.Context, sceneData map[string]int if err != nil { return nil, err } - if err := i.storytellingRepo.Save(ctx, *story); err != nil { + if err := i.storytellingRepo.Filtered(writableFilter).Save(ctx, *story); err != nil { return nil, err } - story, err = i.storytellingRepo.FindByID(ctx, story.Id()) + story, err = i.storytellingRepo.Filtered(readableFilter).FindByID(ctx, story.Id()) if err != nil { return nil, err } diff --git a/server/internal/usecase/interactor/storytelling_test.go b/server/internal/usecase/interactor/storytelling_test.go index e498f0a1ed..e26860869e 100644 --- a/server/internal/usecase/interactor/storytelling_test.go +++ b/server/internal/usecase/interactor/storytelling_test.go @@ -14,11 +14,14 @@ import ( "github.com/reearth/reearth/server/pkg/project" "github.com/reearth/reearth/server/pkg/scene" "github.com/reearth/reearthx/account/accountdomain" + "github.com/reearth/reearthx/idx" "github.com/samber/lo" "github.com/spf13/afero" "github.com/stretchr/testify/assert" ) +// go test -v -run TestImportStory ./internal/usecase/interactor/... + func TestImportStory(t *testing.T) { ctx := context.Background() @@ -111,7 +114,7 @@ func TestImportStory(t *testing.T) { assert.NoError(t, err) // invoke the target function - result, err := ifs.ImportStory(ctx, sceneData) + result, err := ifs.ImportStory(ctx, scene.ID(), sceneData, map[string]idx.ID[id.NLSLayer]{}) assert.NoError(t, err) assert.NotNil(t, result) @@ -124,12 +127,14 @@ func TestImportStory(t *testing.T) { assert.NoError(t, err) // Exclude items that are updated upon creation. + delete(resultMap, "id") delete(resultMap, "propertyId") delete(resultMap, "updatedAt") delete(resultMap, "createdAt") if pages, ok := resultMap["pages"].([]interface{}); ok { for _, page := range pages { if pageMap, ok := page.(map[string]interface{}); ok { + delete(pageMap, "id") // id is skip delete(pageMap, "propertyId") delete(pageMap, "updatedAt") delete(pageMap, "createdAt") @@ -137,6 +142,7 @@ func TestImportStory(t *testing.T) { if blocks, ok := pageMap["blocks"].([]interface{}); ok { for _, block := range blocks { if blockMap, ok := block.(map[string]interface{}); ok { + delete(blockMap, "id") // id is skip delete(blockMap, "propertyId") } } @@ -151,26 +157,21 @@ func TestImportStory(t *testing.T) { // expected var expectedMap map[string]interface{} err = json.Unmarshal([]byte(fmt.Sprintf(`{ - "id": "01j7g9ddvkarms2gmc59ysw66r", "title": "", "alias": "", "pages": [ { - "id": "01j7g9ddwk4a12x1t8wm865s6h", "title": "Untitled", "blocks": [ { - "id": "01j7g9mdnjk1jafw592btqx6t7", "pluginId": "reearth", "extensionId": "textStoryBlock" }, { - "id": "01j7g9n3x4yqae71crdjcpeyc0", "pluginId": "reearth", "extensionId": "mdTextStoryBlock" }, { - "id": "01j7g9nnnap0cwa1farwd841xc", "pluginId": "reearth", "extensionId": "imageStoryBlock" } diff --git a/server/internal/usecase/interactor/style.go b/server/internal/usecase/interactor/style.go index 433e90779d..4f1cef14df 100644 --- a/server/internal/usecase/interactor/style.go +++ b/server/internal/usecase/interactor/style.go @@ -201,23 +201,19 @@ func (i *Style) DuplicateStyle(ctx context.Context, styleID id.StyleID, operator return duplicatedStyle, nil } -func (i *Style) ImportStyles(ctx context.Context, sceneData map[string]interface{}) (scene.StyleList, error) { +func (i *Style) ImportStyles(ctx context.Context, sceneID idx.ID[id.Scene], sceneData map[string]interface{}) (scene.StyleList, error) { sceneJSON, err := builder.ParseSceneJSON(ctx, sceneData) if err != nil { return nil, err } - sceneID, err := id.SceneIDFrom(sceneJSON.ID) - if err != nil { - return nil, err - } + + readableFilter := repo.SceneFilter{Readable: scene.IDList{sceneID}} + writableFilter := repo.SceneFilter{Writable: scene.IDList{sceneID}} styleIDs := idx.List[id.Style]{} styles := []*scene.Style{} for _, layerStyleJson := range sceneJSON.LayerStyles { - styleID, err := id.StyleIDFrom(layerStyleJson.ID) - if err != nil { - return nil, err - } + styleID := id.NewStyleID() styleIDs = append(styleIDs, styleID) style, err := scene.NewStyle(). ID(styleID). @@ -231,15 +227,15 @@ func (i *Style) ImportStyles(ctx context.Context, sceneData map[string]interface styles = append(styles, style) } - // save + // Save style styleList := scene.StyleList(styles) - if err := i.styleRepo.SaveAll(ctx, styleList); err != nil { + if err := i.styleRepo.Filtered(writableFilter).SaveAll(ctx, styleList); err != nil { return nil, err } if len(styleIDs) == 0 { return nil, nil } - styles2, err := i.styleRepo.FindByIDs(ctx, styleIDs) + styles2, err := i.styleRepo.Filtered(readableFilter).FindByIDs(ctx, styleIDs) if err != nil { return nil, err } diff --git a/server/internal/usecase/interactor/style_test.go b/server/internal/usecase/interactor/style_test.go index 4b1487540e..dc68d575fd 100644 --- a/server/internal/usecase/interactor/style_test.go +++ b/server/internal/usecase/interactor/style_test.go @@ -17,6 +17,8 @@ import ( "github.com/stretchr/testify/assert" ) +// go test -v -run TestImportStyles ./internal/usecase/interactor/... + func TestImportStyles(t *testing.T) { ctx := context.Background() @@ -53,7 +55,7 @@ func TestImportStyles(t *testing.T) { assert.NoError(t, err) // invoke the target function - result, err := ifs.ImportStyles(ctx, sceneData) + result, err := ifs.ImportStyles(ctx, scene.ID(), sceneData) assert.NoError(t, err) assert.NotNil(t, result) @@ -64,25 +66,26 @@ func TestImportStyles(t *testing.T) { actual := string(resultJSON) // expected + exp := fmt.Sprintf(`[ + { + "id": "%s", + "sceneId": "%s", + "name": "スタイル_0", + "value": { + "color": "red" + } + }, + { + "id": "%s", + "sceneId": "%s", + "name": "スタイル_1", + "value": { + "font": "bold" + } + } + ]`, temp[0].ID, scene.ID(), temp[1].ID, scene.ID()) var expectedMap []map[string]interface{} - err = json.Unmarshal([]byte(fmt.Sprintf(`[ - { - "id": "01j7hzqgycv76hxsygmcrb47m6", - "sceneId": "%s", - "name": "スタイル_0", - "value": { - "color": "red" - } - }, - { - "id": "01j7hzrgc3ag8m1ftzye05csgx", - "sceneId": "%s", - "name": "スタイル_1", - "value": { - "font": "bold" - } - } - ]`, scene.ID(), scene.ID())), &expectedMap) + err = json.Unmarshal([]byte(exp), &expectedMap) assert.NoError(t, err) expectedJSON, err := json.Marshal(expectedMap) assert.NoError(t, err) diff --git a/server/internal/usecase/interactor/tag.go b/server/internal/usecase/interactor/tag.go index 9fb1872176..ea83be5f69 100644 --- a/server/internal/usecase/interactor/tag.go +++ b/server/internal/usecase/interactor/tag.go @@ -295,7 +295,7 @@ func (i *Tag) Remove(ctx context.Context, tagID id.TagID, operator *usecase.Oper if item := tag.ToTagItem(t); item != nil { g, err := i.tagRepo.FindGroupByItem(ctx, item.ID()) - if err != nil && !errors.Is(rerror.ErrNotFound, err) { + if err != nil && !errors.Is(err, rerror.ErrNotFound) { return nil, nil, err } if g != nil { @@ -307,7 +307,7 @@ func (i *Tag) Remove(ctx context.Context, tagID id.TagID, operator *usecase.Oper } ls, err := i.layerRepo.FindByTag(ctx, tagID) - if err != nil && !errors.Is(rerror.ErrNotFound, err) { + if err != nil && !errors.Is(err, rerror.ErrNotFound) { return nil, nil, err } diff --git a/server/internal/usecase/interfaces/nlslayer.go b/server/internal/usecase/interfaces/nlslayer.go index 2fda2a7bcb..c6f52fb89b 100644 --- a/server/internal/usecase/interfaces/nlslayer.go +++ b/server/internal/usecase/interfaces/nlslayer.go @@ -6,6 +6,7 @@ import ( "github.com/reearth/reearth/server/internal/usecase" "github.com/reearth/reearth/server/pkg/id" "github.com/reearth/reearth/server/pkg/nlslayer" + "github.com/reearth/reearthx/idx" ) type AddNLSLayerSimpleInput struct { @@ -86,5 +87,5 @@ type NLSLayer interface { AddGeoJSONFeature(context.Context, AddNLSLayerGeoJSONFeatureParams, *usecase.Operator) (nlslayer.Feature, error) UpdateGeoJSONFeature(context.Context, UpdateNLSLayerGeoJSONFeatureParams, *usecase.Operator) (nlslayer.Feature, error) DeleteGeoJSONFeature(context.Context, DeleteNLSLayerGeoJSONFeatureParams, *usecase.Operator) (id.FeatureID, error) - ImportNLSLayers(context.Context, map[string]interface{}) (nlslayer.NLSLayerList, error) + ImportNLSLayers(context.Context, idx.ID[id.Scene], map[string]interface{}) (nlslayer.NLSLayerList, map[string]idx.ID[id.NLSLayer], error) } diff --git a/server/internal/usecase/interfaces/plugin.go b/server/internal/usecase/interfaces/plugin.go index 33f67e9742..e866dcbe76 100644 --- a/server/internal/usecase/interfaces/plugin.go +++ b/server/internal/usecase/interfaces/plugin.go @@ -10,6 +10,7 @@ import ( "github.com/reearth/reearth/server/internal/usecase" "github.com/reearth/reearth/server/pkg/id" "github.com/reearth/reearth/server/pkg/plugin" + "github.com/reearth/reearth/server/pkg/property" "github.com/reearth/reearth/server/pkg/scene" ) @@ -22,7 +23,7 @@ type Plugin interface { Fetch(context.Context, []id.PluginID, *usecase.Operator) ([]*plugin.Plugin, error) Upload(context.Context, io.Reader, id.SceneID, *usecase.Operator) (*plugin.Plugin, *scene.Scene, error) UploadFromRemote(context.Context, *url.URL, id.SceneID, *usecase.Operator) (*plugin.Plugin, *scene.Scene, error) - ExportPlugins(context.Context, *scene.Scene, *zip.Writer) ([]*plugin.Plugin, error) - ImportPlugins(context.Context, []interface{}) ([]*plugin.Plugin, error) + ExportPlugins(context.Context, *scene.Scene, *zip.Writer) ([]*plugin.Plugin, []*property.Schema, error) + ImportPlugins(context.Context, *scene.Scene, []interface{}, []interface{}) ([]*plugin.Plugin, property.SchemaList, error) ImporPluginFile(context.Context, id.PluginID, string, *zip.File) error } diff --git a/server/internal/usecase/interfaces/project.go b/server/internal/usecase/interfaces/project.go index 6fb282c7e1..f304b48b8c 100644 --- a/server/internal/usecase/interfaces/project.go +++ b/server/internal/usecase/interfaces/project.go @@ -71,6 +71,6 @@ type Project interface { CheckAlias(context.Context, string) (bool, error) Delete(context.Context, id.ProjectID, *usecase.Operator) error ExportProject(context.Context, id.ProjectID, *zip.Writer, *usecase.Operator) (*project.Project, error) - ImportProject(context.Context, map[string]interface{}) (*project.Project, usecasex.Tx, error) + ImportProject(context.Context, string, map[string]interface{}) (*project.Project, usecasex.Tx, error) UploadExportProjectZip(context.Context, *zip.Writer, afero.File, map[string]interface{}, *project.Project) error } diff --git a/server/internal/usecase/interfaces/scene.go b/server/internal/usecase/interfaces/scene.go index 79da387f33..48b6b419ab 100644 --- a/server/internal/usecase/interfaces/scene.go +++ b/server/internal/usecase/interfaces/scene.go @@ -34,7 +34,7 @@ type Scene interface { UpdateCluster(context.Context, UpdateClusterParam, *usecase.Operator) (*scene.Scene, *scene.Cluster, error) RemoveCluster(context.Context, id.SceneID, id.ClusterID, *usecase.Operator) (*scene.Scene, error) ExportScene(context.Context, *project.Project, *zip.Writer) (*scene.Scene, map[string]interface{}, error) - ImportScene(context.Context, *project.Project, []*plugin.Plugin, map[string]interface{}) (*scene.Scene, error) + ImportScene(context.Context, *scene.Scene, *project.Project, []*plugin.Plugin, map[string]interface{}) (*scene.Scene, error) } type UpdateWidgetParam struct { diff --git a/server/internal/usecase/interfaces/story.go b/server/internal/usecase/interfaces/story.go index 55db62fb0e..a891cb39ee 100644 --- a/server/internal/usecase/interfaces/story.go +++ b/server/internal/usecase/interfaces/story.go @@ -8,6 +8,7 @@ import ( "github.com/reearth/reearth/server/pkg/id" "github.com/reearth/reearth/server/pkg/storytelling" "github.com/reearth/reearthx/i18n" + "github.com/reearth/reearthx/idx" "github.com/reearth/reearthx/rerror" ) @@ -150,5 +151,5 @@ type Storytelling interface { RemoveBlock(context.Context, RemoveBlockParam, *usecase.Operator) (*storytelling.Story, *storytelling.Page, *id.BlockID, error) MoveBlock(context.Context, MoveBlockParam, *usecase.Operator) (*storytelling.Story, *storytelling.Page, *id.BlockID, int, error) - ImportStory(context.Context, map[string]interface{}) (*storytelling.Story, error) + ImportStory(context.Context, idx.ID[id.Scene], map[string]interface{}, map[string]idx.ID[id.NLSLayer]) (*storytelling.Story, error) } diff --git a/server/internal/usecase/interfaces/style.go b/server/internal/usecase/interfaces/style.go index 75e945dd4f..4563f492cb 100644 --- a/server/internal/usecase/interfaces/style.go +++ b/server/internal/usecase/interfaces/style.go @@ -6,6 +6,7 @@ import ( "github.com/reearth/reearth/server/internal/usecase" "github.com/reearth/reearth/server/pkg/id" "github.com/reearth/reearth/server/pkg/scene" + "github.com/reearth/reearthx/idx" ) type AddStyleInput struct { @@ -27,5 +28,5 @@ type Style interface { UpdateStyle(context.Context, UpdateStyleInput, *usecase.Operator) (*scene.Style, error) RemoveStyle(context.Context, id.StyleID, *usecase.Operator) (id.StyleID, error) DuplicateStyle(context.Context, id.StyleID, *usecase.Operator) (*scene.Style, error) - ImportStyles(context.Context, map[string]interface{}) (scene.StyleList, error) + ImportStyles(context.Context, idx.ID[id.Scene], map[string]interface{}) (scene.StyleList, error) } diff --git a/server/pkg/property/schema_field_builder.go b/server/pkg/property/schema_field_builder.go index d3d29ca737..f1c8c6e186 100644 --- a/server/pkg/property/schema_field_builder.go +++ b/server/pkg/property/schema_field_builder.go @@ -53,6 +53,11 @@ func (b *SchemaFieldBuilder) Name(name i18n.String) *SchemaFieldBuilder { return b } +func (b *SchemaFieldBuilder) Title(title i18n.String) *SchemaFieldBuilder { + b.p.title = title.Clone() + return b +} + func (b *SchemaFieldBuilder) Description(description i18n.String) *SchemaFieldBuilder { b.p.description = description.Clone() return b diff --git a/server/pkg/property/schema_field_choice_builder.go b/server/pkg/property/schema_field_choice_builder.go new file mode 100644 index 0000000000..f24d20ef41 --- /dev/null +++ b/server/pkg/property/schema_field_choice_builder.go @@ -0,0 +1,43 @@ +package property + +import ( + "github.com/reearth/reearth/server/pkg/i18n" +) + +type SchemaFieldChoiceBuilder struct { + p *SchemaFieldChoice +} + +func NewSchemaFieldChoice() *SchemaFieldChoiceBuilder { + return &SchemaFieldChoiceBuilder{p: &SchemaFieldChoice{}} +} + +func (b *SchemaFieldChoiceBuilder) Build() (*SchemaFieldChoice, error) { + if b.p.Key == "" { + return nil, ErrInvalidID + } + return b.p, nil +} + +func (b *SchemaFieldChoiceBuilder) MustBuild() *SchemaFieldChoice { + p, err := b.Build() + if err != nil { + panic(err) + } + return p +} + +func (b *SchemaFieldChoiceBuilder) Key(key string) *SchemaFieldChoiceBuilder { + b.p.Key = key + return b +} + +func (b *SchemaFieldChoiceBuilder) Title(title i18n.String) *SchemaFieldChoiceBuilder { + b.p.Title = title.Clone() + return b +} + +func (b *SchemaFieldChoiceBuilder) Icon(icon string) *SchemaFieldChoiceBuilder { + b.p.Icon = icon + return b +} diff --git a/server/pkg/property/schema_field_choice_builder_test.go b/server/pkg/property/schema_field_choice_builder_test.go new file mode 100644 index 0000000000..eb14aa59a4 --- /dev/null +++ b/server/pkg/property/schema_field_choice_builder_test.go @@ -0,0 +1,68 @@ +package property + +import ( + "testing" + + "github.com/reearth/reearth/server/pkg/i18n" + "github.com/stretchr/testify/assert" +) + +func TestSchemaFieldChoiceBuilder_Build(t *testing.T) { + t.Run("valid build", func(t *testing.T) { + title := i18n.StringFrom("Title") + builder := NewSchemaFieldChoice(). + Key("key1"). + Title(title). + Icon("icon1") + + result, err := builder.Build() + + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, "key1", result.Key) + assert.Equal(t, "Title", result.Title.String()) + assert.Equal(t, "icon1", result.Icon) + }) + + t.Run("missing key should return error", func(t *testing.T) { + title := i18n.StringFrom("Title") + builder := NewSchemaFieldChoice(). + Title(title). + Icon("icon1") + + result, err := builder.Build() + + assert.Error(t, err) + assert.Nil(t, result) + }) +} + +func TestSchemaFieldChoiceBuilder_MustBuild(t *testing.T) { + t.Run("valid must build", func(t *testing.T) { + title := i18n.StringFrom("Title") + builder := NewSchemaFieldChoice(). + Key("key1"). + Title(title). + Icon("icon1") + + result := builder.MustBuild() + + assert.NotNil(t, result) + assert.Equal(t, "key1", result.Key) + assert.Equal(t, "Title", result.Title.String()) + assert.Equal(t, "icon1", result.Icon) + }) + + t.Run("must build should panic on missing key", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expected panic, but did not occur") + } + }() + + NewSchemaFieldChoice(). + Title(i18n.StringFrom("Title")). + Icon("icon1"). + MustBuild() + }) +} diff --git a/server/pkg/property/schema_linkable_field.go b/server/pkg/property/schema_linkable_field.go new file mode 100644 index 0000000000..713b576232 --- /dev/null +++ b/server/pkg/property/schema_linkable_field.go @@ -0,0 +1,30 @@ +package property + +type LinkableFieldsBuilder struct { + p *LinkableFields +} + +func NewLinkableFields() *LinkableFieldsBuilder { + return &LinkableFieldsBuilder{p: &LinkableFields{}} +} + +func (b *LinkableFieldsBuilder) Build() (*LinkableFields, error) { + return b.p, nil +} + +func (b *LinkableFieldsBuilder) MustBuild() *LinkableFields { + p, err := b.Build() + if err != nil { + panic(err) + } + return p +} + +func (b *LinkableFieldsBuilder) LatLng(LatLng *SchemaFieldPointer) *LinkableFieldsBuilder { + b.p.LatLng = LatLng + return b +} +func (b *LinkableFieldsBuilder) URL(URL *SchemaFieldPointer) *LinkableFieldsBuilder { + b.p.URL = URL + return b +} diff --git a/server/pkg/scene/builder/builder_test.go b/server/pkg/scene/builder/builder_test.go index e6bbf8d801..a5424e0a9c 100644 --- a/server/pkg/scene/builder/builder_test.go +++ b/server/pkg/scene/builder/builder_test.go @@ -755,6 +755,7 @@ func TestSceneBuilder(t *testing.T) { "a": "hogehoge", }, }, + Enabled: true, Extended: true, }, }, diff --git a/server/pkg/scene/builder/decoder.go b/server/pkg/scene/builder/decoder.go index 72cfe4d2ac..fc38201ad1 100644 --- a/server/pkg/scene/builder/decoder.go +++ b/server/pkg/scene/builder/decoder.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" - "github.com/reearth/reearth/server/internal/adapter/gql/gqlmodel" "github.com/reearth/reearth/server/pkg/id" "github.com/reearth/reearth/server/pkg/property" "github.com/reearth/reearth/server/pkg/scene" @@ -24,56 +23,54 @@ func ParseSceneJSON(ctx context.Context, sceneJSONData map[string]interface{}) ( return &result, nil } -func ParserWidgetAlignSystem(widgetAlignSystemJSON *widgetAlignSystemJSON) *scene.WidgetAlignSystem { +func ParserWidgetAlignSystem(widgetAlignSystemJSON *widgetAlignSystemJSON, replaceWidgetIDs map[string]idx.ID[id.Widget]) *scene.WidgetAlignSystem { if widgetAlignSystemJSON == nil { return nil } was := scene.NewWidgetAlignSystem() if widgetAlignSystemJSON.Inner != nil { - parseWidgetZone(was.Zone(scene.WidgetZoneInner), widgetAlignSystemJSON.Inner) + parseWidgetZone(was.Zone(scene.WidgetZoneInner), widgetAlignSystemJSON.Inner, replaceWidgetIDs) } if widgetAlignSystemJSON.Outer != nil { - parseWidgetZone(was.Zone(scene.WidgetZoneOuter), widgetAlignSystemJSON.Outer) + parseWidgetZone(was.Zone(scene.WidgetZoneOuter), widgetAlignSystemJSON.Outer, replaceWidgetIDs) } return was } -func parseWidgetZone(zone *scene.WidgetZone, widgetZoneJSON *widgetZoneJSON) { +func parseWidgetZone(zone *scene.WidgetZone, widgetZoneJSON *widgetZoneJSON, replaceWidgetIDs map[string]idx.ID[id.Widget]) { if zone == nil || widgetZoneJSON == nil { return } if widgetZoneJSON.Left != nil { - setWidgetSection(zone.Section(scene.WidgetSectionLeft), widgetZoneJSON.Left) + setWidgetSection(zone.Section(scene.WidgetSectionLeft), widgetZoneJSON.Left, replaceWidgetIDs) } if widgetZoneJSON.Center != nil { - setWidgetSection(zone.Section(scene.WidgetSectionCenter), widgetZoneJSON.Center) + setWidgetSection(zone.Section(scene.WidgetSectionCenter), widgetZoneJSON.Center, replaceWidgetIDs) } if widgetZoneJSON.Right != nil { - setWidgetSection(zone.Section(scene.WidgetSectionRight), widgetZoneJSON.Right) + setWidgetSection(zone.Section(scene.WidgetSectionRight), widgetZoneJSON.Right, replaceWidgetIDs) } } -func setWidgetSection(section *scene.WidgetSection, widgetSectionJSON *widgetSectionJSON) { +func setWidgetSection(section *scene.WidgetSection, widgetSectionJSON *widgetSectionJSON, replaceWidgetIDs map[string]idx.ID[id.Widget]) { if section == nil || widgetSectionJSON == nil { return } - section.SetArea(scene.WidgetAreaTop, parseWidgetArea(widgetSectionJSON.Top)) - section.SetArea(scene.WidgetAreaMiddle, parseWidgetArea(widgetSectionJSON.Middle)) - section.SetArea(scene.WidgetAreaBottom, parseWidgetArea(widgetSectionJSON.Bottom)) + section.SetArea(scene.WidgetAreaTop, parseWidgetArea(widgetSectionJSON.Top, replaceWidgetIDs)) + section.SetArea(scene.WidgetAreaMiddle, parseWidgetArea(widgetSectionJSON.Middle, replaceWidgetIDs)) + section.SetArea(scene.WidgetAreaBottom, parseWidgetArea(widgetSectionJSON.Bottom, replaceWidgetIDs)) } -func parseWidgetArea(widgetAreaJSON *widgetAreaJSON) *scene.WidgetArea { +func parseWidgetArea(widgetAreaJSON *widgetAreaJSON, replaceWidgetIDs map[string]idx.ID[id.Widget]) *scene.WidgetArea { if widgetAreaJSON == nil { return nil } + var widgetIDs []idx.ID[id.Widget] - for _, widgetID := range widgetAreaJSON.WidgetIDs { - id, err := gqlmodel.ToID[id.Widget](gqlmodel.ID(widgetID)) - if err != nil { - continue - } - widgetIDs = append(widgetIDs, id) + for _, oldId := range widgetAreaJSON.WidgetIDs { + widgetIDs = append(widgetIDs, replaceWidgetIDs[oldId]) } + return scene.NewWidgetArea( widgetIDs, parseWidgetAlign(widgetAreaJSON.Align), diff --git a/server/pkg/scene/builder/scene.go b/server/pkg/scene/builder/scene.go index cf3fa9c2c9..8cd2ca0e0d 100644 --- a/server/pkg/scene/builder/scene.go +++ b/server/pkg/scene/builder/scene.go @@ -78,6 +78,7 @@ func (b *Builder) widgets(ctx context.Context, p []*property.Property) []*widget PluginID: w.Plugin().String(), ExtensionID: string(w.Extension()), Property: b.property(ctx, findProperty(p, w.Property())), + Enabled: w.Enabled(), Extended: w.Extended(), }) } diff --git a/server/pkg/scene/builder/story.go b/server/pkg/scene/builder/story.go index 78d2ceed20..3cd332cf3a 100644 --- a/server/pkg/scene/builder/story.go +++ b/server/pkg/scene/builder/story.go @@ -10,6 +10,7 @@ import ( type storyJSON struct { ID string `json:"id"` + Title string `json:"title"` Property propertyJSON `json:"property"` Pages []pageJSON `json:"pages"` PanelPosition string `json:"position"`