diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 04b83e6d57..102f54ac16 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -54,16 +54,3 @@ jobs:
test-results/
if-no-files-found: ignore
retention-days: 7
- slack-notification:
- if: success() || failure()
- name: Slack Notification
- needs:
- - e2e
- runs-on: ubuntu-latest
- steps:
- - name: Slack Notification
- uses: Gamesight/slack-workflow-status@master
- if: always()
- with:
- repo_token: ${{ secrets.GITHUB_TOKEN }}
- slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 0181837e93..ce38e51852 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -4,7 +4,9 @@
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"golang.go",
- "redhat.vscode-yaml"
+ "redhat.vscode-yaml",
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
diff --git a/.vscode/settings.json b/.vscode/settings.json
index ab2e48a8da..7b8d51d19b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,7 +1,6 @@
{
"go.testEnvFile": "${workspaceRoot}/.env",
"go.lintTool": "golangci-lint",
- "editor.formatOnSave": true,
"yaml.format.enable": false,
"yaml.completion": true,
"yaml.validate": true,
@@ -17,20 +16,34 @@
},
"json.schemas": [
{
- "fileMatch": [
- "/server/pkg/builtin/manifest.json"
- ],
+ "fileMatch": ["/server/pkg/builtin/manifest.json"],
"url": "./server/schemas/plugin_manifest.json"
},
{
- "fileMatch": [
- "/server/pkg/builtin/manifest_*.json"
- ],
+ "fileMatch": ["/server/pkg/builtin/manifest_*.json"],
"url": "./server/schemas/plugin_manifest_translation.json"
}
],
"[yaml]": {
"editor.formatOnSave": false
},
- "prettier.enable": false
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ },
+ "editor.formatOnSave": true,
+ "eslint.enable": true,
+ "eslint.useFlatConfig": true,
+ "prettier.enable": true,
+ "[javascript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[javascriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ }
}
diff --git a/server/Makefile b/server/Makefile
index ed74ae7199..ea89ff1aca 100644
--- a/server/Makefile
+++ b/server/Makefile
@@ -1,3 +1,16 @@
+help:
+ @echo "Usage:"
+ @echo " make "
+ @echo ""
+ @echo "Targets:"
+ @echo " lint Run golangci-lint with auto-fix"
+ @echo " test Run unit tests with race detector in short mode"
+ @echo " e2e Run end-to-end tests"
+ @echo " build Build the project"
+ @echo " run-app Run the application"
+ @echo " run-db Run the MongoDB database using Docker Compose"
+ @echo " gql Generate GraphQL code"
+
lint:
golangci-lint run --fix
diff --git a/server/e2e/gql_project_test.go b/server/e2e/gql_project_test.go
index bf258f9b83..aad2e57ad1 100644
--- a/server/e2e/gql_project_test.go
+++ b/server/e2e/gql_project_test.go
@@ -4,7 +4,9 @@ import (
"net/http"
"testing"
+ "github.com/gavv/httpexpect/v2"
"github.com/reearth/reearth/server/internal/app/config"
+ "github.com/stretchr/testify/assert"
)
func TestCreateProject(t *testing.T) {
@@ -195,3 +197,258 @@ func TestSortByName(t *testing.T) {
edges.Element(3).Object().Value("node").Object().Value("name").Equal("B-project")
edges.Element(4).Object().Value("node").Object().Value("name").Equal(seedProjectName)
}
+
+func TestFindStarredByWorkspace(t *testing.T) {
+
+ e := StartServer(t, &config.Config{
+ Origins: []string{"https://example.com"},
+ AuthSrv: config.AuthSrvConfig{
+ Disabled: true,
+ },
+ }, true, baseSeeder)
+ project1ID := createProject(e, "Project 1")
+ project2ID := createProject(e, "Project 2")
+ project3ID := createProject(e, "Project 3")
+ project4ID := createProject(e, "Project 4")
+
+ starProject(e, project1ID)
+ starProject(e, project3ID)
+
+ requestBody := GraphQLRequest{
+ OperationName: "GetStarredProjects",
+ Query: `
+ query GetStarredProjects($teamId: ID!) {
+ starredProjects(teamId: $teamId) {
+ nodes {
+ id
+ name
+ starred
+ }
+ totalCount
+ }
+ }`,
+ Variables: map[string]any{
+ "teamId": wID,
+ },
+ }
+
+ response := e.POST("/api/graphql").
+ WithHeader("Origin", "https://example.com").
+ WithHeader("X-Reearth-Debug-User", uID.String()).
+ WithHeader("Content-Type", "application/json").
+ WithJSON(requestBody).
+ Expect().
+ Status(http.StatusOK).
+ JSON().
+ Object()
+
+ starredProjects := response.Value("data").Object().Value("starredProjects").Object()
+
+ totalCount := starredProjects.Value("totalCount").Raw()
+ assert.Equal(t, float64(2), totalCount, "Expected 2 starred projects")
+
+ nodes := starredProjects.Value("nodes").Array()
+ nodeCount := int(nodes.Length().Raw())
+ assert.Equal(t, 2, nodeCount, "Expected 2 nodes in the response")
+
+ starredProjectsMap := make(map[string]bool)
+ for _, node := range nodes.Iter() {
+ obj := node.Object()
+ id := obj.Value("id").String().Raw()
+ name := obj.Value("name").String().Raw()
+ starred := obj.Value("starred").Boolean().Raw()
+
+ starredProjectsMap[id] = true
+
+ if id == project1ID {
+ assert.Equal(t, "Project 1", name)
+ assert.True(t, starred)
+ } else if id == project3ID {
+ assert.Equal(t, "Project 3", name)
+ assert.True(t, starred)
+ } else {
+ t.Errorf("Unexpected project in starred projects: %s", id)
+ }
+ }
+
+ assert.True(t, starredProjectsMap[project1ID], "Project 1 should be starred")
+ assert.True(t, starredProjectsMap[project3ID], "Project 3 should be starred")
+ assert.False(t, starredProjectsMap[project2ID], "Project 2 should not be starred")
+ assert.False(t, starredProjectsMap[project4ID], "Project 4 should not be starred")
+
+}
+
+func starProject(e *httpexpect.Expect, projectID string) {
+ updateProjectMutation := GraphQLRequest{
+ OperationName: "UpdateProject",
+ Query: `
+ mutation UpdateProject($projectId: ID!, $starred: Boolean!) {
+ updateProject(input: {projectId: $projectId, starred: $starred}) {
+ project {
+ id
+ starred
+ }
+ }
+ }`,
+ Variables: map[string]any{
+ "projectId": projectID,
+ "starred": true,
+ },
+ }
+
+ response := e.POST("/api/graphql").
+ WithHeader("Origin", "https://example.com").
+ WithHeader("X-Reearth-Debug-User", uID.String()).
+ WithHeader("Content-Type", "application/json").
+ WithJSON(updateProjectMutation).
+ Expect().
+ Status(http.StatusOK).
+ JSON().
+ Object().
+ Value("data").Object().
+ Value("updateProject").Object().
+ Value("project").Object()
+
+ response.ValueEqual("id", projectID).
+ ValueEqual("starred", true)
+}
+
+func TestSortByUpdatedAt(t *testing.T) {
+
+ e := StartServer(t, &config.Config{
+ Origins: []string{"https://example.com"},
+ AuthSrv: config.AuthSrvConfig{
+ Disabled: true,
+ },
+ }, true, baseSeeder)
+
+ createProject(e, "project1-test")
+ project2ID := createProject(e, "project2-test")
+ createProject(e, "project3-test")
+
+ requestBody := GraphQLRequest{
+ OperationName: "UpdateProject",
+ Query: `mutation UpdateProject($input: UpdateProjectInput!) {
+ updateProject(input: $input) {
+ project {
+ id
+ name
+ description
+ updatedAt
+ __typename
+ }
+ __typename
+ }
+ }`,
+ Variables: map[string]any{
+ "input": map[string]any{
+ "projectId": project2ID,
+ "description": "test updaet",
+ },
+ },
+ }
+
+ // Update 'project2'
+ e.POST("/api/graphql").
+ WithHeader("Origin", "https://example.com").
+ WithHeader("X-Reearth-Debug-User", uID.String()).
+ WithHeader("Content-Type", "application/json").
+ WithJSON(requestBody).
+ Expect().
+ Status(http.StatusOK).
+ JSON()
+
+ requestBody = GraphQLRequest{
+ OperationName: "GetProjects",
+ Query: `
+ query GetProjects($teamId: ID!, $pagination: Pagination, $keyword: String, $sort: ProjectSort) {
+ projects(
+ teamId: $teamId
+ pagination: $pagination
+ keyword: $keyword
+ sort: $sort
+ ) {
+ edges {
+ node {
+ id
+ ...ProjectFragment
+ scene {
+ id
+ __typename
+ }
+ __typename
+ }
+ __typename
+ }
+ nodes {
+ id
+ ...ProjectFragment
+ scene {
+ id
+ __typename
+ }
+ __typename
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ __typename
+ }
+ totalCount
+ __typename
+ }
+ }
+ fragment ProjectFragment on Project {
+ id
+ name
+ description
+ imageUrl
+ isArchived
+ isBasicAuthActive
+ basicAuthUsername
+ basicAuthPassword
+ publicTitle
+ publicDescription
+ publicImage
+ alias
+ enableGa
+ trackingId
+ publishmentStatus
+ updatedAt
+ createdAt
+ coreSupport
+ starred
+ __typename
+ }`,
+ Variables: map[string]any{
+ "pagination": map[string]any{
+ "first": 3, // Get first 3 itme
+ },
+ "teamId": wID.String(),
+ "sort": map[string]string{
+ "field": "UPDATEDAT",
+ "direction": "DESC", // Sort DESC by UPDATEDAT
+ },
+ },
+ }
+
+ edges := e.POST("/api/graphql").
+ WithHeader("Origin", "https://example.com").
+ WithHeader("X-Reearth-Debug-User", uID.String()).
+ WithHeader("Content-Type", "application/json").
+ WithJSON(requestBody).
+ Expect().
+ Status(http.StatusOK).
+ JSON().
+ Object().
+ Value("data").Object().
+ Value("projects").Object().
+ Value("edges").Array()
+
+ edges.Length().Equal(3)
+ edges.Element(0).Object().Value("node").Object().Value("name").Equal("project2-test") // 'project2' is first
+ edges.Element(1).Object().Value("node").Object().Value("name").Equal("project3-test")
+ edges.Element(2).Object().Value("node").Object().Value("name").Equal("project1-test")
+}
diff --git a/server/gql/project.graphql b/server/gql/project.graphql
index 5673d3e052..42a2245082 100644
--- a/server/gql/project.graphql
+++ b/server/gql/project.graphql
@@ -124,6 +124,7 @@ type ProjectEdge {
extend type Query{
projects(teamId: ID!, includeArchived: Boolean, pagination: Pagination, keyword: String, sort: ProjectSort): ProjectConnection!
checkProjectAlias(alias: String!): ProjectAliasAvailability!
+ starredProjects(teamId: ID!): ProjectConnection!
}
extend type Mutation {
diff --git a/server/gql/workspace.graphql b/server/gql/workspace.graphql
index db61e612ed..fa0c64beaf 100644
--- a/server/gql/workspace.graphql
+++ b/server/gql/workspace.graphql
@@ -25,6 +25,9 @@ type Policy {
assetStorageSize: FileSize
datasetSchemaCount: Int
datasetCount: Int
+ nlsLayersCount: Int
+ pageCount: Int
+ blocksCount: Int
}
enum Role {
diff --git a/server/internal/adapter/gql/generated.go b/server/internal/adapter/gql/generated.go
index 8cb40e6b09..eb21021dfc 100644
--- a/server/internal/adapter/gql/generated.go
+++ b/server/internal/adapter/gql/generated.go
@@ -803,12 +803,15 @@ type ComplexityRoot struct {
Policy struct {
AssetStorageSize func(childComplexity int) int
+ BlocksCount func(childComplexity int) int
DatasetCount func(childComplexity int) int
DatasetSchemaCount func(childComplexity int) int
ID func(childComplexity int) int
LayerCount func(childComplexity int) int
MemberCount func(childComplexity int) int
Name func(childComplexity int) int
+ NlsLayersCount func(childComplexity int) int
+ PageCount func(childComplexity int) int
ProjectCount func(childComplexity int) int
PublishedProjectCount func(childComplexity int) int
}
@@ -1007,6 +1010,7 @@ type ComplexityRoot struct {
PropertySchemas func(childComplexity int, id []gqlmodel.ID) int
Scene func(childComplexity int, projectID gqlmodel.ID) int
SearchUser func(childComplexity int, nameOrEmail string) int
+ StarredProjects func(childComplexity int, teamID gqlmodel.ID) int
}
Rect struct {
@@ -1686,6 +1690,7 @@ type QueryResolver interface {
Plugins(ctx context.Context, id []gqlmodel.ID) ([]*gqlmodel.Plugin, error)
Projects(ctx context.Context, teamID gqlmodel.ID, includeArchived *bool, pagination *gqlmodel.Pagination, keyword *string, sort *gqlmodel.ProjectSort) (*gqlmodel.ProjectConnection, error)
CheckProjectAlias(ctx context.Context, alias string) (*gqlmodel.ProjectAliasAvailability, error)
+ StarredProjects(ctx context.Context, teamID gqlmodel.ID) (*gqlmodel.ProjectConnection, error)
PropertySchema(ctx context.Context, id gqlmodel.ID) (*gqlmodel.PropertySchema, error)
PropertySchemas(ctx context.Context, id []gqlmodel.ID) ([]*gqlmodel.PropertySchema, error)
Scene(ctx context.Context, projectID gqlmodel.ID) (*gqlmodel.Scene, error)
@@ -5526,6 +5531,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Policy.AssetStorageSize(childComplexity), true
+ case "Policy.blocksCount":
+ if e.complexity.Policy.BlocksCount == nil {
+ break
+ }
+
+ return e.complexity.Policy.BlocksCount(childComplexity), true
+
case "Policy.datasetCount":
if e.complexity.Policy.DatasetCount == nil {
break
@@ -5568,6 +5580,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Policy.Name(childComplexity), true
+ case "Policy.nlsLayersCount":
+ if e.complexity.Policy.NlsLayersCount == nil {
+ break
+ }
+
+ return e.complexity.Policy.NlsLayersCount(childComplexity), true
+
+ case "Policy.pageCount":
+ if e.complexity.Policy.PageCount == nil {
+ break
+ }
+
+ return e.complexity.Policy.PageCount(childComplexity), true
+
case "Policy.projectCount":
if e.complexity.Policy.ProjectCount == nil {
break
@@ -6624,6 +6650,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.SearchUser(childComplexity, args["nameOrEmail"].(string)), true
+ case "Query.starredProjects":
+ if e.complexity.Query.StarredProjects == nil {
+ break
+ }
+
+ args, err := ec.field_Query_starredProjects_args(context.TODO(), rawArgs)
+ if err != nil {
+ return 0, false
+ }
+
+ return e.complexity.Query.StarredProjects(childComplexity, args["teamId"].(gqlmodel.ID)), true
+
case "Rect.east":
if e.complexity.Rect.East == nil {
break
@@ -9529,6 +9567,7 @@ type ProjectEdge {
extend type Query{
projects(teamId: ID!, includeArchived: Boolean, pagination: Pagination, keyword: String, sort: ProjectSort): ProjectConnection!
checkProjectAlias(alias: String!): ProjectAliasAvailability!
+ starredProjects(teamId: ID!): ProjectConnection!
}
extend type Mutation {
@@ -10586,6 +10625,9 @@ type Policy {
assetStorageSize: FileSize
datasetSchemaCount: Int
datasetCount: Int
+ nlsLayersCount: Int
+ pageCount: Int
+ blocksCount: Int
}
enum Role {
@@ -12670,6 +12712,21 @@ func (ec *executionContext) field_Query_searchUser_args(ctx context.Context, raw
return args, nil
}
+func (ec *executionContext) field_Query_starredProjects_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
+ var err error
+ args := map[string]interface{}{}
+ var arg0 gqlmodel.ID
+ if tmp, ok := rawArgs["teamId"]; ok {
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("teamId"))
+ arg0, err = ec.unmarshalNID2githubᚗcomᚋreearthᚋreearthᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐID(ctx, tmp)
+ if err != nil {
+ return nil, err
+ }
+ }
+ args["teamId"] = arg0
+ return args, nil
+}
+
func (ec *executionContext) field_Scene_datasetSchemas_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -38120,6 +38177,129 @@ func (ec *executionContext) fieldContext_Policy_datasetCount(ctx context.Context
return fc, nil
}
+func (ec *executionContext) _Policy_nlsLayersCount(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.Policy) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_Policy_nlsLayersCount(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.NlsLayersCount, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(*int)
+ fc.Result = res
+ return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Policy_nlsLayersCount(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Policy",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Int does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Policy_pageCount(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.Policy) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_Policy_pageCount(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.PageCount, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(*int)
+ fc.Result = res
+ return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Policy_pageCount(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Policy",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Int does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Policy_blocksCount(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.Policy) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_Policy_blocksCount(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.BlocksCount, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(*int)
+ fc.Result = res
+ return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Policy_blocksCount(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Policy",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Int does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _Polygon_type(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.Polygon) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Polygon_type(ctx, field)
if err != nil {
@@ -44779,6 +44959,71 @@ func (ec *executionContext) fieldContext_Query_checkProjectAlias(ctx context.Con
return fc, nil
}
+func (ec *executionContext) _Query_starredProjects(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_Query_starredProjects(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 ec.resolvers.Query().StarredProjects(rctx, fc.Args["teamId"].(gqlmodel.ID))
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(*gqlmodel.ProjectConnection)
+ fc.Result = res
+ return ec.marshalNProjectConnection2ᚖgithubᚗcomᚋreearthᚋreearthᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐProjectConnection(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Query_starredProjects(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Query",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "edges":
+ return ec.fieldContext_ProjectConnection_edges(ctx, field)
+ case "nodes":
+ return ec.fieldContext_ProjectConnection_nodes(ctx, field)
+ case "pageInfo":
+ return ec.fieldContext_ProjectConnection_pageInfo(ctx, field)
+ case "totalCount":
+ return ec.fieldContext_ProjectConnection_totalCount(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type ProjectConnection", field.Name)
+ },
+ }
+ defer func() {
+ if r := recover(); r != nil {
+ err = ec.Recover(ctx, r)
+ ec.Error(ctx, err)
+ }
+ }()
+ ctx = graphql.WithFieldContext(ctx, fc)
+ if fc.Args, err = ec.field_Query_starredProjects_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+ ec.Error(ctx, err)
+ return fc, err
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _Query_propertySchema(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query_propertySchema(ctx, field)
if err != nil {
@@ -52850,6 +53095,12 @@ func (ec *executionContext) fieldContext_Team_policy(ctx context.Context, field
return ec.fieldContext_Policy_datasetSchemaCount(ctx, field)
case "datasetCount":
return ec.fieldContext_Policy_datasetCount(ctx, field)
+ case "nlsLayersCount":
+ return ec.fieldContext_Policy_nlsLayersCount(ctx, field)
+ case "pageCount":
+ return ec.fieldContext_Policy_pageCount(ctx, field)
+ case "blocksCount":
+ return ec.fieldContext_Policy_blocksCount(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Policy", field.Name)
},
@@ -70073,6 +70324,12 @@ func (ec *executionContext) _Policy(ctx context.Context, sel ast.SelectionSet, o
out.Values[i] = ec._Policy_datasetSchemaCount(ctx, field, obj)
case "datasetCount":
out.Values[i] = ec._Policy_datasetCount(ctx, field, obj)
+ case "nlsLayersCount":
+ out.Values[i] = ec._Policy_nlsLayersCount(ctx, field, obj)
+ case "pageCount":
+ out.Values[i] = ec._Policy_pageCount(ctx, field, obj)
+ case "blocksCount":
+ out.Values[i] = ec._Policy_blocksCount(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -72201,6 +72458,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
}
+ out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
+ case "starredProjects":
+ field := field
+
+ innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ }
+ }()
+ res = ec._Query_starredProjects(ctx, field)
+ if res == graphql.Null {
+ atomic.AddUint32(&fs.Invalids, 1)
+ }
+ return res
+ }
+
+ rrm := func(ctx context.Context) graphql.Marshaler {
+ return ec.OperationContext.RootResolverMiddleware(ctx,
+ func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
+ }
+
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
case "propertySchema":
field := field
diff --git a/server/internal/adapter/gql/gqlmodel/convert_project.go b/server/internal/adapter/gql/gqlmodel/convert_project.go
index ae6d8d0c1f..01500b4b70 100644
--- a/server/internal/adapter/gql/gqlmodel/convert_project.go
+++ b/server/internal/adapter/gql/gqlmodel/convert_project.go
@@ -77,7 +77,7 @@ func ProjectSortTypeFrom(pst *ProjectSort) *project.SortType {
case ProjectSortFieldCreatedat:
key = "id"
case ProjectSortFieldUpdatedat:
- key = "updatedAt"
+ key = "updatedat"
case ProjectSortFieldName:
key = "name"
default:
diff --git a/server/internal/adapter/gql/gqlmodel/convert_workspace.go b/server/internal/adapter/gql/gqlmodel/convert_workspace.go
index 5b61e48f2a..c008a15c66 100644
--- a/server/internal/adapter/gql/gqlmodel/convert_workspace.go
+++ b/server/internal/adapter/gql/gqlmodel/convert_workspace.go
@@ -72,5 +72,8 @@ func ToPolicy(p *policy.Policy) *Policy {
AssetStorageSize: o.AssetStorageSize,
DatasetSchemaCount: o.DatasetSchemaCount,
DatasetCount: o.DatasetCount,
+ NlsLayersCount: o.NLSLayersCount,
+ PageCount: o.PageCount,
+ BlocksCount: o.BlocksCount,
}
}
diff --git a/server/internal/adapter/gql/gqlmodel/convert_workspace_test.go b/server/internal/adapter/gql/gqlmodel/convert_workspace_test.go
index db73784b27..587a66239a 100644
--- a/server/internal/adapter/gql/gqlmodel/convert_workspace_test.go
+++ b/server/internal/adapter/gql/gqlmodel/convert_workspace_test.go
@@ -36,6 +36,9 @@ func TestToPolicy(t *testing.T) {
AssetStorageSize: lo.ToPtr(int64(5)),
DatasetCount: lo.ToPtr(6),
DatasetSchemaCount: lo.ToPtr(7),
+ NlsLayersCount: lo.ToPtr(8),
+ PageCount: lo.ToPtr(9),
+ BlocksCount: lo.ToPtr(6),
}, ToPolicy(policy.New(policy.Option{
ID: policy.ID("x"),
Name: "aaa",
@@ -46,6 +49,9 @@ func TestToPolicy(t *testing.T) {
AssetStorageSize: lo.ToPtr(int64(5)),
DatasetCount: lo.ToPtr(6),
DatasetSchemaCount: lo.ToPtr(7),
+ NLSLayersCount: lo.ToPtr(8),
+ PageCount: lo.ToPtr(9),
+ BlocksCount: lo.ToPtr(6),
})))
assert.Nil(t, ToPolicy(nil))
}
diff --git a/server/internal/adapter/gql/gqlmodel/models_gen.go b/server/internal/adapter/gql/gqlmodel/models_gen.go
index a8b7dbf80e..912e509544 100644
--- a/server/internal/adapter/gql/gqlmodel/models_gen.go
+++ b/server/internal/adapter/gql/gqlmodel/models_gen.go
@@ -1120,6 +1120,9 @@ type Policy struct {
AssetStorageSize *int64 `json:"assetStorageSize,omitempty"`
DatasetSchemaCount *int `json:"datasetSchemaCount,omitempty"`
DatasetCount *int `json:"datasetCount,omitempty"`
+ NlsLayersCount *int `json:"nlsLayersCount,omitempty"`
+ PageCount *int `json:"pageCount,omitempty"`
+ BlocksCount *int `json:"blocksCount,omitempty"`
}
type Polygon struct {
diff --git a/server/internal/adapter/gql/loader_project.go b/server/internal/adapter/gql/loader_project.go
index 8ee2684464..8b0c340802 100644
--- a/server/internal/adapter/gql/loader_project.go
+++ b/server/internal/adapter/gql/loader_project.go
@@ -79,6 +79,28 @@ func (c *ProjectLoader) CheckAlias(ctx context.Context, alias string) (*gqlmodel
return &gqlmodel.ProjectAliasAvailability{Alias: alias, Available: ok}, nil
}
+func (c *ProjectLoader) FindStarredByWorkspace(ctx context.Context, wsID gqlmodel.ID) (*gqlmodel.ProjectConnection, error) {
+ tid, err := gqlmodel.ToID[accountdomain.Workspace](wsID)
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := c.usecase.FindStarredByWorkspace(ctx, tid, getOperator(ctx))
+ if err != nil {
+ return nil, err
+ }
+
+ nodes := make([]*gqlmodel.Project, 0, len(res))
+ for _, p := range res {
+ nodes = append(nodes, gqlmodel.ToProject(p))
+ }
+
+ return &gqlmodel.ProjectConnection{
+ Nodes: nodes,
+ TotalCount: len(nodes),
+ }, nil
+}
+
// data loaders
type ProjectDataLoader interface {
diff --git a/server/internal/adapter/gql/resolver_query.go b/server/internal/adapter/gql/resolver_query.go
index a7a4a5885d..2ed6d91967 100644
--- a/server/internal/adapter/gql/resolver_query.go
+++ b/server/internal/adapter/gql/resolver_query.go
@@ -255,3 +255,7 @@ func (r *queryResolver) SearchUser(ctx context.Context, nameOrEmail string) (*gq
func (r *queryResolver) CheckProjectAlias(ctx context.Context, alias string) (*gqlmodel.ProjectAliasAvailability, error) {
return loaders(ctx).Project.CheckAlias(ctx, alias)
}
+
+func (r *queryResolver) StarredProjects(ctx context.Context, teamId gqlmodel.ID) (*gqlmodel.ProjectConnection, error) {
+ return loaders(ctx).Project.FindStarredByWorkspace(ctx, teamId)
+}
diff --git a/server/internal/infrastructure/memory/nlslayer.go b/server/internal/infrastructure/memory/nlslayer.go
index 37bcc93e93..51e95c9e66 100644
--- a/server/internal/infrastructure/memory/nlslayer.go
+++ b/server/internal/infrastructure/memory/nlslayer.go
@@ -127,6 +127,24 @@ func (r *NLSLayer) FindNLSLayerSimpleByID(ctx context.Context, id id.NLSLayerID)
return nil, rerror.ErrNotFound
}
+func (r *NLSLayer) CountByScene(_ context.Context, sid id.SceneID) (n int, _ error) {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ if !r.f.CanRead(sid) {
+ return
+ }
+
+ for _, d := range r.data {
+ if d.Scene() == sid {
+ if lg := nlslayer.ToNLSLayerGroup(d); lg == nil && !lg.IsRoot() {
+ n++
+ }
+ }
+ }
+ return
+}
+
func (r *NLSLayer) Save(ctx context.Context, l nlslayer.NLSLayer) error {
if !r.f.CanWrite(l.Scene()) {
return repo.ErrOperationDenied
diff --git a/server/internal/infrastructure/memory/project.go b/server/internal/infrastructure/memory/project.go
index b4da73f168..13688864f3 100644
--- a/server/internal/infrastructure/memory/project.go
+++ b/server/internal/infrastructure/memory/project.go
@@ -83,6 +83,28 @@ func (r *Project) FindByWorkspace(ctx context.Context, id accountdomain.Workspac
), nil
}
+func (r *Project) FindStarredByWorkspace(ctx context.Context, id accountdomain.WorkspaceID) ([]*project.Project, error) {
+ r.lock.Lock()
+ defer r.lock.Unlock()
+
+ if !r.f.CanRead(id) {
+ return nil, nil
+ }
+
+ var result []*project.Project
+ for _, p := range r.data {
+ if p.Workspace() == id && p.Starred() {
+ result = append(result, p)
+ }
+ }
+
+ sort.Slice(result, func(i, j int) bool {
+ return result[i].UpdatedAt().After(result[j].UpdatedAt())
+ })
+
+ return result, nil
+}
+
func (r *Project) FindIDsByWorkspace(ctx context.Context, id accountdomain.WorkspaceID) (res []project.ID, _ error) {
r.lock.Lock()
defer r.lock.Unlock()
diff --git a/server/internal/infrastructure/mongo/migration/migrations.go b/server/internal/infrastructure/mongo/migration/migrations.go
index e6ccbc8e35..41741bbf98 100644
--- a/server/internal/infrastructure/mongo/migration/migrations.go
+++ b/server/internal/infrastructure/mongo/migration/migrations.go
@@ -10,11 +10,11 @@ import "github.com/reearth/reearthx/usecasex/migration"
// If the migration takes too long, the deployment may fail in a serverless environment.
// Set the batch size to as large a value as possible without using up the RAM of the deployment destination.
var migrations = migration.Migrations[DBClient]{
- 201217132559: AddSceneWidgetId,
- 201217193948: AddSceneDefaultTile,
- 210310145844: RemovePreviewToken,
- 210730175108: AddSceneAlignSystem,
- 220214180713: SplitSchemaOfProperties,
- 220309174648: AddSceneFieldToPropertySchema,
- 221028204300: MoveTerrainProperties,
+ 201217132559: AddSceneWidgetId,
+ 201217193948: AddSceneDefaultTile,
+ 210310145844: RemovePreviewToken,
+ 210730175108: AddSceneAlignSystem,
+ 220214180713: SplitSchemaOfProperties,
+ 220309174648: AddSceneFieldToPropertySchema,
+ 221028204300: MoveTerrainProperties,
}
diff --git a/server/internal/infrastructure/mongo/mongodoc/config.go b/server/internal/infrastructure/mongo/mongodoc/config.go
index b12f0755e7..ac73560322 100644
--- a/server/internal/infrastructure/mongo/mongodoc/config.go
+++ b/server/internal/infrastructure/mongo/mongodoc/config.go
@@ -18,8 +18,9 @@ type Auth struct {
func NewConfig(c config.Config) ConfigDocument {
return ConfigDocument{
- Migration: c.Migration,
- Auth: NewConfigAuth(c.Auth),
+ Migration: c.Migration,
+ Auth: NewConfigAuth(c.Auth),
+ DefaultPolicy: c.DefaultPolicy,
}
}
@@ -39,7 +40,8 @@ func (c *ConfigDocument) Model() *config.Config {
}
cfg := &config.Config{
- Migration: c.Migration,
+ Migration: c.Migration,
+ DefaultPolicy: c.DefaultPolicy,
}
if c.Auth != nil {
diff --git a/server/internal/infrastructure/mongo/mongodoc/policy.go b/server/internal/infrastructure/mongo/mongodoc/policy.go
index 346bd7d590..838e9a926e 100644
--- a/server/internal/infrastructure/mongo/mongodoc/policy.go
+++ b/server/internal/infrastructure/mongo/mongodoc/policy.go
@@ -15,6 +15,9 @@ type PolicyDocument struct {
DatasetCount *int
DatasetSchemaCount *int
AssetStorageSize *int64
+ NLSLayersCount *int
+ PageCount *int
+ BlocksCount *int
}
func (d PolicyDocument) Model() *policy.Policy {
@@ -28,6 +31,9 @@ func (d PolicyDocument) Model() *policy.Policy {
DatasetCount: d.DatasetCount,
DatasetSchemaCount: d.DatasetSchemaCount,
AssetStorageSize: d.AssetStorageSize,
+ NLSLayersCount: d.NLSLayersCount,
+ PageCount: d.PageCount,
+ BlocksCount: d.BlocksCount,
})
}
diff --git a/server/internal/infrastructure/mongo/nlslayer.go b/server/internal/infrastructure/mongo/nlslayer.go
index 73c7281bef..4203b6434f 100644
--- a/server/internal/infrastructure/mongo/nlslayer.go
+++ b/server/internal/infrastructure/mongo/nlslayer.go
@@ -136,6 +136,18 @@ func (r *NLSLayer) FindByScene(ctx context.Context, id id.SceneID) (nlslayer.NLS
})
}
+func (r *NLSLayer) CountByScene(ctx context.Context, sid id.SceneID) (int, error) {
+ if !r.f.CanRead(sid) {
+ return 0, repo.ErrOperationDenied
+ }
+
+ c, err := r.client.Count(ctx, bson.M{
+ "scene": sid.String(),
+ "group.root": bson.M{"$ne": true},
+ })
+ return int(c), err
+}
+
func (r *NLSLayer) Save(ctx context.Context, layer nlslayer.NLSLayer) error {
if !r.f.CanWrite(layer.Scene()) {
return repo.ErrOperationDenied
diff --git a/server/internal/infrastructure/mongo/project.go b/server/internal/infrastructure/mongo/project.go
index 6822048b0b..cda562ed01 100644
--- a/server/internal/infrastructure/mongo/project.go
+++ b/server/internal/infrastructure/mongo/project.go
@@ -96,6 +96,19 @@ func (r *Project) FindByWorkspace(ctx context.Context, id accountdomain.Workspac
return r.paginate(ctx, filter, uFilter.Sort, uFilter.Pagination)
}
+func (r *Project) FindStarredByWorkspace(ctx context.Context, id accountdomain.WorkspaceID) ([]*project.Project, error) {
+ if !r.f.CanRead(id) {
+ return nil, repo.ErrOperationDenied
+ }
+
+ filter := bson.M{
+ "team": id.String(),
+ "starred": true,
+ }
+
+ return r.find(ctx, filter)
+}
+
func (r *Project) FindByPublicName(ctx context.Context, name string) (*project.Project, error) {
if name == "" {
return nil, rerror.ErrNotFound
diff --git a/server/internal/infrastructure/mongo/project_test.go b/server/internal/infrastructure/mongo/project_test.go
index e3eb604737..254108b3ce 100644
--- a/server/internal/infrastructure/mongo/project_test.go
+++ b/server/internal/infrastructure/mongo/project_test.go
@@ -139,3 +139,55 @@ func TestProject_FindByPublicName(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, prj1, got)
}
+
+func TestProject_FindStarredByWorkspace(t *testing.T) {
+ c := mongotest.Connect(t)(t)
+ ctx := context.Background()
+
+ wid := accountdomain.NewWorkspaceID()
+ wid2 := accountdomain.NewWorkspaceID()
+
+ pid1 := id.NewProjectID()
+ pid2 := id.NewProjectID()
+ pid3 := id.NewProjectID()
+ pid4 := id.NewProjectID()
+
+ _, _ = c.Collection("project").InsertMany(ctx, []any{
+ bson.M{"id": pid1.String(), "team": wid.String(), "name": "Project 1", "starred": true},
+ bson.M{"id": pid2.String(), "team": wid.String(), "name": "Project 2", "starred": true},
+ bson.M{"id": pid3.String(), "team": wid.String(), "name": "Project 3", "starred": false},
+ bson.M{"id": pid4.String(), "team": wid2.String(), "name": "Project 4", "starred": true},
+ })
+
+ r := NewProject(mongox.NewClientWithDatabase(c))
+
+ t.Run("FindStarredByWorkspace", func(t *testing.T) {
+ got, err := r.FindStarredByWorkspace(ctx, wid)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, len(got))
+ assert.ElementsMatch(t, []id.ProjectID{pid1, pid2}, []id.ProjectID{got[0].ID(), got[1].ID()})
+ })
+
+ t.Run("FindStarredByWorkspace with workspace filter", func(t *testing.T) {
+ r2 := r.Filtered(repo.WorkspaceFilter{
+ Readable: accountdomain.WorkspaceIDList{wid2},
+ })
+ got, err := r2.FindStarredByWorkspace(ctx, wid)
+ assert.Equal(t, repo.ErrOperationDenied, err)
+ assert.Nil(t, got)
+ })
+
+ t.Run("FindStarredByWorkspace with different workspace", func(t *testing.T) {
+ got, err := r.FindStarredByWorkspace(ctx, wid2)
+ assert.NoError(t, err)
+ assert.Equal(t, 1, len(got))
+ assert.Equal(t, pid4, got[0].ID())
+ })
+
+ t.Run("FindStarredByWorkspace with workspace having no starred projects", func(t *testing.T) {
+ emptyWid := accountdomain.NewWorkspaceID()
+ got, err := r.FindStarredByWorkspace(ctx, emptyWid)
+ assert.NoError(t, err)
+ assert.Empty(t, got)
+ })
+}
diff --git a/server/internal/usecase/interactor/nlslayer.go b/server/internal/usecase/interactor/nlslayer.go
index 689c10341a..5b0d7c43b6 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/reearthx/account/accountusecase/accountrepo"
"github.com/reearth/reearthx/rerror"
"github.com/reearth/reearthx/usecasex"
)
@@ -26,6 +27,8 @@ type NLSLayer struct {
sceneRepo repo.Scene
propertyRepo repo.Property
pluginRepo repo.Plugin
+ policyRepo repo.Policy
+ workspaceRepo accountrepo.Workspace
transaction usecasex.Transaction
}
@@ -38,6 +41,8 @@ func NewNLSLayer(r *repo.Container) interfaces.NLSLayer {
sceneRepo: r.Scene,
propertyRepo: r.Property,
pluginRepo: r.Plugin,
+ policyRepo: r.Policy,
+ workspaceRepo: r.Workspace,
transaction: r.Transaction,
}
}
@@ -87,6 +92,30 @@ func (i *NLSLayer) AddLayerSimple(ctx context.Context, inp interfaces.AddNLSLaye
return nil, err
}
+ s, err := i.sceneRepo.FindByID(ctx, inp.SceneID)
+ if err != nil {
+ return nil, err
+ }
+
+ ws, err := i.workspaceRepo.FindByID(ctx, s.Workspace())
+ if err != nil {
+ return nil, err
+ }
+
+ if policyID := operator.Policy(ws.Policy()); policyID != nil {
+ p, err := i.policyRepo.FindByID(ctx, *policyID)
+ if err != nil {
+ return nil, err
+ }
+ s, err := i.nlslayerRepo.CountByScene(ctx, s.ID())
+ if err != nil {
+ return nil, err
+ }
+ if err := p.EnforceNLSLayersCount(s + 1); err != nil {
+ return nil, err
+ }
+ }
+
if inp.Schema != nil {
featureCollection := nlslayer.NewFeatureCollection(
"FeatureCollection",
diff --git a/server/internal/usecase/interactor/project.go b/server/internal/usecase/interactor/project.go
index 03befee355..d42c60a4cf 100644
--- a/server/internal/usecase/interactor/project.go
+++ b/server/internal/usecase/interactor/project.go
@@ -73,6 +73,10 @@ func (i *Project) FindByWorkspace(ctx context.Context, id accountdomain.Workspac
})
}
+func (i *Project) FindStarredByWorkspace(ctx context.Context, id accountdomain.WorkspaceID, operator *usecase.Operator) ([]*project.Project, error) {
+ return i.projectRepo.FindStarredByWorkspace(ctx, id)
+}
+
func (i *Project) Create(ctx context.Context, p interfaces.CreateProjectParam, operator *usecase.Operator) (_ *project.Project, err error) {
if err := i.CanWriteWorkspace(p.WorkspaceID, operator); err != nil {
return nil, err
diff --git a/server/internal/usecase/interactor/storytelling.go b/server/internal/usecase/interactor/storytelling.go
index b3a3b09809..4424644b0f 100644
--- a/server/internal/usecase/interactor/storytelling.go
+++ b/server/internal/usecase/interactor/storytelling.go
@@ -464,6 +464,28 @@ func (i *Storytelling) CreatePage(ctx context.Context, inp interfaces.CreatePage
return nil, nil, err
}
+ s, err := i.sceneRepo.FindByID(ctx, inp.SceneID)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ws, err := i.workspaceRepo.FindByID(ctx, s.Workspace())
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if policyID := op.Policy(ws.Policy()); policyID != nil {
+ p, err := i.policyRepo.FindByID(ctx, *policyID)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var pageCount = len(story.Pages().Pages())
+ if err := p.EnforcePageCount(pageCount + 1); err != nil {
+ return nil, nil, err
+ }
+ }
+
story.Pages().AddAt(page, inp.Index)
if err = i.propertyRepo.Save(ctx, prop); err != nil {
@@ -789,6 +811,38 @@ func (i *Storytelling) CreateBlock(ctx context.Context, inp interfaces.CreateBlo
return nil, nil, nil, -1, err
}
+ s, err := i.sceneRepo.FindByID(ctx, story.Scene())
+ if err != nil {
+ return nil, nil, nil, -1, err
+ }
+
+ ws, err := i.workspaceRepo.FindByID(ctx, s.Workspace())
+ if err != nil {
+ return nil, nil, nil, -1, err
+ }
+
+ if policyID := op.Policy(ws.Policy()); policyID != nil {
+ p, err := i.policyRepo.FindByID(ctx, *policyID)
+ if err != nil {
+ return nil, nil, nil, -1, err
+ }
+
+ story, err := i.storytellingRepo.FindByID(ctx, inp.StoryID)
+ if err != nil {
+ return nil, nil, nil, -1, err
+ }
+
+ page := story.Pages().Page(inp.PageID)
+ if page == nil {
+ return nil, nil, nil, -1, interfaces.ErrPageNotFound
+ }
+
+ var s = page.Count()
+ if err := p.EnforceBlocksCount(s + 1); err != nil {
+ return nil, nil, nil, -1, err
+ }
+ }
+
_, extension, err := i.getPlugin(ctx, &inp.PluginID, &inp.ExtensionID)
if err != nil {
return nil, nil, nil, -1, err
diff --git a/server/internal/usecase/interfaces/project.go b/server/internal/usecase/interfaces/project.go
index 0f0b9a338f..7bfc578f3a 100644
--- a/server/internal/usecase/interfaces/project.go
+++ b/server/internal/usecase/interfaces/project.go
@@ -60,6 +60,7 @@ var (
type Project interface {
Fetch(context.Context, []id.ProjectID, *usecase.Operator) ([]*project.Project, error)
FindByWorkspace(context.Context, accountdomain.WorkspaceID, *string, *project.SortType, *usecasex.Pagination, *usecase.Operator) ([]*project.Project, *usecasex.PageInfo, error)
+ FindStarredByWorkspace(context.Context, accountdomain.WorkspaceID, *usecase.Operator) ([]*project.Project, error)
Create(context.Context, CreateProjectParam, *usecase.Operator) (*project.Project, error)
Update(context.Context, UpdateProjectParam, *usecase.Operator) (*project.Project, error)
Publish(context.Context, PublishProjectParam, *usecase.Operator) (*project.Project, error)
diff --git a/server/internal/usecase/repo/nlslayer.go b/server/internal/usecase/repo/nlslayer.go
index 5fca222601..44ca883ab0 100644
--- a/server/internal/usecase/repo/nlslayer.go
+++ b/server/internal/usecase/repo/nlslayer.go
@@ -18,6 +18,7 @@ type NLSLayer interface {
FindParentByID(context.Context, id.NLSLayerID) (*nlslayer.NLSLayerGroup, error)
FindParentsByIDs(context.Context, id.NLSLayerIDList) (nlslayer.NLSLayerGroupList, error)
FindByScene(context.Context, id.SceneID) (nlslayer.NLSLayerList, error)
+ CountByScene(context.Context, id.SceneID) (int, error)
Save(context.Context, nlslayer.NLSLayer) error
SaveAll(context.Context, nlslayer.NLSLayerList) error
Remove(context.Context, id.NLSLayerID) error
diff --git a/server/internal/usecase/repo/project.go b/server/internal/usecase/repo/project.go
index f72c44babd..7ba88d180d 100644
--- a/server/internal/usecase/repo/project.go
+++ b/server/internal/usecase/repo/project.go
@@ -22,6 +22,7 @@ type Project interface {
FindByID(context.Context, id.ProjectID) (*project.Project, error)
FindByScene(context.Context, id.SceneID) (*project.Project, error)
FindByWorkspace(context.Context, accountdomain.WorkspaceID, ProjectFilter) ([]*project.Project, *usecasex.PageInfo, error)
+ FindStarredByWorkspace(context.Context, accountdomain.WorkspaceID) ([]*project.Project, error)
FindByPublicName(context.Context, string) (*project.Project, error)
CountByWorkspace(context.Context, accountdomain.WorkspaceID) (int, error)
CountPublicByWorkspace(context.Context, accountdomain.WorkspaceID) (int, error)
diff --git a/server/pkg/builtin/manifest.yml b/server/pkg/builtin/manifest.yml
index c37e6e02a5..6bcf6d0865 100644
--- a/server/pkg/builtin/manifest.yml
+++ b/server/pkg/builtin/manifest.yml
@@ -2709,3 +2709,45 @@ extensions:
field: displayType
type: string
value: custom
+ - id: videoInfoboxBetaBlock
+ name: Video
+ type: infoboxBlock
+ description: Infobox Video Block
+ schema:
+ groups:
+ - id: panel
+ title: Panel Setting
+ fields:
+ - id: padding
+ type: spacing
+ title: Padding
+ ui: padding
+ min: 0
+ max: 100
+ - id: default
+ title: Video Block
+ fields:
+ - id: src
+ type: string
+ title: Video
+ - id: markdownInfoboxBetaBlock
+ name: MD Text
+ type: infoboxBlock
+ description: Infobox MD Text Block
+ schema:
+ groups:
+ - id: panel
+ title: Panel Setting
+ fields:
+ - id: padding
+ type: spacing
+ title: Padding
+ ui: padding
+ min: 0
+ max: 100
+ - id: default
+ title: MD Text Block
+ fields:
+ - id: src
+ type: string
+ title: Content
diff --git a/server/pkg/builtin/manifest_ja.yml b/server/pkg/builtin/manifest_ja.yml
index 7c54b474e2..cd6bfaf32c 100644
--- a/server/pkg/builtin/manifest_ja.yml
+++ b/server/pkg/builtin/manifest_ja.yml
@@ -1347,6 +1347,34 @@ extensions:
custom: カスタム
property_list:
title: プロパティリスト
+ panel:
+ title: パネル設定
+ fields:
+ padding:
+ title: 余白
+ videoInfoboxBetaBlock:
+ name: ビデオ
+ description: インフォボックスビデオブロック
+ propertySchema:
+ default:
+ title: ビデオブロック
+ fields:
+ src:
+ title: ビデオ
+ panel:
+ title: パネル設定
+ fields:
+ padding:
+ title: 余白
+ markdownInfoboxBetaBlock:
+ name: MDテキスト
+ description: インフォボックスMDテキストブロック
+ propertySchema:
+ default:
+ title: MDテキストブロック
+ fields:
+ src:
+ title: コンテンツ
panel:
title: パネル設定
fields:
diff --git a/server/pkg/config/config.go b/server/pkg/config/config.go
index d5745c3176..9c005dc8a1 100644
--- a/server/pkg/config/config.go
+++ b/server/pkg/config/config.go
@@ -2,11 +2,14 @@ package config
import (
"sort"
+
+ "github.com/reearth/reearth/server/pkg/policy"
)
type Config struct {
- Migration int64
- Auth *Auth
+ Migration int64
+ Auth *Auth
+ DefaultPolicy *policy.ID
}
type Auth struct {
diff --git a/server/pkg/policy/policy.go b/server/pkg/policy/policy.go
index eede59af53..17c68c0849 100644
--- a/server/pkg/policy/policy.go
+++ b/server/pkg/policy/policy.go
@@ -25,6 +25,9 @@ type Option struct {
AssetStorageSize *int64
DatasetSchemaCount *int
DatasetCount *int
+ NLSLayersCount *int
+ PageCount *int
+ BlocksCount *int
}
func New(opts Option) *Policy {
@@ -71,6 +74,18 @@ func (p *Policy) EnforceDatasetCount(count int) error {
return p.error(p == nil || p.opts.DatasetCount == nil || *p.opts.DatasetCount >= count)
}
+func (p *Policy) EnforceNLSLayersCount(count int) error {
+ return p.error(p == nil || p.opts.NLSLayersCount == nil || *p.opts.NLSLayersCount >= count)
+}
+
+func (p *Policy) EnforcePageCount(count int) error {
+ return p.error(p == nil || p.opts.PageCount == nil || *p.opts.PageCount >= count)
+}
+
+func (p *Policy) EnforceBlocksCount(count int) error {
+ return p.error(p == nil || p.opts.BlocksCount == nil || *p.opts.BlocksCount >= count)
+}
+
func (*Policy) error(ok bool) error {
if !ok {
return ErrPolicyViolation
@@ -96,5 +111,8 @@ func (p Option) Clone() Option {
AssetStorageSize: util.CloneRef(p.AssetStorageSize),
DatasetSchemaCount: util.CloneRef(p.DatasetSchemaCount),
DatasetCount: util.CloneRef(p.DatasetCount),
+ NLSLayersCount: util.CloneRef(p.NLSLayersCount),
+ PageCount: util.CloneRef(p.PageCount),
+ BlocksCount: util.CloneRef(p.BlocksCount),
}
}
diff --git a/server/pkg/policy/policy_test.go b/server/pkg/policy/policy_test.go
index 3fe0bcdb54..373865a6f9 100644
--- a/server/pkg/policy/policy_test.go
+++ b/server/pkg/policy/policy_test.go
@@ -150,6 +150,57 @@ func TestPolicy_EnforceDatasetCount(t *testing.T) {
})
}
+func TestPolicy_EnforceNLSLayersCount(t *testing.T) {
+ tests := []policyTest[int]{
+ {limit: 0, arg: 0, fail: false},
+ {limit: 1, arg: 0, fail: false},
+ {limit: 1, arg: 1, fail: false},
+ {limit: 1, arg: 2, fail: true},
+ {limitNil: true, arg: 100, fail: false},
+ {policyNil: true, arg: 100, fail: false},
+ }
+
+ testPolicy(t, tests, func(d int) Option {
+ return Option{NLSLayersCount: lo.ToPtr(d)}
+ }, func(p *Policy, a int) error {
+ return p.EnforceNLSLayersCount(a)
+ })
+}
+
+func TestPolicy_EnforcePageCount(t *testing.T) {
+ tests := []policyTest[int]{
+ {limit: 0, arg: 0, fail: false},
+ {limit: 1, arg: 0, fail: false},
+ {limit: 1, arg: 1, fail: false},
+ {limit: 1, arg: 2, fail: true},
+ {limitNil: true, arg: 100, fail: false},
+ {policyNil: true, arg: 100, fail: false},
+ }
+
+ testPolicy(t, tests, func(d int) Option {
+ return Option{PageCount: lo.ToPtr(d)}
+ }, func(p *Policy, a int) error {
+ return p.EnforcePageCount(a)
+ })
+}
+
+func TestPolicy_EnforceBlocksCount(t *testing.T) {
+ tests := []policyTest[int]{
+ {limit: 0, arg: 0, fail: false},
+ {limit: 1, arg: 0, fail: false},
+ {limit: 1, arg: 1, fail: false},
+ {limit: 1, arg: 2, fail: true},
+ {limitNil: true, arg: 100, fail: false},
+ {policyNil: true, arg: 100, fail: false},
+ }
+
+ testPolicy(t, tests, func(d int) Option {
+ return Option{BlocksCount: lo.ToPtr(d)}
+ }, func(p *Policy, a int) error {
+ return p.EnforceBlocksCount(a)
+ })
+}
+
func testPolicy[T any](t *testing.T, tests []policyTest[T], f func(d T) Option, tf func(p *Policy, a T) error) {
t.Helper()
for _, tt := range tests {
@@ -191,7 +242,10 @@ func TestPolicy_Clone(t *testing.T) {
LayerCount: lo.ToPtr(1),
AssetStorageSize: lo.ToPtr(int64(1)),
DatasetSchemaCount: lo.ToPtr(1),
- DatasetCount: lo.ToPtr(2),
+ DatasetCount: lo.ToPtr(1),
+ NLSLayersCount: lo.ToPtr(1),
+ PageCount: lo.ToPtr(1),
+ BlocksCount: lo.ToPtr(1),
},
}
got := p.Clone()
diff --git a/server/schemas/plugin_manifest.json b/server/schemas/plugin_manifest.json
index bcb2bed9a7..6cdbf013c4 100644
--- a/server/schemas/plugin_manifest.json
+++ b/server/schemas/plugin_manifest.json
@@ -336,7 +336,8 @@
"cluster",
"story",
"storyPage",
- "storyBlock"
+ "storyBlock",
+ "infoboxBlock"
]
},
"singleOnly": {
@@ -454,4 +455,4 @@
}
},
"$ref": "#/definitions/root"
-}
+}
\ No newline at end of file
diff --git a/web/.env.example b/web/.env.example
new file mode 100644
index 0000000000..2ef5020335
--- /dev/null
+++ b/web/.env.example
@@ -0,0 +1,9 @@
+# Required
+REEARTH_WEB_API=http://localhost:8080/api
+REEARTH_WEB_PLUGINS=http://localhost:8080/plugins
+REEARTH_WEB_AUTH0_DOMAIN=
+REEARTH_WEB_AUTH0_AUDIENCE=
+REEARTH_WEB_AUTH0_CLIENT_ID=
+
+# Optional
+REEARTH_WEB_CESIUM_ION_TOKEN_URL=
\ No newline at end of file
diff --git a/web/.eslintignore b/web/.eslintignore
deleted file mode 100644
index 95ced518e0..0000000000
--- a/web/.eslintignore
+++ /dev/null
@@ -1,7 +0,0 @@
-/build
-/dist
-/coverage
-/storybook-static
-!/.storybook
-/.storybook/public
-/src/services/gql/__gen__
\ No newline at end of file
diff --git a/web/.eslintrc.yml b/web/.eslintrc.yml
deleted file mode 100644
index 0102080ce3..0000000000
--- a/web/.eslintrc.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-extends:
- - reearth
-root: true
-rules:
- no-restricted-imports:
- - error
- - paths:
- - name: lodash
- message: Use lodash-es instead.
- import/order:
- - warn
- - pathGroups:
- - pattern: "@reearth/**"
- group: external
- position: after
- pathGroupsExcludedImportTypes: ["builtin"]
- newlines-between: always
- alphabetize:
- order: asc
- caseInsensitive: true
-overrides:
- - files:
- - e2e/**/*
- extends:
- - reearth/node
- - plugin:playwright/playwright-test
- env:
- browser: false
- rules:
- no-restricted-imports:
- - error
- - paths:
- - name: "@playwright/test"
- message: Use @reearth/e2e/utils instead.
- - files:
- - i18next-parser.config.js
- extends:
- - reearth/commonjs
- - files: src/beta/**
- rules:
- "@typescript-eslint/no-explicit-any":
- - warn
diff --git a/web/.gitignore b/web/.gitignore
index 04bcd77583..9662ac8a30 100644
--- a/web/.gitignore
+++ b/web/.gitignore
@@ -9,7 +9,7 @@ node_modules
__screenshots__
/playwright-report
/test-results
-/.env*
+/.env
/reearth-config.json
/cesium_ion_token.txt
.idea/*
diff --git a/web/.huskyrc.json b/web/.huskyrc.json
index 05ea0d92bf..4d077c8292 100644
--- a/web/.huskyrc.json
+++ b/web/.huskyrc.json
@@ -2,4 +2,4 @@
"hooks": {
"pre-commit": "lint-staged"
}
-}
\ No newline at end of file
+}
diff --git a/web/.prettierignore b/web/.prettierignore
index ec6d3cdd7f..d85205ac1d 100644
--- a/web/.prettierignore
+++ b/web/.prettierignore
@@ -1 +1,3 @@
package.json
+**/__gen__/**
+src/services/i18n/translations/*.yml
\ No newline at end of file
diff --git a/web/.prettierrc b/web/.prettierrc
new file mode 100644
index 0000000000..36b356317b
--- /dev/null
+++ b/web/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "trailingComma": "none"
+}
diff --git a/web/.storybook/main.ts b/web/.storybook/main.ts
index 91acbc0d14..77251d12f7 100644
--- a/web/.storybook/main.ts
+++ b/web/.storybook/main.ts
@@ -8,55 +8,55 @@ const config: StorybookConfig = {
addons: ["@storybook/addon-essentials", "@storybook/addon-styling"],
framework: {
name: "@storybook/react-vite",
- options: {},
+ options: {}
},
core: {
- disableTelemetry: true,
+ disableTelemetry: true
},
staticDirs: ["./public"],
viteFinal(config, { configType }) {
return mergeConfig(config, {
define: {
- "process.env.QTS_DEBUG": "false", // quickjs-emscripten
+ "process.env.QTS_DEBUG": "false" // quickjs-emscripten
},
build:
configType === "PRODUCTION"
? {
// https://github.com/storybookjs/builder-vite/issues/409
minify: false,
- sourcemap: false,
+ sourcemap: false
}
: {},
resolve: {
alias: [
{
find: "crypto",
- replacement: "crypto-js",
+ replacement: "crypto-js"
},
{
find: "@reearth/core",
- replacement: resolve(__dirname, "..", "node_modules/@reearth/core"),
+ replacement: resolve(__dirname, "..", "node_modules/@reearth/core")
},
// quickjs-emscripten
{
find: "@reearth",
- replacement: resolve(__dirname, "..", "src"),
- },
- ],
+ replacement: resolve(__dirname, "..", "src")
+ }
+ ]
},
server: {
watch: {
// https://github.com/storybookjs/storybook/issues/22253#issuecomment-1673229400
- ignored: ["**/.env"],
- },
+ ignored: ["**/.env"]
+ }
},
optimizeDeps: {
- exclude: ["storybook"],
- },
+ exclude: ["storybook"]
+ }
});
},
docs: {
- autodocs: true,
- },
+ autodocs: true
+ }
};
module.exports = config;
diff --git a/web/.storybook/manager.ts b/web/.storybook/manager.ts
index 3ea19f8bdb..0376b21922 100644
--- a/web/.storybook/manager.ts
+++ b/web/.storybook/manager.ts
@@ -3,5 +3,5 @@ import { addons } from "@storybook/manager-api";
import theme from "./theme";
addons.setConfig({
- theme,
+ theme
});
diff --git a/web/.storybook/preview-head.html b/web/.storybook/preview-head.html
index bc16b3abc1..c6ce14c850 100644
--- a/web/.storybook/preview-head.html
+++ b/web/.storybook/preview-head.html
@@ -1,7 +1,10 @@
-
\ No newline at end of file
+
diff --git a/web/.storybook/preview.tsx b/web/.storybook/preview.tsx
index 215f8c77d3..7feb977a8a 100644
--- a/web/.storybook/preview.tsx
+++ b/web/.storybook/preview.tsx
@@ -1,10 +1,9 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
import {
ApolloProvider,
ApolloClient,
InMemoryCache,
ApolloLink,
- Observable,
+ Observable
} from "@apollo/client";
import { ThemeProvider } from "@emotion/react";
import { withThemeFromJSXProvider } from "@storybook/addon-styling";
@@ -21,11 +20,11 @@ import theme from "./theme";
const mockClient = new ApolloClient({
link: new ApolloLink(
() =>
- new Observable(observer => {
+ new Observable((observer) => {
observer.complete();
- }),
+ })
),
- cache: new InMemoryCache(),
+ cache: new InMemoryCache()
});
const preview: Preview = {
@@ -33,27 +32,27 @@ const preview: Preview = {
backgrounds: {
values: [
{ name: "Light", value: "lightGrey" },
- { name: "Dark", value: "ash" },
- ],
+ { name: "Dark", value: "ash" }
+ ]
},
layout: "fullscreen",
controls: { expanded: true },
actions: { argTypesRegex: "^on.*" },
docs: {
- theme,
- },
+ theme
+ }
},
decorators: [
withThemeFromJSXProvider({
themes: {
light: lightTheme,
- dark: darkTheme,
+ dark: darkTheme
},
defaultTheme: "dark",
Provider: ThemeProvider,
- GlobalStyles,
+ GlobalStyles
}),
- Story => {
+ (Story) => {
return (
@@ -63,8 +62,8 @@ const preview: Preview = {
);
- },
- ],
+ }
+ ]
};
export default preview;
diff --git a/web/.storybook/public/plugins/block.js b/web/.storybook/public/plugins/block.js
index 75b4e57beb..cf203dd3d1 100644
--- a/web/.storybook/public/plugins/block.js
+++ b/web/.storybook/public/plugins/block.js
@@ -34,6 +34,6 @@ reearth.ui.show(html);
reearth.on("update", () => {
reearth.ui.postMessage(reearth.block);
});
-reearth.on("message", msg => {
+reearth.on("message", (msg) => {
console.log("message received:", msg);
});
diff --git a/web/.storybook/public/sample.geojson b/web/.storybook/public/sample.geojson
index 5593349c59..8a8185bbcf 100644
--- a/web/.storybook/public/sample.geojson
+++ b/web/.storybook/public/sample.geojson
@@ -5,10 +5,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -47.18951431751117,
- -24.789938520384457
- ]
+ "coordinates": [-47.18951431751117, -24.789938520384457]
},
"properties": {}
},
@@ -16,10 +13,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 155.84221983372979,
- 74.01264716547318
- ]
+ "coordinates": [155.84221983372979, 74.01264716547318]
},
"properties": {}
},
@@ -27,10 +21,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 110.07076785868927,
- -89.46999216488513
- ]
+ "coordinates": [110.07076785868927, -89.46999216488513]
},
"properties": {}
},
@@ -38,10 +29,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -99.86847083205676,
- 63.7797307132169
- ]
+ "coordinates": [-99.86847083205676, 63.7797307132169]
},
"properties": {}
},
@@ -49,10 +37,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -113.37578194870969,
- -76.69367044221802
- ]
+ "coordinates": [-113.37578194870969, -76.69367044221802]
},
"properties": {}
},
@@ -60,10 +45,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 72.72561073468972,
- -66.07423396540386
- ]
+ "coordinates": [72.72561073468972, -66.07423396540386]
},
"properties": {}
},
@@ -71,10 +53,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -40.760352456527926,
- 35.2020395893229
- ]
+ "coordinates": [-40.760352456527926, 35.2020395893229]
},
"properties": {}
},
@@ -82,10 +61,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 64.5926351271439,
- -44.17513767822357
- ]
+ "coordinates": [64.5926351271439, -44.17513767822357]
},
"properties": {}
},
@@ -93,10 +69,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -42.09615220517878,
- -33.50335584434493
- ]
+ "coordinates": [-42.09615220517878, -33.50335584434493]
},
"properties": {}
},
@@ -104,10 +77,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -149.22492792801265,
- 21.841224879347543
- ]
+ "coordinates": [-149.22492792801265, 21.841224879347543]
},
"properties": {}
},
@@ -115,10 +85,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 84.29321157015941,
- 0.07515859608883346
- ]
+ "coordinates": [84.29321157015941, 0.07515859608883346]
},
"properties": {}
},
@@ -126,10 +93,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -78.3095833369632,
- 4.117647950235579
- ]
+ "coordinates": [-78.3095833369632, 4.117647950235579]
},
"properties": {}
},
@@ -137,10 +101,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -148.5588515858767,
- -63.777936872357444
- ]
+ "coordinates": [-148.5588515858767, -63.777936872357444]
},
"properties": {}
},
@@ -148,10 +109,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -100.10454457712412,
- 86.03524243483149
- ]
+ "coordinates": [-100.10454457712412, 86.03524243483149]
},
"properties": {}
},
@@ -159,10 +117,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 13.436120209269031,
- 82.41150556167928
- ]
+ "coordinates": [13.436120209269031, 82.41150556167928]
},
"properties": {}
},
@@ -170,10 +125,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 124.28432367091438,
- 8.144080311051605
- ]
+ "coordinates": [124.28432367091438, 8.144080311051605]
},
"properties": {}
},
@@ -181,10 +133,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -173.04327936439537,
- -72.49509733559518
- ]
+ "coordinates": [-173.04327936439537, -72.49509733559518]
},
"properties": {}
},
@@ -192,10 +141,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -96.18937306090523,
- -57.13860396512657
- ]
+ "coordinates": [-96.18937306090523, -57.13860396512657]
},
"properties": {}
},
@@ -203,10 +149,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 94.57455414504705,
- 68.25466741220373
- ]
+ "coordinates": [94.57455414504705, 68.25466741220373]
},
"properties": {}
},
@@ -214,10 +157,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 81.74710445221764,
- -70.17136693930061
- ]
+ "coordinates": [81.74710445221764, -70.17136693930061]
},
"properties": {}
},
@@ -225,10 +165,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -175.60441108831705,
- -5.460861619300985
- ]
+ "coordinates": [-175.60441108831705, -5.460861619300985]
},
"properties": {}
},
@@ -236,10 +173,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -141.4436798546378,
- 73.45550273035175
- ]
+ "coordinates": [-141.4436798546378, 73.45550273035175]
},
"properties": {}
},
@@ -247,10 +181,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -146.00894799433456,
- 68.81252435965777
- ]
+ "coordinates": [-146.00894799433456, 68.81252435965777]
},
"properties": {}
},
@@ -258,10 +189,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -141.1682273861793,
- -75.59589534868311
- ]
+ "coordinates": [-141.1682273861793, -75.59589534868311]
},
"properties": {}
},
@@ -269,10 +197,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -65.48510873602658,
- 63.81204037415824
- ]
+ "coordinates": [-65.48510873602658, 63.81204037415824]
},
"properties": {}
},
@@ -280,10 +205,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -138.04168562826547,
- -35.12478156027714
- ]
+ "coordinates": [-138.04168562826547, -35.12478156027714]
},
"properties": {}
},
@@ -291,10 +213,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -7.345168851766726,
- 10.815197462658052
- ]
+ "coordinates": [-7.345168851766726, 10.815197462658052]
},
"properties": {}
},
@@ -302,10 +221,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -88.88438521329691,
- -51.135607358754754
- ]
+ "coordinates": [-88.88438521329691, -51.135607358754754]
},
"properties": {}
},
@@ -313,10 +229,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 138.43728672485503,
- -49.28473394997033
- ]
+ "coordinates": [138.43728672485503, -49.28473394997033]
},
"properties": {}
},
@@ -324,10 +237,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 121.91193801130521,
- 43.62048233138311
- ]
+ "coordinates": [121.91193801130521, 43.62048233138311]
},
"properties": {}
},
@@ -335,10 +245,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -106.01217795615815,
- 86.58776759386622
- ]
+ "coordinates": [-106.01217795615815, 86.58776759386622]
},
"properties": {}
},
@@ -346,10 +253,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 155.15099140482403,
- 64.4291884202601
- ]
+ "coordinates": [155.15099140482403, 64.4291884202601]
},
"properties": {}
},
@@ -357,10 +261,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 6.285539827907183,
- 43.07090989642467
- ]
+ "coordinates": [6.285539827907183, 43.07090989642467]
},
"properties": {}
},
@@ -368,10 +269,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 173.4289638276471,
- 82.07788534436796
- ]
+ "coordinates": [173.4289638276471, 82.07788534436796]
},
"properties": {}
},
@@ -379,10 +277,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -135.69217883585586,
- -85.81180716608387
- ]
+ "coordinates": [-135.69217883585586, -85.81180716608387]
},
"properties": {}
},
@@ -390,10 +285,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -60.29750446366873,
- 63.14262347620537
- ]
+ "coordinates": [-60.29750446366873, 63.14262347620537]
},
"properties": {}
},
@@ -401,10 +293,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -62.65524929885156,
- 82.76057831274042
- ]
+ "coordinates": [-62.65524929885156, 82.76057831274042]
},
"properties": {}
},
@@ -412,10 +301,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 3.647638695141726,
- 60.28437784958815
- ]
+ "coordinates": [3.647638695141726, 60.28437784958815]
},
"properties": {}
},
@@ -423,10 +309,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 66.07623950766029,
- -53.519719711469676
- ]
+ "coordinates": [66.07623950766029, -53.519719711469676]
},
"properties": {}
},
@@ -434,10 +317,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -103.32561477332257,
- -28.078600679457146
- ]
+ "coordinates": [-103.32561477332257, -28.078600679457146]
},
"properties": {}
},
@@ -445,10 +325,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 117.02433904022372,
- -6.075675781895833
- ]
+ "coordinates": [117.02433904022372, -6.075675781895833]
},
"properties": {}
},
@@ -456,10 +333,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -77.05424621483276,
- -88.18362440176043
- ]
+ "coordinates": [-77.05424621483276, -88.18362440176043]
},
"properties": {}
},
@@ -467,10 +341,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -107.24413765555809,
- -53.31111388966479
- ]
+ "coordinates": [-107.24413765555809, -53.31111388966479]
},
"properties": {}
},
@@ -478,10 +349,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -46.29469818008551,
- 77.06906016616188
- ]
+ "coordinates": [-46.29469818008551, 77.06906016616188]
},
"properties": {}
},
@@ -489,10 +357,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 90.62559625004982,
- -58.12741750202258
- ]
+ "coordinates": [90.62559625004982, -58.12741750202258]
},
"properties": {}
},
@@ -500,10 +365,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 72.97403970358879,
- 53.71220806128216
- ]
+ "coordinates": [72.97403970358879, 53.71220806128216]
},
"properties": {}
},
@@ -511,10 +373,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -6.423696645118504,
- -54.62053611672645
- ]
+ "coordinates": [-6.423696645118504, -54.62053611672645]
},
"properties": {}
},
@@ -522,10 +381,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -92.57863432629472,
- 39.1899433173881
- ]
+ "coordinates": [-92.57863432629472, 39.1899433173881]
},
"properties": {}
},
@@ -533,10 +389,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 94.3018895274722,
- -37.87978124619209
- ]
+ "coordinates": [94.3018895274722, -37.87978124619209]
},
"properties": {}
},
@@ -544,10 +397,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -59.63452453725998,
- 9.312835101197606
- ]
+ "coordinates": [-59.63452453725998, 9.312835101197606]
},
"properties": {}
},
@@ -555,10 +405,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 32.74313358469327,
- -58.71018187460506
- ]
+ "coordinates": [32.74313358469327, -58.71018187460506]
},
"properties": {}
},
@@ -566,10 +413,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 54.08213637169812,
- 73.56937517387249
- ]
+ "coordinates": [54.08213637169812, 73.56937517387249]
},
"properties": {}
},
@@ -577,10 +421,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 71.66115695922338,
- 89.34765770532591
- ]
+ "coordinates": [71.66115695922338, 89.34765770532591]
},
"properties": {}
},
@@ -588,10 +429,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 148.22009937003904,
- 16.440042348147017
- ]
+ "coordinates": [148.22009937003904, 16.440042348147017]
},
"properties": {}
},
@@ -599,10 +437,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 109.96961209627963,
- 1.317883999627374
- ]
+ "coordinates": [109.96961209627963, 1.317883999627374]
},
"properties": {}
},
@@ -610,10 +445,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 89.11392432194302,
- 7.187090763173218
- ]
+ "coordinates": [89.11392432194302, 7.187090763173218]
},
"properties": {}
},
@@ -621,10 +453,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 7.397089922527362,
- -14.49479933018607
- ]
+ "coordinates": [7.397089922527362, -14.49479933018607]
},
"properties": {}
},
@@ -632,10 +461,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -63.33632164620309,
- 6.614277893452294
- ]
+ "coordinates": [-63.33632164620309, 6.614277893452294]
},
"properties": {}
},
@@ -643,10 +469,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 55.60109788024797,
- -50.53410339369433
- ]
+ "coordinates": [55.60109788024797, -50.53410339369433]
},
"properties": {}
},
@@ -654,10 +477,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 32.18277610111382,
- 22.619992552016637
- ]
+ "coordinates": [32.18277610111382, 22.619992552016637]
},
"properties": {}
},
@@ -665,10 +485,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 117.20235858298702,
- -38.274881672283044
- ]
+ "coordinates": [117.20235858298702, -38.274881672283044]
},
"properties": {}
},
@@ -676,10 +493,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -148.48865535761198,
- -45.39820754754232
- ]
+ "coordinates": [-148.48865535761198, -45.39820754754232]
},
"properties": {}
},
@@ -687,10 +501,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -172.18848840185686,
- -4.842754863901573
- ]
+ "coordinates": [-172.18848840185686, -4.842754863901573]
},
"properties": {}
},
@@ -698,10 +509,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 43.00795052700316,
- -44.82394719401794
- ]
+ "coordinates": [43.00795052700316, -44.82394719401794]
},
"properties": {}
},
@@ -709,10 +517,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 7.984586790965764,
- -76.02586619776837
- ]
+ "coordinates": [7.984586790965764, -76.02586619776837]
},
"properties": {}
},
@@ -720,10 +525,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 68.39612181028589,
- 43.278002948463346
- ]
+ "coordinates": [68.39612181028589, 43.278002948463346]
},
"properties": {}
},
@@ -731,10 +533,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 132.2033282476544,
- 27.382666929097876
- ]
+ "coordinates": [132.2033282476544, 27.382666929097876]
},
"properties": {}
},
@@ -742,10 +541,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -162.00643498458444,
- 36.01867885338586
- ]
+ "coordinates": [-162.00643498458444, 36.01867885338586]
},
"properties": {}
},
@@ -753,10 +549,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 43.346305680126285,
- 51.45674000464121
- ]
+ "coordinates": [43.346305680126285, 51.45674000464121]
},
"properties": {}
},
@@ -764,10 +557,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 60.99605027146011,
- -75.43512302422783
- ]
+ "coordinates": [60.99605027146011, -75.43512302422783]
},
"properties": {}
},
@@ -775,10 +565,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 117.286795694323,
- -33.18659469474927
- ]
+ "coordinates": [117.286795694323, -33.18659469474927]
},
"properties": {}
},
@@ -786,10 +573,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -104.77795396770827,
- -65.654248002992
- ]
+ "coordinates": [-104.77795396770827, -65.654248002992]
},
"properties": {}
},
@@ -797,10 +581,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -83.81159332418417,
- 89.66881367669653
- ]
+ "coordinates": [-83.81159332418417, 89.66881367669653]
},
"properties": {}
},
@@ -808,10 +589,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -145.1702655049707,
- -63.04204514510235
- ]
+ "coordinates": [-145.1702655049707, -63.04204514510235]
},
"properties": {}
},
@@ -819,10 +597,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 51.73970902606021,
- 72.56187337601145
- ]
+ "coordinates": [51.73970902606021, 72.56187337601145]
},
"properties": {}
},
@@ -830,10 +605,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -89.51497017142346,
- -11.642594253819322
- ]
+ "coordinates": [-89.51497017142346, -11.642594253819322]
},
"properties": {}
},
@@ -841,10 +613,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 78.90593679381274,
- -37.74321668577914
- ]
+ "coordinates": [78.90593679381274, -37.74321668577914]
},
"properties": {}
},
@@ -852,10 +621,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -151.65592145690476,
- 83.91736494909414
- ]
+ "coordinates": [-151.65592145690476, 83.91736494909414]
},
"properties": {}
},
@@ -863,10 +629,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 101.09924453183163,
- -86.4968375258272
- ]
+ "coordinates": [101.09924453183163, -86.4968375258272]
},
"properties": {}
},
@@ -874,10 +637,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 89.5887631550857,
- -70.98316756584224
- ]
+ "coordinates": [89.5887631550857, -70.98316756584224]
},
"properties": {}
},
@@ -885,10 +645,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 110.88640089233499,
- -61.394756050132145
- ]
+ "coordinates": [110.88640089233499, -61.394756050132145]
},
"properties": {}
},
@@ -896,10 +653,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -6.408007048114506,
- -13.206655474747105
- ]
+ "coordinates": [-6.408007048114506, -13.206655474747105]
},
"properties": {}
},
@@ -907,10 +661,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -80.34145911471012,
- 47.09413813141
- ]
+ "coordinates": [-80.34145911471012, 47.09413813141]
},
"properties": {}
},
@@ -918,10 +669,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -98.52823413653272,
- -75.34243719432504
- ]
+ "coordinates": [-98.52823413653272, -75.34243719432504]
},
"properties": {}
},
@@ -929,10 +677,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -8.397659944248783,
- -41.335025921318504
- ]
+ "coordinates": [-8.397659944248783, -41.335025921318504]
},
"properties": {}
},
@@ -940,10 +685,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 156.84815091050078,
- 25.87816836338686
- ]
+ "coordinates": [156.84815091050078, 25.87816836338686]
},
"properties": {}
},
@@ -951,10 +693,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 139.09672720530364,
- 37.35325706292393
- ]
+ "coordinates": [139.09672720530364, 37.35325706292393]
},
"properties": {}
},
@@ -962,10 +701,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 8.793646682558673,
- -34.68886988597508
- ]
+ "coordinates": [8.793646682558673, -34.68886988597508]
},
"properties": {}
},
@@ -973,10 +709,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 106.93267903996136,
- -20.689254088169832
- ]
+ "coordinates": [106.93267903996136, -20.689254088169832]
},
"properties": {}
},
@@ -984,10 +717,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 76.51651800557826,
- 66.15715833700494
- ]
+ "coordinates": [76.51651800557826, 66.15715833700494]
},
"properties": {}
},
@@ -995,10 +725,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -179.33101817473622,
- -24.97493845676012
- ]
+ "coordinates": [-179.33101817473622, -24.97493845676012]
},
"properties": {}
},
@@ -1006,10 +733,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 43.88474876000892,
- 60.44898938781364
- ]
+ "coordinates": [43.88474876000892, 60.44898938781364]
},
"properties": {}
},
@@ -1017,10 +741,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- -23.74702773345115,
- 56.44480168552528
- ]
+ "coordinates": [-23.74702773345115, 56.44480168552528]
},
"properties": {}
},
@@ -1028,10 +749,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 89.1965818771476,
- -63.127928972824996
- ]
+ "coordinates": [89.1965818771476, -63.127928972824996]
},
"properties": {}
},
@@ -1039,10 +757,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 139.17803502371603,
- 76.80553072643481
- ]
+ "coordinates": [139.17803502371603, 76.80553072643481]
},
"properties": {}
},
@@ -1050,10 +765,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 138.04162199308098,
- -14.6234595277084
- ]
+ "coordinates": [138.04162199308098, -14.6234595277084]
},
"properties": {}
},
@@ -1061,10 +773,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 174.31317825590452,
- 12.57531175694111
- ]
+ "coordinates": [174.31317825590452, 12.57531175694111]
},
"properties": {}
},
@@ -1072,10 +781,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 36.826555455819346,
- 15.871073075400028
- ]
+ "coordinates": [36.826555455819346, 15.871073075400028]
},
"properties": {}
},
@@ -1083,10 +789,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 84.65234855310884,
- 15.736922349868095
- ]
+ "coordinates": [84.65234855310884, 15.736922349868095]
},
"properties": {}
},
@@ -1094,10 +797,7 @@
"type": "Feature",
"geometry": {
"type": "Point",
- "coordinates": [
- 97.5413389416346,
- -58.1831539942764
- ]
+ "coordinates": [97.5413389416346, -58.1831539942764]
},
"properties": {}
}
diff --git a/web/.storybook/public/testdata/sample.gltf b/web/.storybook/public/testdata/sample.gltf
index e7d915e191..5e7945f846 100644
--- a/web/.storybook/public/testdata/sample.gltf
+++ b/web/.storybook/public/testdata/sample.gltf
@@ -1,70 +1,72 @@
- {
+{
"scene": 0,
- "scenes" : [
+ "scenes": [
{
- "nodes" : [ 0 ]
+ "nodes": [0]
}
],
-
- "nodes" : [
+
+ "nodes": [
{
- "mesh" : 0
+ "mesh": 0
}
],
-
- "meshes" : [
+
+ "meshes": [
{
- "primitives" : [ {
- "attributes" : {
- "POSITION" : 1
- },
- "indices" : 0
- } ]
+ "primitives": [
+ {
+ "attributes": {
+ "POSITION": 1
+ },
+ "indices": 0
+ }
+ ]
}
],
- "buffers" : [
+ "buffers": [
{
- "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
- "byteLength" : 44
+ "uri": "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
+ "byteLength": 44
}
],
- "bufferViews" : [
+ "bufferViews": [
{
- "buffer" : 0,
- "byteOffset" : 0,
- "byteLength" : 6,
- "target" : 34963
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 6,
+ "target": 34963
},
{
- "buffer" : 0,
- "byteOffset" : 8,
- "byteLength" : 36,
- "target" : 34962
+ "buffer": 0,
+ "byteOffset": 8,
+ "byteLength": 36,
+ "target": 34962
}
],
- "accessors" : [
+ "accessors": [
{
- "bufferView" : 0,
- "byteOffset" : 0,
- "componentType" : 5123,
- "count" : 3,
- "type" : "SCALAR",
- "max" : [ 2 ],
- "min" : [ 0 ]
+ "bufferView": 0,
+ "byteOffset": 0,
+ "componentType": 5123,
+ "count": 3,
+ "type": "SCALAR",
+ "max": [2],
+ "min": [0]
},
{
- "bufferView" : 1,
- "byteOffset" : 0,
- "componentType" : 5126,
- "count" : 3,
- "type" : "VEC3",
- "max" : [ 1.0, 1.0, 0.0 ],
- "min" : [ 0.0, 0.0, 0.0 ]
+ "bufferView": 1,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 3,
+ "type": "VEC3",
+ "max": [1.0, 1.0, 0.0],
+ "min": [0.0, 0.0, 0.0]
}
],
-
- "asset" : {
- "version" : "2.0"
+
+ "asset": {
+ "version": "2.0"
}
}
diff --git a/web/.storybook/public/tileset/tileset.json b/web/.storybook/public/tileset/tileset.json
index eaddb399a2..e18258af72 100644
--- a/web/.storybook/public/tileset/tileset.json
+++ b/web/.storybook/public/tileset/tileset.json
@@ -5,38 +5,13 @@
"geometricError": 500,
"root": {
"transform": [
- 96.86356343768793,
- 24.848542777253734,
- 0,
- 0,
- -15.986465724980844,
- 62.317780594908875,
- 76.5566922962899,
- 0,
- 19.02322243409411,
- -74.15554020821229,
- 64.3356267137516,
- 0,
- 1215107.7612304366,
- -4736682.902037748,
- 4081926.095098698,
- 1
+ 96.86356343768793, 24.848542777253734, 0, 0, -15.986465724980844,
+ 62.317780594908875, 76.5566922962899, 0, 19.02322243409411,
+ -74.15554020821229, 64.3356267137516, 0, 1215107.7612304366,
+ -4736682.902037748, 4081926.095098698, 1
],
"boundingVolume": {
- "box": [
- 0,
- 0,
- 0,
- 7.0955,
- 0,
- 0,
- 0,
- 3.1405,
- 0,
- 0,
- 0,
- 5.0375
- ]
+ "box": [0, 0, 0, 7.0955, 0, 0, 0, 3.1405, 0, 0, 0, 5.0375]
},
"geometricError": 1,
"refine": "REPLACE",
@@ -46,20 +21,7 @@
"children": [
{
"boundingVolume": {
- "box": [
- 0,
- 0,
- 0,
- 7.0955,
- 0,
- 0,
- 0,
- 3.1405,
- 0,
- 0,
- 0,
- 5.0375
- ]
+ "box": [0, 0, 0, 7.0955, 0, 0, 0, 3.1405, 0, 0, 0, 5.0375]
},
"geometricError": 0.1,
"content": {
@@ -68,20 +30,7 @@
"children": [
{
"boundingVolume": {
- "box": [
- 0,
- 0,
- 0,
- 7.0955,
- 0,
- 0,
- 0,
- 3.1405,
- 0,
- 0,
- 0,
- 5.0375
- ]
+ "box": [0, 0, 0, 7.0955, 0, 0, 0, 3.1405, 0, 0, 0, 5.0375]
},
"geometricError": 0,
"content": {
diff --git a/web/.storybook/theme.ts b/web/.storybook/theme.ts
index 2a97be1046..8e773e11be 100644
--- a/web/.storybook/theme.ts
+++ b/web/.storybook/theme.ts
@@ -9,5 +9,5 @@ export default create({
brandImage: "/logo.svg",
brandTarget: "_self",
colorPrimary: "#E95518",
- colorSecondary: "#4770FF",
+ colorSecondary: "#4770FF"
});
diff --git a/web/.vscode/settings.json b/web/.vscode/settings.json
index 170b9e5d8e..7dee390f26 100644
--- a/web/.vscode/settings.json
+++ b/web/.vscode/settings.json
@@ -1,7 +1,7 @@
{
- "typescript.tsdk": "node_modules/typescript/lib",
- "yaml.schemas": {
- "https://json.schemastore.org/github-workflow": "/.github/workflows/**/*.yml"
- },
- "vitest.commandLine": "yarn run vitest watch"
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "yaml.schemas": {
+ "https://json.schemastore.org/github-workflow": "/.github/workflows/**/*.yml"
+ },
+ "vitest.commandLine": "yarn run vitest watch"
}
diff --git a/web/CODE_OF_CONDUCT.md b/web/CODE_OF_CONDUCT.md
index ae596ec118..2969f5f5a4 100644
--- a/web/CODE_OF_CONDUCT.md
+++ b/web/CODE_OF_CONDUCT.md
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the
+- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
-* The use of sexualized language or imagery, and sexual attention or
+- The use of sexualized language or imagery, and sexual attention or
advances of any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email
address, without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
+- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
+standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
@@ -118,11 +118,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
-Community Impact Guidelines were inspired by
+Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
-[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
diff --git a/web/README.md b/web/README.md
index 5628083d1a..385c7f8843 100644
--- a/web/README.md
+++ b/web/README.md
@@ -1,4 +1,22 @@
-[![ci-web](https://github.com/reearth/reearth-visualizer/actions/workflows/ci_web.yml/badge.svg)](https://github.com/reearth/reearth-visualizer/actions/workflows/ci_web.yml) [![codecov](https://codecov.io/gh/reearth/reearth/branch/main/graph/badge.svg?flag=web-beta,web-utils,web-classic)](https://codecov.io/gh/reearth/reearth)
-
# reearth/web
-Documentation (coming soon)
+
+Web interface for Re:Earth GIS software built with React 18 and Vite.
+
+## Getting started
+
+### Development
+
+Add environment variables to .env. A template of the required variables are in `env.local`.
+Consult the development team for the correct environment variables.
+
+`yarn start` to start the local server. The interface will open on [http://localhost:3000](http://localhost:3000)
+
+`yarn build` TBC
+
+`yarn format` to lint code
+
+`yarn storybook` to open Storybook for web interface's UI on [http://localhost:9001](http://localhost:9001)
+
+`yarn gql` automatically generates typing of GraphQL operations.
+
+`yarn i18n` parses components to provide internationalisation for texts in supported languages.
diff --git a/web/bin/pluginDoc.ts b/web/bin/pluginDoc.ts
index cb82202904..4cc1d813d7 100644
--- a/web/bin/pluginDoc.ts
+++ b/web/bin/pluginDoc.ts
@@ -23,13 +23,13 @@ type P = {
for (const s of program.getSourceFiles()) {
if (s.fileName.substr(-5) === ".d.ts") continue;
const decls: D[] = [];
- ts.forEachChild(s, node => {
+ ts.forEachChild(s, (node) => {
const d = parse(node);
if (d) {
decls.push(d);
}
});
- const res = render(decls, n => decls.findIndex(d => d.name === n) != -1);
+ const res = render(decls, (n) => decls.findIndex((d) => d.name === n) != -1);
// writeFileSync("./docs/plugin.md", res);
console.log(res);
}
@@ -50,7 +50,7 @@ function parse(node: ts.Node): D | undefined {
name: s.name,
type: tc.typeToString(t),
required: (s.flags & ts.SymbolFlags.Optional) === 0,
- desc: ts.displayPartsToString(s.getDocumentationComment(tc)),
+ desc: ts.displayPartsToString(s.getDocumentationComment(tc))
};
})
.filter((p): p is P => !!p);
@@ -58,17 +58,19 @@ function parse(node: ts.Node): D | undefined {
return {
name: node.name.text,
desc: ts.displayPartsToString(sym.getDocumentationComment(tc)),
- properties,
+ properties
};
}
function render(decls: D[], linkable: (name: string) => boolean): string {
return decls
- .flatMap(d => [
+ .flatMap((d) => [
`# ${d.name}`,
d.desc,
- d.properties.map(p => `- ${renderHead(p, linkable)}`).join("\n"),
- ...d.properties.filter(p => p.desc).flatMap(p => [`## ${renderHead(p, linkable)}`, p.desc]),
+ d.properties.map((p) => `- ${renderHead(p, linkable)}`).join("\n"),
+ ...d.properties
+ .filter((p) => p.desc)
+ .flatMap((p) => [`## ${renderHead(p, linkable)}`, p.desc])
])
.filter(Boolean)
.join("\n\n");
@@ -78,17 +80,37 @@ function renderHead(p: P, linkable: (name: string) => boolean): string {
return [p.name, ": ", renderType(p.type, p.required, linkable)].join("");
}
-function renderType(type: string, required: boolean, linkable: (name: string) => boolean) {
- const ts = split(type, [") => ", "[]", "?: ", ": ", " & ", " | ", "(", ")", ", "])
- .map(s =>
+function renderType(
+ type: string,
+ required: boolean,
+ linkable: (name: string) => boolean
+) {
+ const ts = split(type, [
+ ") => ",
+ "[]",
+ "?: ",
+ ": ",
+ " & ",
+ " | ",
+ "(",
+ ")",
+ ", "
+ ])
+ .map((s) =>
typeof s === "string" && linkable(s)
? {
- link: s,
+ link: s
}
- : s,
+ : s
)
- .map(s =>
- typeof s === "string" ? s : "link" in s ? `[${s.link}](#${s.link})` : "s" in s ? s.s : "",
+ .map((s) =>
+ typeof s === "string"
+ ? s
+ : "link" in s
+ ? `[${s.link}](#${s.link})`
+ : "s" in s
+ ? s.s
+ : ""
)
.join("");
const func = / => /.test(type);
@@ -96,7 +118,7 @@ function renderType(type: string, required: boolean, linkable: (name: string) =>
!required && func ? "(" : "",
ts,
!required && func ? ")" : "",
- !required ? " | undefined" : "",
+ !required ? " | undefined" : ""
].join("");
}
@@ -104,9 +126,9 @@ function split(text: string, splitter: string[]): (string | { s: string })[] {
if (!text.length) return [];
const res: (string | { s: string })[] = [];
let buf = "";
- for (let i = 0; i < text.length; i++) {
- buf += text[i];
- const s = splitter.find(s => buf.includes(s));
+ for (const t of text) {
+ buf += t;
+ const s = splitter.find((s) => buf.includes(s));
if (s) {
res.push(buf.slice(0, -s.length), { s: buf.slice(-s.length) });
buf = "";
diff --git a/web/codegen.ts b/web/codegen.ts
index a9c2e23588..9315424f21 100644
--- a/web/codegen.ts
+++ b/web/codegen.ts
@@ -7,13 +7,16 @@ const rootGenerateDirectory = `${rootGQLDirectory}__gen__/`;
const config: CodegenConfig = {
overwrite: true,
schema: "../server/gql/*.graphql",
- documents: [`${rootGQLDirectory}fragments/*.ts`, `${rootGQLDirectory}queries/*.ts`],
+ documents: [
+ `${rootGQLDirectory}fragments/*.ts`,
+ `${rootGQLDirectory}queries/*.ts`
+ ],
generates: {
[rootGenerateDirectory]: {
preset: "client",
presetConfig: {
gqlTagName: "gql",
- fragmentMasking: false,
+ fragmentMasking: false
},
config: {
useTypeImports: true,
@@ -25,15 +28,15 @@ const config: CodegenConfig = {
URL: "string",
Lang: "string",
TranslatedString: "{ [lang in string]?: string } | null",
- JSON: "any",
- },
- },
+ JSON: "any"
+ }
+ }
},
[`${rootGenerateDirectory}/fragmentMatcher.json`]: {
- plugins: ["fragment-matcher"],
- },
+ plugins: ["fragment-matcher"]
+ }
},
- ignoreNoDocuments: true,
+ ignoreNoDocuments: true
};
export default config;
diff --git a/web/e2e/utils/config.ts b/web/e2e/utils/config.ts
index 8734db8b86..1ad5d2cb43 100644
--- a/web/e2e/utils/config.ts
+++ b/web/e2e/utils/config.ts
@@ -9,8 +9,7 @@ export const config = {
authUrl: process.env["REEARTH_WEB_AUTH0_DOMAIN"],
signUpSecret: process.env["REEARTH_WEB_E2E_SIGNUP_SECRET"],
authProvider: process.env["REEARTH_WEB_AUTH_PROVIDER"],
- developerMode: process.env["REEARTH_WEB_DEVELOPER_MODE"],
- earlyAccessAdmins: process.env["REEARTH_WEB_EARLY_ACCESS_ADMINS"],
+ earlyAccessAdmins: process.env["REEARTH_WEB_EARLY_ACCESS_ADMINS"]
};
export type Config = typeof config;
diff --git a/web/e2e/utils/index.ts b/web/e2e/utils/index.ts
index 42cfb8acd3..56548dc917 100644
--- a/web/e2e/utils/index.ts
+++ b/web/e2e/utils/index.ts
@@ -1,5 +1,10 @@
// eslint-disable-next-line no-restricted-imports
-import { type APIRequestContext, request, test as base, type Page } from "@playwright/test";
+import {
+ type APIRequestContext,
+ request,
+ test as base,
+ type Page
+} from "@playwright/test";
import { config, getAccessToken, type Config } from "./config";
@@ -18,7 +23,7 @@ export type Reearth = {
gql: (
query: string,
variables?: Record,
- options?: { ignoreError?: boolean },
+ options?: { ignoreError?: boolean }
) => Promise;
} & Config;
@@ -35,7 +40,9 @@ export const test = base.extend<{
async goto(url, options) {
const res = await page.goto(url, options);
if (this.token) {
- await page.evaluate(`window.REEARTH_E2E_ACCESS_TOKEN = ${JSON.stringify(this.token)};`);
+ await page.evaluate(
+ `window.REEARTH_E2E_ACCESS_TOKEN = ${JSON.stringify(this.token)};`
+ );
}
return res;
},
@@ -45,11 +52,11 @@ export const test = base.extend<{
const resp = await request.post(config.api + "/graphql", {
data: {
query,
- variables,
+ variables
},
headers: {
- Authorization: `Bearer ${this.token}`,
- },
+ Authorization: `Bearer ${this.token}`
+ }
});
const body = await resp.json();
@@ -58,14 +65,14 @@ export const test = base.extend<{
}
return body;
- },
+ }
});
- },
+ }
});
export async function initUser(
token: string,
- ctx?: APIRequestContext,
+ ctx?: APIRequestContext
): Promise<{
token: string;
userId: string;
@@ -85,8 +92,8 @@ export async function initUser(
userId,
teamId,
api,
- signUpSecret: signUpSecret ? "***" : "",
- })}`,
+ signUpSecret: signUpSecret ? "***" : ""
+ })}`
);
}
@@ -102,12 +109,12 @@ export async function initUser(
userId,
teamId,
secret: signUpSecret,
- lang: "en",
- },
+ lang: "en"
+ }
},
headers: {
- Authorization: `Bearer ${token}`,
- },
+ Authorization: `Bearer ${token}`
+ }
});
const body = await resp.json();
@@ -118,8 +125,8 @@ export async function initUser(
userId,
teamId,
api,
- signUpSecret: signUpSecret ? "***" : "",
- })}`,
+ signUpSecret: signUpSecret ? "***" : ""
+ })}`
);
}
@@ -127,6 +134,6 @@ export async function initUser(
token,
userName,
userId: body.data.signup.user.id,
- teamId,
+ teamId
};
}
diff --git a/web/e2e/utils/login.ts b/web/e2e/utils/login.ts
index 1251d46cd2..af596490e2 100644
--- a/web/e2e/utils/login.ts
+++ b/web/e2e/utils/login.ts
@@ -13,9 +13,9 @@ export async function login(): Promise {
userName,
password: password ? "***" : "",
authAudience,
- authClientId,
- },
- )}`,
+ authClientId
+ }
+ )}`
);
}
@@ -28,8 +28,8 @@ export async function login(): Promise {
audience: authAudience,
client_id: authClientId,
grant_type: "password",
- scope: "openid profile email",
- },
+ scope: "openid profile email"
+ }
);
if (!resp.data.access_token) {
@@ -43,8 +43,8 @@ export async function login(): Promise {
userName,
password: password ? "***" : "",
authAudience,
- authClientId,
- })}`,
+ authClientId
+ })}`
);
}
}
diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs
new file mode 100644
index 0000000000..ddb38d5236
--- /dev/null
+++ b/web/eslint.config.mjs
@@ -0,0 +1,40 @@
+import config from "eslint-config-reearth";
+import playwright from "eslint-plugin-playwright";
+
+/** @type { import("eslint").Linter.Config[] } */
+
+const themeConfig = [
+ {
+ rules: {
+ "@typescript-eslint/consistent-type-definitions": "off",
+ "@typescript-eslint/no-empty-object-type": [
+ "error",
+ {
+ allowInterfaces: "with-single-extends"
+ }
+ ]
+ }
+ }
+];
+
+const e2eConfig = [
+ {
+ files: ["e2e/**/*"],
+ ...playwright.configs["flat/recommended"],
+ rules: {
+ "no-restricted-imports": [
+ "error",
+ {
+ paths: [
+ {
+ name: "@playwright/test",
+ message: "Use @reearth/e2e/utils instead."
+ }
+ ]
+ }
+ ]
+ }
+ }
+];
+
+export default [...config("@reearth"), ...e2eConfig, ...themeConfig];
diff --git a/web/i18next-parser.config.js b/web/i18next-parser.config.js
index 403bb187f6..4723a3bb53 100644
--- a/web/i18next-parser.config.js
+++ b/web/i18next-parser.config.js
@@ -5,5 +5,5 @@ export default {
// allow keys to be phrases having `:`, `.`
namespaceSeparator: false,
keySeparator: false,
- createOldCatalogs: false,
+ createOldCatalogs: false
};
diff --git a/web/index.html b/web/index.html
index ab4c00810b..43379d90c8 100644
--- a/web/index.html
+++ b/web/index.html
@@ -1,21 +1,22 @@
-
+
+
+
+ Re:Earth
+
+
+
+
+
+
-
-
- Re:Earth
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
+
+
+