diff --git a/server/e2e/gql_storytelling_test.go b/server/e2e/gql_storytelling_test.go index a6fab1d602..c6ffa2d4eb 100644 --- a/server/e2e/gql_storytelling_test.go +++ b/server/e2e/gql_storytelling_test.go @@ -116,6 +116,7 @@ func fetchSceneForStories(e *httpexpect.Expect, sID string) (GraphQLRequest, *ht } } } + bgColor } __typename } @@ -180,11 +181,12 @@ func createStory(e *httpexpect.Expect, sID, name string, index int) (GraphQLRequ func updateStory(e *httpexpect.Expect, storyID, sID string) (GraphQLRequest, *httpexpect.Value) { requestBody := GraphQLRequest{ OperationName: "UpdateStory", - Query: `mutation UpdateStory($sceneId: ID!, $storyId: ID!, $title: String!, $index: Int) { - updateStory( input: {sceneId: $sceneId, storyId: $storyId, title: $title, index: $index} ) { + Query: `mutation UpdateStory($sceneId: ID!, $storyId: ID!, $title: String!, $index: Int, $bgColor: String) { + updateStory( input: {sceneId: $sceneId, storyId: $storyId, title: $title, index: $index, bgColor: $bgColor} ) { story { id title + bgColor __typename } __typename @@ -195,6 +197,7 @@ func updateStory(e *httpexpect.Expect, storyID, sID string) (GraphQLRequest, *ht "sceneId": sID, "title": "test2", "index": 0, + "bgColor": "newBG", }, } @@ -780,6 +783,14 @@ func TestStoryCRUD(t *testing.T) { // update story _, _ = updateStory(e, storyID, sID) + // fetch scene and check story + _, res = fetchSceneForStories(e, sID) + storiesRes = res.Object(). + Value("data").Object(). + Value("node").Object(). + Value("stories").Array() + storiesRes.First().Object().ValueEqual("bgColor", "newBG") + _, _ = deleteStory(e, storyID, sID) } @@ -1037,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":{},"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":\[]}]},"nlsLayers":null,"layerStyles":null,"coreSupport":true}`, 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","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}`, 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/storytelling.graphql b/server/gql/storytelling.graphql index 2671930201..a5bc71c2ab 100644 --- a/server/gql/storytelling.graphql +++ b/server/gql/storytelling.graphql @@ -12,6 +12,7 @@ type Story implements Node { sceneId: ID! scene: Scene panelPosition: Position! + bgColor: String isBasicAuthActive: Boolean! basicAuthUsername: String! @@ -68,6 +69,7 @@ input UpdateStoryInput { title: String index: Int panelPosition: Position + bgColor: String # publishment isBasicAuthActive: Boolean diff --git a/server/internal/adapter/gql/generated.go b/server/internal/adapter/gql/generated.go index e12083b850..9c933cb066 100644 --- a/server/internal/adapter/gql/generated.go +++ b/server/internal/adapter/gql/generated.go @@ -1022,6 +1022,7 @@ type ComplexityRoot struct { Alias func(childComplexity int) int BasicAuthPassword func(childComplexity int) int BasicAuthUsername func(childComplexity int) int + BgColor func(childComplexity int) int CreatedAt func(childComplexity int) int ID func(childComplexity int) int IsBasicAuthActive func(childComplexity int) int @@ -6467,6 +6468,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Story.BasicAuthUsername(childComplexity), true + case "Story.bgColor": + if e.complexity.Story.BgColor == nil { + break + } + + return e.complexity.Story.BgColor(childComplexity), true + case "Story.createdAt": if e.complexity.Story.CreatedAt == nil { break @@ -8988,6 +8996,7 @@ extend type Mutation { sceneId: ID! scene: Scene panelPosition: Position! + bgColor: String isBasicAuthActive: Boolean! basicAuthUsername: String! @@ -9044,6 +9053,7 @@ input UpdateStoryInput { title: String index: Int panelPosition: Position + bgColor: String # publishment isBasicAuthActive: Boolean @@ -14493,6 +14503,8 @@ func (ec *executionContext) fieldContext_CreateStoryBlockPayload_story(ctx conte return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -17294,6 +17306,8 @@ func (ec *executionContext) fieldContext_DeleteStoryPagePayload_story(ctx contex return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -25730,6 +25744,8 @@ func (ec *executionContext) fieldContext_MoveStoryBlockPayload_story(ctx context return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -25976,6 +25992,8 @@ func (ec *executionContext) fieldContext_MoveStoryPagePayload_story(ctx context. return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -26194,6 +26212,8 @@ func (ec *executionContext) fieldContext_MoveStoryPayload_stories(ctx context.Co return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -42050,6 +42070,8 @@ func (ec *executionContext) fieldContext_RemoveStoryBlockPayload_story(ctx conte return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -43203,6 +43225,8 @@ func (ec *executionContext) fieldContext_Scene_stories(ctx context.Context, fiel return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -45112,6 +45136,47 @@ func (ec *executionContext) fieldContext_Story_panelPosition(ctx context.Context return fc, nil } +func (ec *executionContext) _Story_bgColor(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.Story) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Story_bgColor(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.BgColor, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Story_bgColor(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Story", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Story_isBasicAuthActive(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.Story) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Story_isBasicAuthActive(ctx, field) if err != nil { @@ -46617,6 +46682,8 @@ func (ec *executionContext) fieldContext_StoryPagePayload_story(ctx context.Cont return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -46703,6 +46770,8 @@ func (ec *executionContext) fieldContext_StoryPayload_story(ctx context.Context, return ec.fieldContext_Story_scene(ctx, field) case "panelPosition": return ec.fieldContext_Story_panelPosition(ctx, field) + case "bgColor": + return ec.fieldContext_Story_bgColor(ctx, field) case "isBasicAuthActive": return ec.fieldContext_Story_isBasicAuthActive(ctx, field) case "basicAuthUsername": @@ -57078,7 +57147,7 @@ func (ec *executionContext) unmarshalInputUpdateStoryInput(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"sceneId", "storyId", "title", "index", "panelPosition", "isBasicAuthActive", "basicAuthUsername", "basicAuthPassword", "alias", "publicTitle", "publicDescription", "publicImage", "publicNoIndex", "deletePublicImage"} + fieldsInOrder := [...]string{"sceneId", "storyId", "title", "index", "panelPosition", "bgColor", "isBasicAuthActive", "basicAuthUsername", "basicAuthPassword", "alias", "publicTitle", "publicDescription", "publicImage", "publicNoIndex", "deletePublicImage"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -57125,6 +57194,14 @@ func (ec *executionContext) unmarshalInputUpdateStoryInput(ctx context.Context, if err != nil { return it, err } + case "bgColor": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("bgColor")) + it.BgColor, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } case "isBasicAuthActive": var err error @@ -65803,6 +65880,10 @@ func (ec *executionContext) _Story(ctx context.Context, sel ast.SelectionSet, ob if out.Values[i] == graphql.Null { atomic.AddUint32(&invalids, 1) } + case "bgColor": + + out.Values[i] = ec._Story_bgColor(ctx, field, obj) + case "isBasicAuthActive": out.Values[i] = ec._Story_isBasicAuthActive(ctx, field, obj) diff --git a/server/internal/adapter/gql/gqlmodel/convert_storytelling.go b/server/internal/adapter/gql/gqlmodel/convert_storytelling.go index eb429904e9..d2a1374b36 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_storytelling.go +++ b/server/internal/adapter/gql/gqlmodel/convert_storytelling.go @@ -21,6 +21,7 @@ func ToStory(s *storytelling.Story) *Story { UpdatedAt: s.UpdatedAt(), PublishedAt: s.PublishedAt(), PanelPosition: ToStoryPosition(s.PanelPosition()), + BgColor: ToStoryBgColor(s.BgColor()), IsBasicAuthActive: s.IsBasicAuthActive(), BasicAuthUsername: s.BasicAuthUsername(), @@ -113,6 +114,10 @@ func ToStoryPosition(v storytelling.Position) Position { return "" } +func ToStoryBgColor(bg string) *string { + return &bg +} + func FromStoryPositionRef(v *Position) *storytelling.Position { if v == nil { return nil diff --git a/server/internal/adapter/gql/gqlmodel/convert_value.go b/server/internal/adapter/gql/gqlmodel/convert_value.go index 6b51822745..2f587ebbb7 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_value.go +++ b/server/internal/adapter/gql/gqlmodel/convert_value.go @@ -73,6 +73,12 @@ func valueInterfaceToGqlValue(v interface{}) interface{} { North: v2.North, South: v2.South, } + case []any: + gqlArray := make([]any, len(v2)) + for i, item := range v2 { + gqlArray[i] = valueInterfaceToGqlValue(item) + } + return gqlArray } return nil } diff --git a/server/internal/adapter/gql/gqlmodel/models_gen.go b/server/internal/adapter/gql/gqlmodel/models_gen.go index eef7a1674e..0ffd7f5865 100644 --- a/server/internal/adapter/gql/gqlmodel/models_gen.go +++ b/server/internal/adapter/gql/gqlmodel/models_gen.go @@ -1273,6 +1273,7 @@ type Story struct { SceneID ID `json:"sceneId"` Scene *Scene `json:"scene"` PanelPosition Position `json:"panelPosition"` + BgColor *string `json:"bgColor"` IsBasicAuthActive bool `json:"isBasicAuthActive"` BasicAuthUsername string `json:"basicAuthUsername"` BasicAuthPassword string `json:"basicAuthPassword"` @@ -1538,6 +1539,7 @@ type UpdateStoryInput struct { Title *string `json:"title"` Index *int `json:"index"` PanelPosition *Position `json:"panelPosition"` + BgColor *string `json:"bgColor"` IsBasicAuthActive *bool `json:"isBasicAuthActive"` BasicAuthUsername *string `json:"basicAuthUsername"` BasicAuthPassword *string `json:"basicAuthPassword"` diff --git a/server/internal/adapter/gql/resolver_mutation_storytelling.go b/server/internal/adapter/gql/resolver_mutation_storytelling.go index 68e34c1a7a..36133680a4 100644 --- a/server/internal/adapter/gql/resolver_mutation_storytelling.go +++ b/server/internal/adapter/gql/resolver_mutation_storytelling.go @@ -44,6 +44,7 @@ func (r *mutationResolver) UpdateStory(ctx context.Context, input gqlmodel.Updat Title: input.Title, Index: input.Index, PanelPosition: gqlmodel.FromStoryPositionRef(input.PanelPosition), + BgColor: input.BgColor, IsBasicAuthActive: input.IsBasicAuthActive, BasicAuthUsername: input.BasicAuthUsername, diff --git a/server/internal/infrastructure/mongo/mongodoc/storytelling.go b/server/internal/infrastructure/mongo/mongodoc/storytelling.go index fe12fdac05..3308abb9e5 100644 --- a/server/internal/infrastructure/mongo/mongodoc/storytelling.go +++ b/server/internal/infrastructure/mongo/mongodoc/storytelling.go @@ -22,6 +22,7 @@ type StorytellingDocument struct { UpdatedAt time.Time Index int PanelPosition string + BgColor string IsBasicAuthActive bool BasicAuthUsername string @@ -72,6 +73,7 @@ func NewStorytelling(s *storytelling.Story) (*StorytellingDocument, string) { UpdatedAt: s.UpdatedAt(), Index: 1, PanelPosition: string(s.PanelPosition()), + BgColor: s.BgColor(), IsBasicAuthActive: s.IsBasicAuthActive(), BasicAuthUsername: s.BasicAuthUsername(), @@ -179,6 +181,7 @@ func (d *StorytellingDocument) Model() (*storytelling.Story, error) { Alias(d.Alias). Status(storytelling.PublishmentStatus(d.Status)). PanelPosition(storytelling.Position(d.PanelPosition)). + BgColor(d.BgColor). PublishedAt(d.PublishedAt). UpdatedAt(d.UpdatedAt). Pages(storytelling.NewPageList(pages)). diff --git a/server/internal/usecase/interactor/storytelling.go b/server/internal/usecase/interactor/storytelling.go index 9102f44213..fc4ce39d6b 100644 --- a/server/internal/usecase/interactor/storytelling.go +++ b/server/internal/usecase/interactor/storytelling.go @@ -170,6 +170,10 @@ func (i *Storytelling) Update(ctx context.Context, inp interfaces.UpdateStoryInp story.SetPanelPosition(*inp.PanelPosition) } + if inp.BgColor != nil { + story.SetBgColor(*inp.BgColor) + } + oldAlias := story.Alias() if inp.Alias != nil && *inp.Alias != oldAlias { if err := story.UpdateAlias(*inp.Alias); err != nil { diff --git a/server/internal/usecase/interfaces/story.go b/server/internal/usecase/interfaces/story.go index b7671a18af..a6529bb8e9 100644 --- a/server/internal/usecase/interfaces/story.go +++ b/server/internal/usecase/interfaces/story.go @@ -23,6 +23,7 @@ type UpdateStoryInput struct { Title *string Index *int PanelPosition *storytelling.Position + BgColor *string IsBasicAuthActive *bool BasicAuthUsername *string diff --git a/server/pkg/builtin/manifest.yml b/server/pkg/builtin/manifest.yml index 52eb56ed13..4c76ba12ff 100644 --- a/server/pkg/builtin/manifest.yml +++ b/server/pkg/builtin/manifest.yml @@ -94,7 +94,7 @@ extensions: collection: Terrain title: Terrain fields: - - id: terrainBool + - id: terrain type: bool title: Enable description: Show elevation when close to the surface. @@ -104,7 +104,7 @@ extensions: description: Specify terrain type. defaultValue: cesium availableIf: - field: terrainBool + field: terrain type: bool value: true choices: @@ -145,7 +145,7 @@ extensions: defaultValue: 1 suffix: x availableIf: - field: terrainBool + field: terrain type: bool value: true - id: terrainExaggerationRelativeHeight @@ -155,7 +155,7 @@ extensions: defaultValue: 0 suffix: m availableIf: - field: terrainBool + field: terrain type: bool value: true - id: depthTestAgainstTerrain @@ -163,87 +163,94 @@ extensions: title: Hide objects under terrain description: Hides objects under the terrain. Depending on the loading status of the terrain, objects may be shown or hidden. availableIf: - field: terrainBool + field: terrain type: bool value: true - id: globeLighting collection: Globe title: Globe Lighting fields: - - id: globeLightingBool + - id: globeLighting type: bool title: Enable + defaultValue: false description: This property will support the globe receive Entitys Lighting. - id: globeShadow collection: Globe title: Globe Shadow fields: - - id: globeShadowBool + - id: globeShadow type: bool title: Enable + defaultValue: false description: This property will support the globe receive Entitys shadows. - id: globeAtmosphere collection: Globe title: Globe Atmosphere fields: - - id: globeAtmosphereBool + - id: globeAtmosphere type: bool title: Enable + defaultValue: true description: This setting handles the so-called atmosphere effect of Earth. - id: globeAtmosphereIntensity type: number title: Light Intensity - description: "Change the light intensity of the selected tile map. Min: 0 Max: 1" - defaultValue: 0.5 + description: "Change the light intensity of the selected tile map. Min: 0 Max: 30" + defaultValue: 10 ui: slider min: 0 - max: 1 + max: 30 availableIf: - field: globeAtmosphereBool + field: globeAtmosphere type: bool value: true - id: skyBox collection: Sky title: Sky Box fields: - - id: skyBoxBool + - id: skyBox type: bool title: Enable + defaultValue: true description: Description needed. - id: sun collection: Sky title: Sun fields: - - id: sunBool + - id: sun type: bool title: Enable + defaultValue: true description: Description needed. - id: moon collection: Sky title: Moon fields: - - id: moonBool + - id: moon type: bool title: Enable + defaultValue: true description: Description needed. - id: skyAtmosphere collection: Sky title: Sky Atmosphere fields: - - id: skyAtmosphereBool + - id: skyAtmosphere type: bool title: Enable + defaultValue: true description: Description neeeded. - id: skyAtmosphereIntensity type: number title: Light Intensity - description: "Change the light intensity of the selected tile map. Min: 0 Max: 1" - defaultValue: 0.5 + description: "Change the light intensity of the selected tile map. Min: 0 Max: 200" + defaultValue: 50 ui: slider min: 0 - max: 1 + max: 200 availableIf: - field: skyAtmosphereBool + field: skyAtmosphere type: bool value: true - id: camera @@ -262,7 +269,7 @@ extensions: - id: fov type: number title: FOV - description: Description needed + # description: Description needed defaultValue: 3 ui: slider min: 0 @@ -2303,9 +2310,6 @@ extensions: type: number suffix: px defaultValue: 20 - - id: background - title: background Setting - type: string - id: title title: Title Setting fields: @@ -2336,7 +2340,7 @@ extensions: ui: datetime title: Time - id: textStoryBlock - name: Text Block + name: Text type: storyBlock description: Storytelling Text Block schema: @@ -2358,7 +2362,7 @@ extensions: title: Content ui: multiline - id: mdTextStoryBlock - name: MD Text Block + name: MD Text type: storyBlock description: Storytelling MD Text Block schema: @@ -2380,7 +2384,7 @@ extensions: title: Content ui: multiline - id: imageStoryBlock - name: Image Block + name: Image type: storyBlock description: Storytelling Image Block schema: @@ -2402,7 +2406,7 @@ extensions: title: Image ui: image - id: videoStoryBlock - name: Video Block + name: Video type: storyBlock description: Storytelling Video Block schema: @@ -2457,6 +2461,12 @@ extensions: - id: cameraPosition type: camera title: Camera position + - id: cameraDuration + type: number + title: Duration + suffix: s + min: 0 + defaultValue: 2 - id: linkButtonStoryBlock name: Link Button type: storyBlock @@ -2475,6 +2485,7 @@ extensions: - id: default title: Link Button list: true + representativeField: title fields: - id: title type: string @@ -2490,40 +2501,8 @@ extensions: - id: url type: url title: Link URL - - id: timelineStoryBlock - name: Timeline Block - type: storyBlock - description: Storytelling Timeline Block - schema: - groups: - - id: panel - title: Panel Setting - fields: - - id: padding - type: spacing - title: Padding - ui: padding - min: 0 - max: 100 - - id: default - title: Timeline Setting - fields: - - id: timelineSetting - type: timeline - ui: datetime - title: Timeline Setting - - id: playMode - type: string - title: Play Mode - description: Specify play mode. - defaultValue: once - choices: - - key: once - label: Once - - key: loop - label: Loop - id: showLayersStoryBlock - name: Show Layers + name: Show Layers Button type: storyBlock description: Storytelling Show Layers schema: @@ -2557,4 +2536,36 @@ extensions: type: array title: Show Layers ui: layer - choices: \ No newline at end of file + choices: + - id: timelineStoryBlock + name: Timeline + type: storyBlock + description: Storytelling Timeline Block + schema: + groups: + - id: panel + title: Panel Setting + fields: + - id: padding + type: spacing + title: Padding + ui: padding + min: 0 + max: 100 + - id: default + title: Timeline Setting + fields: + - id: timelineSetting + type: timeline + ui: datetime + title: Timeline Setting + - id: playMode + type: string + title: Play Mode + description: Specify play mode. + defaultValue: once + choices: + - key: once + label: Once + - key: loop + label: Loop \ No newline at end of file diff --git a/server/pkg/builtin/manifest_ja.yml b/server/pkg/builtin/manifest_ja.yml index 004105ad02..b441dad5fe 100644 --- a/server/pkg/builtin/manifest_ja.yml +++ b/server/pkg/builtin/manifest_ja.yml @@ -1091,4 +1091,62 @@ extensions: title: 動画 fields: text: - title: コンテンツ \ No newline at end of file + title: コンテンツ + cameraButtonStoryBlock: + name: カメラボタン + description: ストーリーテリングのカメラ移動出来るボタンブロック + propertySchema: + panel: + title: パネル設定 + fields: + padding: + title: 余白 + default: + title: カメラボタン + fields: + title: + title: タイトル + color: + title: 色 + bgColor: + title: 背景色 + cameraPosition: + title: カメラ位置 + cameraDuration: + title: カメラ移動時間 + timelineStoryBlock: + name: タイムライン + description: ストーリーテリングの時間変更できるブロック + propertySchema: + panel: + title: パネル設定 + fields: + padding: + title: 余白 + default: + title: タイムライン設定 + fields: + timelineSetting: + title: タイムライン設定 + playMode: + title: プレイモード + showLayersStoryBlock: + name: レイヤーボタン + description: ストーリーテリングのレイヤー変更出来るボタンブロック + propertySchema: + panel: + title: パネル設定 + fields: + padding: + title: 余白 + default: + title: レイヤーボタン + fields: + title: + title: タイトル + color: + title: 色 + bgColor: + title: 背景色 + showLayers: + title: 見えるレイヤー \ No newline at end of file diff --git a/server/pkg/scene/builder/story.go b/server/pkg/scene/builder/story.go index 77fcf4f72f..78d2ceed20 100644 --- a/server/pkg/scene/builder/story.go +++ b/server/pkg/scene/builder/story.go @@ -9,14 +9,17 @@ import ( ) type storyJSON struct { - ID string `json:"id"` - Property propertyJSON `json:"property"` - Pages []pageJSON `json:"pages"` + ID string `json:"id"` + Property propertyJSON `json:"property"` + Pages []pageJSON `json:"pages"` + PanelPosition string `json:"position"` + BgColor string `json:"bgColor"` } type pageJSON struct { ID string `json:"id"` Property propertyJSON `json:"property"` + Title string `json:"title"` Blocks []blockJSON `json:"blocks"` Swipeable bool `json:"swipeable"` SwipeableLayers []string `json:"swipeableLayers"` @@ -45,6 +48,8 @@ func (b *Builder) storyJSON(ctx context.Context, p []*property.Property) (*story } return b.pageJSON(ctx, *page, p), true }), + PanelPosition: string(b.story.PanelPosition()), + BgColor: b.story.BgColor(), }, nil } @@ -52,6 +57,7 @@ func (b *Builder) pageJSON(ctx context.Context, page storytelling.Page, p []*pro return pageJSON{ ID: page.Id().String(), Property: b.property(ctx, findProperty(p, page.Property())), + Title: page.Title(), Blocks: lo.FilterMap(page.Blocks(), func(block *storytelling.Block, _ int) (blockJSON, bool) { if block == nil { return blockJSON{}, false diff --git a/server/pkg/storytelling/story.go b/server/pkg/storytelling/story.go index 896669f8fd..7f6ce1d3a5 100644 --- a/server/pkg/storytelling/story.go +++ b/server/pkg/storytelling/story.go @@ -24,6 +24,7 @@ type Story struct { title string pages *PageList panelPosition Position + bgColor string updatedAt time.Time alias string @@ -116,6 +117,10 @@ func (s *Story) SetPanelPosition(panelPosition Position) { s.panelPosition = panelPosition } +func (s *Story) SetBgColor(bgColor string) { + s.bgColor = bgColor +} + func (s *Story) Rename(name string) { s.title = name s.updatedAt = util.Now() @@ -160,6 +165,10 @@ func (s *Story) PanelPosition() Position { return s.panelPosition } +func (s *Story) BgColor() string { + return s.bgColor +} + func (s *Story) ValidateProperties(pm property.Map) error { if pm == nil { return nil diff --git a/server/pkg/storytelling/story_bulider.go b/server/pkg/storytelling/story_bulider.go index 9010849fe2..006a14a299 100644 --- a/server/pkg/storytelling/story_bulider.go +++ b/server/pkg/storytelling/story_bulider.go @@ -68,6 +68,11 @@ func (b *StoryBuilder) PanelPosition(position Position) *StoryBuilder { return b } +func (b *StoryBuilder) BgColor(bgColor string) *StoryBuilder { + b.s.bgColor = bgColor + return b +} + func (b *StoryBuilder) Alias(alias string) *StoryBuilder { b.s.alias = alias return b diff --git a/server/pkg/storytelling/story_test.go b/server/pkg/storytelling/story_test.go index 212566db19..47d812f07e 100644 --- a/server/pkg/storytelling/story_test.go +++ b/server/pkg/storytelling/story_test.go @@ -66,6 +66,9 @@ func TestStory_SettersGetters(t *testing.T) { s.SetPanelPosition(PositionLeft) assert.Equal(t, PositionLeft, s.PanelPosition()) + s.SetBgColor("test") + assert.Equal(t, "test", s.BgColor()) + err := s.SetBasicAuth(true, nil, nil) assert.Equal(t, ErrBasicAuthUserNamePasswordEmpty, err) diff --git a/web/package.json b/web/package.json index 4c9a2417e5..ae856a0e1b 100644 --- a/web/package.json +++ b/web/package.json @@ -188,4 +188,4 @@ "use-file-input": "1.0.0", "uuid": "9.0.1" } -} \ No newline at end of file +} diff --git a/web/src/beta/components/Icon/Icons/layer.svg b/web/src/beta/components/Icon/Icons/layer.svg new file mode 100644 index 0000000000..dc3c73c495 --- /dev/null +++ b/web/src/beta/components/Icon/Icons/layer.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/web/src/beta/components/Icon/Icons/pause.svg b/web/src/beta/components/Icon/Icons/pause.svg new file mode 100644 index 0000000000..38cd5e13fa --- /dev/null +++ b/web/src/beta/components/Icon/Icons/pause.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/beta/components/Icon/Icons/set.svg b/web/src/beta/components/Icon/Icons/set.svg new file mode 100644 index 0000000000..c8ec79edb3 --- /dev/null +++ b/web/src/beta/components/Icon/Icons/set.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/beta/components/Icon/Icons/showLayersStoryBlock.svg b/web/src/beta/components/Icon/Icons/showLayersStoryBlock.svg new file mode 100644 index 0000000000..91bf822a73 --- /dev/null +++ b/web/src/beta/components/Icon/Icons/showLayersStoryBlock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/src/beta/components/Icon/Icons/slider.svg b/web/src/beta/components/Icon/Icons/slider.svg new file mode 100644 index 0000000000..a216ee08b3 --- /dev/null +++ b/web/src/beta/components/Icon/Icons/slider.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/beta/components/Icon/Icons/timeline-play-left.svg b/web/src/beta/components/Icon/Icons/timeline-play-left.svg new file mode 100644 index 0000000000..8e40d78935 --- /dev/null +++ b/web/src/beta/components/Icon/Icons/timeline-play-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/beta/components/Icon/Icons/timeline-play-right.svg b/web/src/beta/components/Icon/Icons/timeline-play-right.svg new file mode 100644 index 0000000000..93c1ab0e76 --- /dev/null +++ b/web/src/beta/components/Icon/Icons/timeline-play-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/beta/components/Icon/Icons/timelineStoryBlock.svg b/web/src/beta/components/Icon/Icons/timelineStoryBlock.svg new file mode 100644 index 0000000000..8b64acebce --- /dev/null +++ b/web/src/beta/components/Icon/Icons/timelineStoryBlock.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/src/beta/components/Icon/Icons/timelineStoryBlockSolid.svg b/web/src/beta/components/Icon/Icons/timelineStoryBlockSolid.svg new file mode 100644 index 0000000000..e3d0fc806f --- /dev/null +++ b/web/src/beta/components/Icon/Icons/timelineStoryBlockSolid.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/beta/components/Icon/icons.ts b/web/src/beta/components/Icon/icons.ts index 0e0c7e61dd..debb6b8320 100644 --- a/web/src/beta/components/Icon/icons.ts +++ b/web/src/beta/components/Icon/icons.ts @@ -43,6 +43,7 @@ import ZoomToLayer from "./Icons/zoomToLayer.svg"; import LayerStyleIcon from "./Icons/layerStyle.svg"; import AddLayerStyleButtonIcon from "./Icons/addLayerStyleButton.svg"; import LayerInspector from "./Icons/layerInspector.svg"; +import LayerIcon from "./Icons/layer.svg"; import Clock from "./Icons/Clock.svg"; // MSIC @@ -56,6 +57,11 @@ import Timeline from "./Icons/timeline.svg"; import PlayRight from "./Icons/play-right.svg"; import PlayLeft from "./Icons/play-left.svg"; import Ellipse from "./Icons/ellipse.svg"; +import TimelinePlayRight from "./Icons/timeline-play-right.svg"; +import TimelinePlayLeft from "./Icons/timeline-play-left.svg"; +import Pause from "./Icons/pause.svg"; +import Slider from "./Icons/slider.svg"; +import Set from "./Icons/set.svg"; // Dashboard import Dashboard from "./Icons/dashboard.svg"; @@ -85,6 +91,9 @@ import VideoStoryBlock from "./Icons/videoStoryBlock.svg"; import ImageStoryBlock from "./Icons/imageStoryBlock.svg"; import MdTextStoryBlock from "./Icons/mdTextStoryBlock.svg"; import CameraButtonStoryBlock from "./Icons/cameraButtonStoryBlock.svg"; +import ShowLayersStoryBlock from "./Icons/showLayersStoryBlock.svg"; +import TimelineStoryBlock from "./Icons/timelineStoryBlock.svg"; +import TimelineStoryBlockSolid from "./Icons/timelineStoryBlockSolid.svg"; // Widget tab import Desktop from "./Icons/desktop.svg"; @@ -113,6 +122,7 @@ import PublicGitHubRepo from "./Icons/publicGitHubRepo.svg"; import Marketplace from "./Icons/marketplace.svg"; export default { + layer: LayerIcon, addLayerStyle: AddLayerStyleButtonIcon, layerStyle: LayerStyleIcon, layerInspector: LayerInspector, @@ -141,6 +151,11 @@ export default { ellipse: Ellipse, playRight: PlayRight, playLeft: PlayLeft, + timelinePlayLeft: TimelinePlayLeft, + timelinePlayRight: TimelinePlayRight, + pause: Pause, + slider: Slider, + set: Set, storyPage: StoryPage, square: Square, swiper: Swiper, @@ -176,6 +191,9 @@ export default { imageStoryBlock: ImageStoryBlock, mdTextStoryBlock: MdTextStoryBlock, cameraButtonStoryBlock: CameraButtonStoryBlock, + showLayersStoryBlock: ShowLayersStoryBlock, + timelineStoryBlock: TimelineStoryBlock, + timelineStoryBlockSolid: TimelineStoryBlockSolid, widget: Widgets, widgets: Widgets, menu: WidgetMenu, diff --git a/web/src/beta/components/Resizable/index.tsx b/web/src/beta/components/Resizable/index.tsx index d406590816..f4db5333fe 100644 --- a/web/src/beta/components/Resizable/index.tsx +++ b/web/src/beta/components/Resizable/index.tsx @@ -106,14 +106,14 @@ const MinimizedWrapper = styled.div>` display: flex; flex-direction: ${({ direction }) => (direction === "horizontal" ? "column" : "row")}; align-items: center; - width: ${({ direction }) => (direction === "horizontal" ? null : `24px`)}; - height: ${({ direction }) => (direction === "vertical" ? null : `24px`)}; - background: ${({ theme }) => theme.bg[2]}; + width: ${({ direction }) => (direction === "horizontal" ? null : `20px`)}; + height: ${({ direction }) => (direction === "vertical" ? null : `20px`)}; + background: ${({ theme }) => theme.bg[0]}; cursor: pointer; transition: background 0.3s; :hover { - background: ${({ theme }) => theme.bg[3]}; + background: ${({ theme }) => theme.bg[1]}; } `; export default Resizable; diff --git a/web/src/beta/components/SidePanelSectionField/index.tsx b/web/src/beta/components/SidePanelSectionField/index.tsx index 8c5ee2821f..06f57c61cf 100644 --- a/web/src/beta/components/SidePanelSectionField/index.tsx +++ b/web/src/beta/components/SidePanelSectionField/index.tsx @@ -1,4 +1,4 @@ -import { useState, ReactNode } from "react"; +import { useState, ReactNode, useEffect } from "react"; import { styled, useTheme } from "@reearth/services/theme"; @@ -8,10 +8,15 @@ import Text from "../Text"; const SidePanelSectionField: React.FC<{ className?: string; title?: string; + startCollapsed?: boolean; children?: ReactNode; -}> = ({ className, title, children }) => { +}> = ({ className, title, startCollapsed, children }) => { const theme = useTheme(); - const [opened, setOpened] = useState(true); + const [opened, setOpened] = useState(); + + useEffect(() => { + setOpened(!startCollapsed ?? true); + }, []); // eslint-disable-line react-hooks/exhaustive-deps return ( @@ -42,7 +47,7 @@ const Header = styled.div` height: 38px; `; -const ArrowIcon = styled(Icon)<{ opened: boolean }>` +const ArrowIcon = styled(Icon)<{ opened?: boolean }>` transform: rotate(${props => (props.opened ? 90 : 180)}deg); transition: all 0.2s; `; @@ -51,7 +56,7 @@ const Content = styled.div` padding: 8px; display: flex; flex-direction: column; - gap: 4px; + gap: 16px; `; export default SidePanelSectionField; diff --git a/web/src/beta/components/fields/ColorField/index.tsx b/web/src/beta/components/fields/ColorField/index.tsx index db9cd48422..9b161f62e3 100644 --- a/web/src/beta/components/fields/ColorField/index.tsx +++ b/web/src/beta/components/fields/ColorField/index.tsx @@ -16,7 +16,7 @@ import { Props, RGBA } from "./types"; const channels = ["r", "g", "b", "a"]; const hexPlaceholder = "#RRGGBBAA"; -const ColorField: React.FC = ({ name, description, value, onChange }) => { +const ColorField: React.FC = ({ name, description, value, onChange, className }) => { const t = useT(); const theme = useTheme(); const { @@ -37,7 +37,7 @@ const ColorField: React.FC = ({ name, description, value, onChange }) => } = useHooks({ value, onChange }); return ( - + diff --git a/web/src/beta/components/fields/ColorField/types.ts b/web/src/beta/components/fields/ColorField/types.ts index 2be9ab1ca6..2babeafe15 100644 --- a/web/src/beta/components/fields/ColorField/types.ts +++ b/web/src/beta/components/fields/ColorField/types.ts @@ -1,5 +1,6 @@ // Component Props export type Props = { + className?: string; name?: string; description?: string; value?: string; diff --git a/web/src/beta/components/fields/DateTimeField/EditPanel/hooks.ts b/web/src/beta/components/fields/DateTimeField/EditPanel/hooks.ts index 58c642a873..9806be50ce 100644 --- a/web/src/beta/components/fields/DateTimeField/EditPanel/hooks.ts +++ b/web/src/beta/components/fields/DateTimeField/EditPanel/hooks.ts @@ -45,7 +45,7 @@ export default ({ value, onChange, setDateTime }: Props) => { setDateTime?.(formattedDateTime); onChange?.(formattedDateTime); } - }, [offsetFromUTC, selectedTimezone, date, time, setDateTime, onChange]); + }, [offsetFromUTC, selectedTimezone.timezone, date, time, setDateTime, onChange]); const handleTimezoneSelect = useCallback( (newValue: string) => { @@ -81,7 +81,7 @@ export default ({ value, onChange, setDateTime }: Props) => { time, selectedTimezone, offsetFromUTC, - onTimeChange: handleTimeChange, + handleTimeChange, onTimezoneSelect: handleTimezoneSelect, onDateChange: handleDateChange, onDateTimeApply: handleApplyChange, diff --git a/web/src/beta/components/fields/DateTimeField/EditPanel/index.tsx b/web/src/beta/components/fields/DateTimeField/EditPanel/index.tsx index 02585f9644..8b4dc6da24 100644 --- a/web/src/beta/components/fields/DateTimeField/EditPanel/index.tsx +++ b/web/src/beta/components/fields/DateTimeField/EditPanel/index.tsx @@ -26,9 +26,9 @@ const EditPanel: React.FC = ({ onChange, onClose, value, setDateTime }) = selectedTimezone, offsetFromUTC, onDateChange, - onTimeChange, onTimezoneSelect, onDateTimeApply, + handleTimeChange, } = useHooks({ value, onChange, setDateTime }); const isButtonDisabled = useMemo(() => { @@ -45,7 +45,7 @@ const EditPanel: React.FC = ({ onChange, onClose, value, setDateTime }) = - + diff --git a/web/src/beta/components/fields/SelectField/index.stories.tsx b/web/src/beta/components/fields/SelectField/index.stories.tsx index 91a7d5d04f..cfeb83b4d5 100644 --- a/web/src/beta/components/fields/SelectField/index.stories.tsx +++ b/web/src/beta/components/fields/SelectField/index.stories.tsx @@ -4,7 +4,7 @@ import { useCallback } from "react"; import { styled } from "@reearth/services/theme"; -import SelectField, { Props } from "."; +import SelectField, { SingleSelectProps, MultiSelectProps } from "."; const meta: Meta = { component: SelectField, @@ -14,10 +14,15 @@ export default meta; type Story = StoryObj; -export const Default: Story = (args: Props) => { +export const Default: Story = (args: SingleSelectProps) => { const [_, updateArgs] = useArgs(); - const handleChange = useCallback((value: string) => updateArgs({ value: value }), [updateArgs]); + const handleChange = useCallback( + (value: string) => { + updateArgs({ value: value }); + }, + [updateArgs], + ); return ( @@ -29,7 +34,6 @@ export const Default: Story = (args: Props) => { {...args} name="Disabled" description="Props are controlled by the field above" - placeholder="This is a disabled field" disabled={true} onChange={handleChange} /> @@ -37,10 +41,44 @@ export const Default: Story = (args: Props) => {
+
+
+ ); +}; + +export const MultiSelect: Story = (args: MultiSelectProps) => { + const [_, updateArgs] = useArgs(); + + const handleChange = useCallback( + (value: string[] | undefined) => { + updateArgs({ value: value }); + }, + [updateArgs], + ); + + return ( + +
+ +
+
+
@@ -72,3 +110,24 @@ Default.args = { ], onChange: () => console.log("clicked"), }; + +MultiSelect.args = { + name: "Select Field", + description: "Select from the options ", + disabled: false, + value: undefined, + options: [ + { + label: "item 1", + key: "item_1", + }, + { label: "item 2", key: "item_2" }, + { label: "item 3", key: "item_3" }, + { label: "item 4", key: "item_4" }, + { label: "item 5", key: "item_5" }, + { label: "item 6", key: "item_6" }, + { label: "item 7", key: "item_7" }, + ], + multiSelect: true, + onChange: () => console.log("clicked"), +}; diff --git a/web/src/beta/components/fields/SelectField/index.tsx b/web/src/beta/components/fields/SelectField/index.tsx index 5f182b353a..589f2d4d2f 100644 --- a/web/src/beta/components/fields/SelectField/index.tsx +++ b/web/src/beta/components/fields/SelectField/index.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useMemo } from "react"; +import React, { useState, useCallback, useMemo } from "react"; import Icon from "@reearth/beta/components/Icon"; import * as Popover from "@reearth/beta/components/Popover"; @@ -13,21 +13,36 @@ export type SelectValue = { key: string; }; -export type Props = { +type CommonProps = { + className?: string; options?: SelectValue[]; - onChange: (key: string) => void; - value?: string; disabled?: boolean; - placeholder?: string; // Property field name?: string; description?: string; }; +export type SingleSelectProps = { + onChange: (key: string) => void; + value?: string; + multiSelect?: false; +} & CommonProps; + +export type MultiSelectProps = { + onChange: (keys: string[] | undefined) => void; + value?: string[]; + multiSelect: true; +} & CommonProps; + +export type Props = SingleSelectProps | MultiSelectProps; + +// TODO: Fix the onChange method TS error const SelectField: React.FC = ({ + className, options, onChange, value, + multiSelect, disabled = false, name, description, @@ -40,43 +55,106 @@ const SelectField: React.FC = ({ const handleClick = useCallback( (key: string) => { + if (multiSelect === true) { + // handle multiselect + if (value && Array.isArray(value)) { + const tempArray = [...value]; + tempArray.includes(key) + ? tempArray.splice(tempArray.indexOf(key), 1) + : tempArray.push(key); + onChange(tempArray.length > 0 ? [...tempArray] : undefined); + } else { + onChange([key]); + } + return; + } + setOpen(false); - if (key != value) onChange(key); + key != value && onChange(key); + return; }, - [setOpen, onChange, value], + [setOpen, onChange, value, multiSelect], ); const selected = useMemo(() => { - return options?.find(({ key }) => key === value); + return value + ? Array.isArray(value) + ? value.map(key => ({ + key, + label: options?.find(x => x.key === key)?.label, + })) + : options?.find(x => x.key === value) + : undefined; }, [options, value]); + const checkSelected = useCallback( + (key: string) => { + return value ? (Array.isArray(value) ? value.includes(key) : value === key) : false; + }, + [value], + ); + return ( - + - - - - - - - - {options?.map(({ label: value, key }) => ( - - ))} - + 0}> + + + + + + + + {options?.map(({ label: value, key }) => ( + handleClick(key)} + selected={checkSelected(key)}> + {multiSelect && ( + + )} + {value} + + ))} + + {Array.isArray(selected) && ( + + {selected.map(({ label, key }) => ( + + + {label} + + !disabled && handleClick(key)} + disabled={disabled} + /> + + ))} + + )} + ); }; +const ProviderWrapper = styled.div<{ multiSelect: boolean }>` + display: flex; + flex-direction: column; + gap: 6px; + border-radius: 4px; + padding: 4px; + border: ${({ theme, multiSelect }) => (multiSelect ? `1px solid ${theme.outline.weak}` : "none")}; +`; + const InputWrapper = styled.div<{ disabled: boolean }>` display: flex; position: relative; @@ -119,9 +197,9 @@ const ArrowIcon = styled(Icon)<{ open: boolean }>` `; const PickerWrapper = styled(Popover.Content)` - min-width: 100px; - gap: 10px; + min-width: 150px; border: 1px solid ${({ theme }) => theme.outline.weak}; + gap: 4px; outline: none; border-radius: 4px; background: ${({ theme }) => theme.bg[1]}; @@ -133,12 +211,41 @@ const PickerWrapper = styled(Popover.Content)` z-index: 1; `; -const Option = styled(Text)<{ selected: boolean }>` - padding: 4px 12px; - cursor: ${({ selected }) => (selected ? "not-allowed" : "pointer")}; +const OptionWrapper = styled.div<{ selected: boolean }>` + display: flex; + padding: 7px 12px; + align-items: center; + cursor: "pointer"; + background: ${({ theme, selected }) => (selected ? theme.bg[2] : "inherit")}; &:hover { background: ${({ theme, selected }) => (selected ? theme.bg[2] : theme.select.main)}; } `; +const CheckIcon = styled(Icon)` + margin-right: 12px; +`; + +const SelectedWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + max-height: 125px; + overflow: auto; +`; + +const Selected = styled.div<{ disabled: boolean }>` + display: flex; + padding: 7px 12px; + align-items: center; + justify-content: space-between; + border-radius: 2px; + background: ${({ theme }) => theme.bg[2]}; + opacity: ${({ disabled }) => (disabled ? 0.6 : 1)}; +`; + +const CancelIcon = styled(Icon)<{ disabled: boolean }>` + cursor: ${({ disabled }) => (disabled ? "not-allowed" : "pointer")}; +`; + export default SelectField; diff --git a/web/src/beta/components/fields/SliderField/index.stories.tsx b/web/src/beta/components/fields/SliderField/index.stories.tsx index 8a497ab5fe..249a2cd543 100644 --- a/web/src/beta/components/fields/SliderField/index.stories.tsx +++ b/web/src/beta/components/fields/SliderField/index.stories.tsx @@ -17,7 +17,12 @@ type Story = StoryObj; export const Default: Story = (args: Props) => { const [_, updateArgs] = useArgs(); - const handleChange = useCallback((value: number) => updateArgs({ value: value }), [updateArgs]); + const handleChange = useCallback( + (value: number) => { + updateArgs({ value: value }); + }, + [updateArgs], + ); return ( @@ -50,8 +55,7 @@ const Wrapper = styled.div` display: flex; flex-direction: column; gap: 10%; - margin-left: 2rem; - margin-top: 2rem; + margin: 2rem; height: 300px; `; diff --git a/web/src/beta/components/fields/SliderField/index.tsx b/web/src/beta/components/fields/SliderField/index.tsx index 34a56f886f..18689c83c2 100644 --- a/web/src/beta/components/fields/SliderField/index.tsx +++ b/web/src/beta/components/fields/SliderField/index.tsx @@ -1,3 +1,5 @@ +import { useCallback, useEffect, useState } from "react"; + import Property from "@reearth/beta/components/fields"; import Slider, { Props as SliderProps } from "@reearth/beta/components/Slider"; @@ -6,10 +8,23 @@ export type Props = { description?: string; } & SliderProps; -const SliderField: React.FC = ({ name, description, ...args }: Props) => { +const SliderField: React.FC = ({ name, description, value, onChange, ...args }: Props) => { + const [internalState, setInternalState] = useState(value); + + const handleChange = useCallback( + (value: number) => { + setInternalState(value); + }, + [setInternalState], + ); + + useEffect(() => { + setInternalState(value); + }, [value]); + return ( - + ); }; diff --git a/web/src/beta/components/fields/TimelineField/EditPanel/hooks.ts b/web/src/beta/components/fields/TimelineField/EditPanel/hooks.ts index 22c9f38b80..f839d46184 100644 --- a/web/src/beta/components/fields/TimelineField/EditPanel/hooks.ts +++ b/web/src/beta/components/fields/TimelineField/EditPanel/hooks.ts @@ -21,24 +21,28 @@ export default ({ timelineValues, onChange, onClose, setTimelineValues }: Props) endTime: timelineValues?.endTime || "", }; - const endTime = new Date(updatedData?.endTime?.substring(0, 19) || new Date()); - switch (fieldId) { case "startTime": - updatedData.startTime = newValue; - break; - case "endTime": - updatedData.endTime = newValue; + updatedData.startTime = newValue || ""; break; case "currentTime": updatedData.currentTime = newValue; - if ( - (updatedData.startTime && - updatedData.endTime && - new Date(updatedData.currentTime.substring(0, 19)) < - new Date(updatedData.startTime.substring(0, 19))) || - new Date(updatedData.currentTime.substring(0, 19)) > endTime + updatedData.startTime && + new Date(updatedData.currentTime.substring(0, 19)) < + new Date(updatedData.startTime.substring(0, 19)) + ) { + setWarning(true); + } else { + setWarning(false); + } + break; + case "endTime": + updatedData.endTime = newValue; + if ( + updatedData.endTime && + new Date(updatedData.currentTime.substring(0, 19)) > + new Date(updatedData?.endTime?.substring(0, 19)) ) { setWarning(true); } else { diff --git a/web/src/beta/components/fields/TimelineField/EditPanel/index.tsx b/web/src/beta/components/fields/TimelineField/EditPanel/index.tsx index 1092352392..8b9af554f3 100644 --- a/web/src/beta/components/fields/TimelineField/EditPanel/index.tsx +++ b/web/src/beta/components/fields/TimelineField/EditPanel/index.tsx @@ -25,7 +25,7 @@ const EditPanel = ({ onChange, }: EditPanelProps) => { const t = useT(); - const { warning, handleOnChange, onAppyChange } = useHooks({ + const { isDisabled, warning, handleOnChange, onAppyChange } = useHooks({ timelineValues, onChange, onClose, @@ -43,7 +43,7 @@ const EditPanel = ({